Coverage for pygeodesy/utily.py: 94%

1

2# -*- coding: utf-8 -*-

3

4 u'''Various utility functions.

5

6 After I{Karney}'s C++ U{Math<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}

7 class and I{Veness}' U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>}

8 and U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}

9 and published under the same MIT Licence**.

10'''

11# make sure int/int division yields float quotient, see .basics

12 from __future__ import division as _; del _ # noqa: E702 ;

13

14 from pygeodesy.basics import _copysign, isinstanceof, isint, isstr

15 from pygeodesy.constants import EPS, EPS0, NAN, PI, PI2, PI_2, PI_4, PI_6, R_M, \

16 _M_KM, _M_NM, _M_SM, _SQRT2_2 as _COS_45, \

17 _SQRT3_2 as _COS_30, _0_0, _0_5, _1_0, _N_1_0, \

18 _10_0, _90_0, _180_0, _360_0, _copysign_0_0, \

19 _copysignINF, _float, _isfinite, isnan, isnear0, \

20 _over_1, _umod_360, _umod_PI2, OVERFLOW

21 from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get

22 from pygeodesy.internals import _Enum, _passargs, typename

23 from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_

24 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS

25 from pygeodesy.units import Degrees, Degrees_, Feet, Float, Lam, Lamd, \

26 Meter, Meter2, Radians # Radians_

27

28 from math import acos, asin, asinh, atan2 as _atan2, cos, degrees, fabs, \

29 radians, sin, sinh, tan as _tan # pow

30

31 __all__ = _ALL_LAZY.utily

32 __version__ = '26.03.20'

33

34 _SIN_30 = _0_5

35 _SIN_45 = _COS_45

36

37 _G = _Enum( # grades per ...

38 DEG = _float( 400.0 / _360_0), # degree

39 RAD = _float( 400.0 / PI2)) # radian

40

41 _M = _Enum( # meter per ...

42 ACRE = _float( 4046.8564224), # acre, chain2m(1) * furlong2m(1), squared

43 CHAIN = _float( 20.1168), # yard2m(1) * 22

44 FATHOM = _float( 1.8288), # yard2m(1) * 2 or _M.NM * 1e-3

45 FOOT = _float( 0.3048), # Int'l foot, 1 / 3.280_839_895_0131 == (254 * 12) / 10_000

46 FOOT_FR = _float( 0.3248406), # French Pied-du-Roi or pied, 1 / 3.078_432_929_8739

47 FOOT_GE = _float( 0.31608), # German Fuss, 1 / 3.163_756_011_1364

48 FOOT_IN = _float( 0.304799514), # India foot, 1 / 3.280_845_126_281_9

49 FOOT_US = _float( 0.3048006096012192), # US Survey foot, 1_200 / 3_937

50 FURLONG = _float( 201.168), # furlong, 220 * yard2m(1) = 10 * m2chain(1)

51 HA = _float(10000.0), # hectare, 100 * 100, squared

52 KM = _M_KM, # kilo meter

53 NM = _M_NM, # nautical mile

54 SM = _M_SM, # statute mile

55 TOISE = _float( 1.9490436), # French toise, 6 pieds = 6 / 3.078_432_929_8739

56 YARD = _float( 0.9144), # Int'l yard, 254 * 12 * 3 / 10_000 = 3 * _M.FOOT

57 YARD_UK = _float( 0.914398415)) # Imperial yard, until 1959

58

59

60 def _abs1nan(x):

61 '''(INTERNAL) Bracket C{-1 < x < 1 or isnan(x)}.

62 '''

63 return _N_1_0 < x < _1_0 or isnan(x)

64

65

66 def acos1(x):

67 '''Return C{math.acos(max(-1, min(1, B{x})))}.

68 '''

69 return acos(x) if _abs1nan(x) else (PI if x < 0 else _0_0)

70

71

72 def acre2ha(acres):

73 '''Convert an area in I{acres} to I{hectares}.

74

75 @arg acres: Area in acres (C{scalar}).

76

77 @return: Area in hectares (C{float}).

78

79 @raise ValueError: Invalid B{C{acres}}.

80 '''

81 return m2ha(acre2m2(acres))

82

83

84 def acre2m2(acres):

85 '''Convert an area in I{acres} to I{square} meter.

86

87 @arg acres: Area in acres (C{scalar}).

88

89 @return: Area in C{square} meter (C{float}).

90

91 @raise ValueError: Invalid B{C{acres}}.

92 '''

93 return Meter2(Float(acres=acres) * _M.ACRE)

94

95

96 def agdf(phi):

97 '''Inverse U{Gudermannian function

98 <https://WikiPedia.org/wiki/Gudermannian_function>}.

99

100 @arg phi: Angle (C{radians}).

101

102 @return: Gudermannian (psi, C{float}).

103

104 @see: Function L{gdf}.

105 '''

106 return asinh(tan(phi))

107

108

109 def asin1(x):

110 '''Return C{math.asin(max(-1, min(1, B{x})))}.

111 '''

112 return asin(x) if _abs1nan(x) else _copysign(PI_2, x)

113

114

115 def atan1(y, x=_1_0):

116 '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}

117 using C{atan2} for consistency and to avoid C{ZeroDivisionError}.

118 '''

119 return _atan1u(y, x, atan2)

120

121

122 def atan1d(y, x=_1_0):

123 '''Return C{atan(B{y} / B{x})} angle in C{degrees} M{[-90..+90]}

124 using C{atan2d} for consistency and to avoid C{ZeroDivisionError}.

125

126 @see: Function L{pygeodesy.atan2d}.

127 '''

128 return _atan1u(y, x, atan2d)

129

130

131 def _atan1u(y, x, _2u):

132 '''(INTERNAL) Helper for functions C{atan1} and C{atan1d}.

133 '''

134 if x < 0:

135 x = -x

136 y = -y

137 return _2u(y, x or _0_0)

138

139

140 atan2 = _atan2

141'''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.

142'''

143

144

145 def atan2b(y, x):

146 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360), counter-clockwise}.

147

148 @see: Function L{pygeodesy.atan2p} and L{pygeodesy.atan2d}.

149 '''

150 b = atan2d(y, x)

151 if b < 0:

152 b += _360_0

153 return b or _0_0 # unsigned-0

154

155

156 def atan2d(y, x, reverse=False):

157 '''Return C{atan2(B{y}, B{x})} in degrees M{[-180..+180]},

158 optionally I{reversed} (by 180 degrees for C{azimuth}s).

159

160 @see: I{Karney}'s C++ function U{Math.atan2d

161 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.

162 '''

163 d = degrees(_atan2(y, x)) # preserves signed-0

164 return _azireversed(d) if reverse else d

165

166

167 def atan2p(y, x): # in .fmath.polar2

168 '''Return C{atan2(B{y}, B{x})} in radians M{[0..+PI2), counter-clockwise}.

169

170 @see: Function L{pygeodesy.atan2b}.

171 '''

172 p = atan2(y, x)

173 if p < 0:

174 p += PI2

175 return p or _0_0 # unsigned-0

176

177

178 def _azireversed(azi): # in .rhumb.bases

179 '''(INTERNAL) Return the I{reverse} B{C{azi}} in degrees M{[-180..+180]}.

180 '''

181 return (azi + _180_0) if azi < 0 else (azi - _180_0)

182

183

184 def chain2m(chains):

185 '''Convert a length in I{UK} chains to meter.

186

187 @arg chains: Length in chains (C{scalar}).

188

189 @return: Length in C{meter} (C{float}).

190

191 @raise ValueError: Invalid B{C{chains}}.

192 '''

193 return Meter(Float(chains=chains) * _M.CHAIN)

194

195

196 def circle4(earth, lat):

197 '''Get the equatorial or a parallel I{circle of latitude}.

198

199 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum

200 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

201 @arg lat: Geodetic latitude (C{degrees90}, C{str}).

202

203 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}.

204

205 @raise RangeError: Latitude B{C{lat}} outside valid range and

206 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

207

208 @raise TypeError: Invalid B{C{earth}}.

209

210 @raise ValueError: invalid B{C{earth}} or B{C{lat}}.

211 '''

212 E = _MODS.datums._earth_ellipsoid(earth)

213 return E.circle4(lat)

214

215

216 def _circle4radius(earth, lat=0, **radius):

217 '''(INTERNAL) Get C{circle4(earth, lat).radius}.

218 '''

219 e = _xkwds_get(radius, radius=earth) if radius else earth

220 return e if e is R_M and not lat else circle4(e, lat).radius

221

222

223 def cot(rad, **raiser_kwds):

224 '''Return the C{cotangent} of an angle in C{radians}.

225

226 @arg rad: Angle (C{radians}).

227 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

228 ValueErrors and optional, additional

229 ValueError keyword argments.

230

231 @return: C{cot(B{rad})}.

232

233 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{rad})}.

234 '''

235 try:

236 return _cotu(*sincos2(rad))

237 except ZeroDivisionError:

238 return _nonfinite(cot, rad, **raiser_kwds)

239

240

241 def cot_(*rads, **raiser_kwds):

242 '''Yield the C{cotangent} of angle(s) in C{radians}.

243

244 @arg rads: One or more angles (each in C{radians}).

245

246 @return: Yield C{cot(B{rad})} for each angle.

247

248 @see: Function L{pygeodesy.cot} for further details.

249 '''

250 try:

251 for r in rads:

252 yield _cotu(*sincos2(r))

253 except ZeroDivisionError:

254 yield _nonfinite(cot_, r, **raiser_kwds)

255

256

257 def cotd(deg, **raiser_kwds):

258 '''Return the C{cotangent} of an angle in C{degrees}.

259

260 @arg deg: Angle (C{degrees}).

261 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

262 ValueErrors and optional, additional

263 ValueError keyword argments.

264

265 @return: C{cot(B{deg})}.

266

267 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{deg})}.

268 '''

269 try:

270 return _cotu(*sincos2d(deg))

271 except ZeroDivisionError:

272 return _nonfinite(cotd, deg, **raiser_kwds)

273

274

275 def cotd_(*degs, **raiser_kwds):

276 '''Yield the C{cotangent} of angle(s) in C{degrees}.

277

278 @arg degs: One or more angles (each in C{degrees}).

279

280 @return: Yield C{cotd(B{deg})} for each angle.

281

282 @see: Function L{pygeodesy.cotd} for further details.

283 '''

284 try:

285 for d in degs:

286 yield _cotu(*sincos2d(d))

287 except ZeroDivisionError:

288 yield _nonfinite(cotd_, d, **raiser_kwds)

289

290

291 def _cotu(s, c):

292 '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.

293 '''

294 return _tanu(c, s)

295

296

297 def degrees90(rad):

298 '''Convert radians to degrees and wrap M{[-90..+90)}.

299

300 @arg rad: Angle (C{radians}).

301

302 @return: Angle, wrapped (C{degrees90}).

303 '''

304 return wrap90(degrees(rad))

305

306

307 def degrees180(rad):

308 '''Convert radians to degrees and wrap M{[-180..+180)}.

309

310 @arg rad: Angle (C{radians}).

311

312 @return: Angle, wrapped (C{degrees180}).

313 '''

314 return wrap180(degrees(rad))

315

316

317 def degrees360(rad):

318 '''Convert radians to degrees and wrap M{[0..+360)}.

319

320 @arg rad: Angle (C{radians}).

321

322 @return: Angle, wrapped (C{degrees360}).

323 '''

324 return _umod_360(degrees(rad))

325

326

327 def degrees2grades(deg):

328 '''Convert degrees to I{grades} (aka I{gons} or I{gradians}).

329

330 @arg deg: Angle (C{degrees}).

331

332 @return: Angle (C{grades}).

333 '''

334 return Float(grades=Degrees(deg) * _G.DEG)

335

336

337 def degrees2m(deg, earth=R_M, lat=0, **radius):

338 '''Convert an arc in degrees to a distance along the equator or along a

339 parallel at (geodetic) latitude.

340

341 @arg deg: The (longitudinal) angle (C{degrees} or C{str}).

342 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum

343 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

344 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

345 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

346

347 @return: Distance (C{meter}, same units as B{C{earth}} or polar and

348 equatorial radii) or C{0.0} for near-polar B{C{lat}}.

349

350 @raise RangeError: Latitude B{C{lat}} outside valid range, only if

351 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

352

353 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

354

355 @raise ValueError: Invalid B{C{deg}}, B{C{earth}} or B{C{lat}}.

356

357 @see: Function L{radians2m} and L{m2degrees}.

358 '''

359 return _rad2m(Lamd(deg=deg, clip=0), earth, lat, **radius)

360

361

362 def fathom2m(fathoms):

363 '''Convert a length in I{Imperial} fathoms to meter.

364

365 @arg fathoms: Length in fathoms (C{scalar}).

366

367 @return: Length in C{meter} (C{float}).

368

369 @raise ValueError: Invalid B{C{fathoms}}.

370

371 @see: Function L{toise2m}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}

372 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.

373 '''

374 return Meter(Float(fathoms=fathoms) * _M.FATHOM)

375

376

377 def ft2m(feet, usurvey=False, pied=False, india=False, fuss=False):

378 '''Convert a length in I{International}, I{US Survey}, I{French},

379 I{Indian} or I{German} feet to meter.

380

381 @arg feet: Length in feet (C{scalar}).

382 @kwarg usurvey: If C{True}, convert I{US Survey} foot else ...

383 @kwarg pied: If C{True}, convert French I{pied-du-Roi} else ...

384 @kwarg india: If C{True}, convert I{India foot} else ...

385 @kwarg fuss: If C{True}, convert German I{Fuss}, otherwise

386 I{International} foot to C{meter}.

387

388 @return: Length in C{meter} (C{float}).

389

390 @raise ValueError: Invalid B{C{feet}}.

391 '''

392 return Meter(Feet(feet) * (_M.FOOT_US if usurvey else

393 (_M.FOOT_FR if pied else

394 (_M.FOOT_IN if india else

395 (_M.FOOT_GE if fuss else _M.FOOT)))))

396

397

398 def furlong2m(furlongs):

399 '''Convert a length in I{furlongs} to meter.

400

401 @arg furlongs: Length in furlongs (C{scalar}).

402

403 @return: Length in C{meter} (C{float}).

404

405 @raise ValueError: Invalid B{C{furlongs}}.

406 '''

407 return Meter(Float(furlongs=furlongs) * _M.FURLONG)

408

409

410 def gdf(psi):

411 '''U{Gudermannian function

412 <https://WikiPedia.org/wiki/Gudermannian_function>}.

413

414 @arg psi: Gudermannian (C{float}).

415

416 @return: Angle (C{radians}).

417

418 @see: Function L{agdf}.

419 '''

420 return atan1(sinh(psi))

421

422

423 def grades(rad):

424 '''Convert radians to I{grades} (aka I{gons} or I{gradians}).

425

426 @arg rad: Angle (C{radians}).

427

428 @return: Angle (C{grades}).

429 '''

430 return Float(grades=Radians(rad) * _G.RAD)

431

432

433 def grades400(rad):

434 '''Convert radians to I{grades} (aka I{gons} or I{gradians}) and wrap M{[0..+400)}.

435

436 @arg rad: Angle (C{radians}).

437

438 @return: Angle, wrapped (C{grades}).

439 '''

440 return Float(grades400=wrapPI2(rad) * _G.RAD)

441

442

443 def grades2degrees(gon):

444 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{degrees}.

445

446 @arg gon: Angle (C{grades}).

447

448 @return: Angle (C{degrees}).

449 '''

450 return Degrees(Float(gon=gon) / _G.DEG)

451

452

453 def grades2radians(gon):

454 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{radians}.

455

456 @arg gon: Angle (C{grades}).

457

458 @return: Angle (C{radians}).

459 '''

460 return Radians(Float(gon=gon) / _G.RAD)

461

462

463 def ha2acre(ha):

464 '''Convert an area in I{hectares} to I{acres}.

465

466 @arg ha: Area in hectares (C{scalar}).

467

468 @return: Area in C{acres} (C{float}).

469

470 @raise ValueError: Invalid B{C{ha}}.

471 '''

472 return m2acre(ha2m2(ha))

473

474

475 def ha2m2(ha):

476 '''Convert an area in I{hectares} to I{square} meter.

477

478 @arg ha: Area in hectares (C{scalar}).

479

480 @return: Area in C{square} meter (C{float}).

481

482 @raise ValueError: Invalid B{C{ha}}.

483 '''

484 return Meter2(Float(ha=ha) * _M.HA)

485

486

487 def hav(rad):

488 '''Return the U{haversine<https://WikiPedia.org/wiki/Haversine_formula>} of an angle.

489

490 @arg rad: Angle (C{radians}).

491

492 @return: C{sin(B{rad} / 2)**2}.

493 '''

494 return sin(rad * _0_5)**2

495

496

497 def km2m(km):

498 '''Convert a length in I{kilo meter} to meter (C{m}).

499

500 @arg km: Length in kilo meter (C{scalar}).

501

502 @return: Length in meter (C{float}).

503

504 @raise ValueError: Invalid B{C{km}}.

505 '''

506 return Meter(Float(km=km) * _M.KM)

507

508

509 def _loneg(lon):

510 '''(INTERNAL) "Complement" of C{lon}.

511 '''

512 return _180_0 - lon

513

514

515 def m2acre(meter2):

516 '''Convert an area in I{square} meter to I{acres}.

517

518 @arg meter2: Area in C{square} meter (C{scalar}).

519

520 @return: Area in acres (C{float}).

521

522 @raise ValueError: Invalid B{C{meter2}}.

523 '''

524 return Float(acre=Meter2(meter2) / _M.ACRE)

525

526

527 def m2chain(meter):

528 '''Convert a length in meter to I{UK} chains.

529

530 @arg meter: Length in meter (C{scalar}).

531

532 @return: Length in C{chains} (C{float}).

533

534 @raise ValueError: Invalid B{C{meter}}.

535 '''

536 return Float(chain=Meter(meter) / _M.CHAIN) # * 0.049_709_695_378_986_715

537

538

539 def m2degrees(distance, earth=R_M, lat=0, **radius):

540 '''Convert a distance to an arc in degrees along the equator or along a

541 parallel at (geodetic) latitude.

542

543 @arg distance: Distance (C{meter}, same units as B{C{radius}}).

544 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

545 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

546 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

547 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

548

549 @return: Angle (C{degrees}) or C{0.0,} for near-polar B{C{lat}}.

550

551 @raise RangeError: Latitude B{C{lat}} outside valid range, only if

552 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

553

554 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

555

556 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.

557

558 @see: Function L{m2radians} and L{degrees2m}.

559 '''

560 return Degrees(degrees(_m2rad(distance, earth, lat, **radius)))

561

562

563 def m2fathom(meter):

564 '''Convert a length in meter to I{Imperial} fathoms.

565

566 @arg meter: Length in meter (C{scalar}).

567

568 @return: Length in C{fathoms} (C{float}).

569

570 @raise ValueError: Invalid B{C{meter}}.

571

572 @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}

573 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.

574 '''

575 return Float(fathom=Meter(meter) / _M.FATHOM) # * 0.546_806_649

576

577

578 def m2ft(meter, usurvey=False, pied=False, india=False, fuss=False):

579 '''Convert a length in meter to I{International}, I{US Survey},

580 I{French}, I{Indian} or I{German} feet (C{ft}).

581

582 @arg meter: Length in meter (C{scalar}).

583 @kwarg usurvey: If C{True}, convert to I{US Survey} foot else ...

584 @kwarg pied: If C{True}, convert to French I{pied-du-Roi} else ...

585 @kwarg india: If C{True}, convert to I{India foot} else ...

586 @kwarg fuss: If C{True}, convert to German I{Fuss}, otherwise to

587 I{International} foot.

588

589 @return: Length in C{feet} (C{float}).

590

591 @raise ValueError: Invalid B{C{meter}}.

592 '''

593 # * 3.280_833_333_333_3333, US Survey 3_937 / 1_200

594 # * 3.280_839_895_013_1235, Int'l 10_000 / (254 * 12)

595 return Float(feet=Meter(meter) / (_M.FOOT_US if usurvey else

596 (_M.FOOT_FR if pied else

597 (_M.FOOT_IN if india else

598 (_M.FOOT_GE if fuss else _M.FOOT)))))

599

600

601 def m2furlong(meter):

602 '''Convert a length in meter to furlongs.

603

604 @arg meter: Length in meter (C{scalar}).

605

606 @return: Length in C{furlongs} (C{float}).

607

608 @raise ValueError: Invalid B{C{meter}}.

609 '''

610 return Float(furlong=Meter(meter) / _M.FURLONG) # * 0.004_970_969_54

611

612

613 def m2ha(meter2):

614 '''Convert an area in I{square} meter to I{hectares}.

615

616 @arg meter2: Area in C{square} meter (C{scalar}).

617

618 @return: Area in hectares (C{float}).

619

620 @raise ValueError: Invalid B{C{meter2}}.

621 '''

622 return Float(ha=Meter2(meter2) / _M.HA)

623

624

625 def m2km(meter):

626 '''Convert a length in meter to kilo meter (C{Km}).

627

628 @arg meter: Length in meter (C{scalar}).

629

630 @return: Length in C{Km} (C{float}).

631

632 @raise ValueError: Invalid B{C{meter}}.

633 '''

634 return Float(km=Meter(meter) / _M.KM)

635

636

637 def m2NM(meter):

638 '''Convert a length in meter to nautical miles (C{NM}).

639

640 @arg meter: Length in meter (C{scalar}).

641

642 @return: Length in C{NM} (C{float}).

643

644 @raise ValueError: Invalid B{C{meter}}.

645 '''

646 return Float(NM=Meter(meter) / _M.NM) # * 5.399_568_04e-4

647

648

649 def _m2rad(distance, earth, lat, **radius):

650 '''(INTERNAL) Helper for C{m2degrees} and C{m2radians}.

651 '''

652 d = Float(distance=distance)

653 m = _circle4radius(earth, lat, **radius)

654 return _copysign_0_0(d) if m < EPS0 else (d / m) # radians

655

656

657 def m2radians(distance, earth=R_M, lat=0, **radius):

658 '''Convert a distance to an arc in radians along the equator or along a

659 parallel at (geodetic) latitude.

660

661 @arg distance: Distance (C{meter}, same units as B{C{radius}}).

662 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

663 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

664 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

665 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

666

667 @return: Angle (C{radians}) or C{0.0} for near-polar B{C{lat}}.

668

669 @raise RangeError: Latitude B{C{lat}} outside valid range, only if

670 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

671

672 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

673

674 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.

675

676 @see: Function L{m2degrees} and L{radians2m}.

677 '''

678 return Radians(_m2rad(distance, earth, lat, **radius))

679

680

681 def m2SM(meter):

682 '''Convert a length in meter to statute miles (SM).

683

684 @arg meter: Length in meter (C{scalar}).

685

686 @return: Length in C{SM} (C{float}).

687

688 @raise ValueError: Invalid B{C{meter}}.

689 '''

690 return Float(SM=Meter(meter) / _M.SM) # * 6.213_699_49e-4 == 1 / 1_609.344

691

692

693 def m2toise(meter):

694 '''Convert a length in meter to French U{toises

695 <https://WikiPedia.org/wiki/Toise>}.

696

697 @arg meter: Length in meter (C{scalar}).

698

699 @return: Length in C{toises} (C{float}).

700

701 @raise ValueError: Invalid B{C{meter}}.

702

703 @see: Function L{m2fathom}.

704 '''

705 return Float(toise=Meter(meter) / _M.TOISE) # * 0.513_083_632_632_119

706

707

708 def m2yard(meter, imperial=False):

709 '''Convert a length in meter to I{International} or U{Imperial

710 Standard <https://WikiPedia.org/wiki/Imperial_units>} yards.

711

712 @arg meter: Length in meter (C{scalar}).

713 @kwarg imperial: If C{True}, convert to I{Imperial Standard} yards.

714

715 @return: Length in C{yards} (C{float}).

716

717 @raise ValueError: Invalid B{C{meter}}.

718 '''

719 return Float(yard=Meter(meter) / (_M.YARD_UK if imperial else

720 _M.YARD)) # * 1.093_613_298_337_707_8

721

722

723 def NM2m(nm):

724 '''Convert a length in nautical miles to meter (C{m}).

725

726 @arg nm: Length in nautical miles (C{scalar}).

727

728 @return: Length in meter (C{float}).

729

730 @raise ValueError: Invalid B{C{nm}}.

731 '''

732 return Meter(Float(nm=nm) * _M.NM)

733

734

735 def _nonfinite(where, x, raiser=True, **kwds): # PYCHOK no cover

736 '''(INTERNAL) Raise a C{_ValueError} or return C{INF} or C{NINF}.

737 '''

738 if raiser:

739 t = typename(where)

740 t = _MODS.streprs.Fmt.PAREN(t, x)

741 raise _ValueError(t, **kwds)

742 return _copysignINF(x)

743

744

745 def radians2m(rad, earth=R_M, lat=0, **radius):

746 '''Convert an arc in radians to a distance along the equator or along a

747 parallel at (geodetic) latitude.

748

749 @arg rad: The (longitudinal) angle (C{radians} or C{str}).

750 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum

751 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).

752 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).

753 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.

754

755 @return: Distance (C{meter}, same units as B{C{earth}} or polar and

756 equatorial radii) or C{0.0} for near-polar B{C{lat}}.

757

758 @raise RangeError: Latitude B{C{lat}} outside valid range, only if

759 L{rangerrors<pygeodesy.rangerrors>} is C{True}.

760

761 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.

762

763 @raise ValueError: Invalid B{C{rad}}, B{C{earth}} or B{C{lat}}.

764

765 @see: Function L{degrees2m} and L{m2radians}.

766 '''

767 return _rad2m(Lam(rad=rad, clip=0), earth, lat, **radius)

768

769

770 def radiansPI(deg):

771 '''Convert and wrap degrees to radians M{[-PI..+PI]}.

772

773 @arg deg: Angle (C{degrees}).

774

775 @return: Radians, wrapped (C{radiansPI})

776 '''

777 return wrapPI(radians(deg))

778

779

780 def radiansPI2(deg):

781 '''Convert and wrap degrees to radians M{[0..+2PI)}.

782

783 @arg deg: Angle (C{degrees}).

784

785 @return: Radians, wrapped (C{radiansPI2})

786 '''

787 return _umod_PI2(radians(deg))

788

789

790 def radiansPI_2(deg):

791 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}.

792

793 @arg deg: Angle (C{degrees}).

794

795 @return: Radians, wrapped (C{radiansPI_2})

796 '''

797 return wrapPI_2(radians(deg))

798

799

800 def _rad2m(rad, earth, lat, **radius):

801 '''(INTERNAL) Helper for C{degrees2m} and C{radians2m}.

802 '''

803 m = _circle4radius(earth, lat, **radius)

804 return Meter(_copysign_0_0(rad) if m < EPS0 else (rad * m))

805

806

807 def _sin0cos2(q, r, sign):

808 '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3} and

809 C{sin} zero I{signed} with B{C{sign}}, like Karney's U{Math.sind and .cosd

810 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}

811 '''

812 q &= 3 # -1: 3, -2: 2, -3: 1, -4: 0 ...

813 if r < PI_2: # Quarter turn

814 a = fabs(r)

815 if a:

816 s = sin(r)

817 if a == PI_4:

818 s, c = _copysign(_SIN_45, s), _COS_45

819 elif a == PI_6:

820 s, c = _copysign(_SIN_30, s), _COS_30

821 else:

822 c = cos(r)

823 else:

824 s, c = _0_0, _1_0

825 else:

826 s, c = _1_0, _0_0

827 t = s, c, -s, -c, s

828 s = t[q] or _copysign_0_0(sign)

829 c = t[q + 1] or _0_0

830 return s, c

831

832

833 def SinCos2(x, unit=Radians):

834 '''Get C{sin} and C{cos} of I{typed} angle.

835

836 @arg x: Angle (L{Degrees}, L{Radians} or scalar C{radians}).

837 @kwarg unit: The C{B{x}} unit (L{Degrees}, L{Radians}).

838

839 @return: 2-Tuple (C{sin(B{x})}, C{cos(B{x})}).

840 '''

841 return sincos2d(x) if unit is Degrees or unit is degrees or isinstanceof(x, Degrees, Degrees_) else (

842# sincos2(x) if unit is Radians or unit is radians or isinstanceof(x, Radians, Radians_) else

843 sincos2(Radians(x))) # assume C{radians}

844

845

846 def sincos2(rad):

847 '''Return the C{sine} and C{cosine} of an angle in C{radians}.

848

849 @arg rad: Angle (C{radians}).

850

851 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}).

852

853 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/

854 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd

855 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

856 python/geographiclib/geomath.py#l155>} and C++ U{sincosd

857 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

858 include/GeographicLib/Math.hpp#l558>}.

859 '''

860 if _isfinite(rad):

861 q, r = divmod(rad, PI_2)

862 t = _sin0cos2(int(q), r, rad)

863 else:

864 t = NAN, NAN

865 return t

866

867

868 def sincos2_(*rads):

869 '''Yield the C{sine} and C{cosine} of angle(s) in C{radians}.

870

871 @arg rads: One or more angles (C{radians}).

872

873 @return: Yield C{sin(B{rad})} and C{cos(B{rad})} for each angle.

874

875 @see: Function L{sincos2}.

876 '''

877 for r in rads:

878 s, c = sincos2(r)

879 yield s

880 yield c

881

882

883 def sincos2d(deg, adeg=_0_0):

884 '''Return the C{sine} and C{cosine} of an angle in C{degrees}.

885

886 @arg deg: Angle (C{degrees}).

887 @kwarg adeg: Optional correction (C{degrees}).

888

889 @return: 2-Tuple (C{sin(B{deg_})}, C{cos(B{deg_})} with C{B{deg_} =

890 B{deg} + B{adeg}}).

891

892 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/

893 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd

894 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

895 python/geographiclib/geomath.py#l155>} and C++ U{sincosd

896 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/

897 include/GeographicLib/Math.hpp#l558>}.

898 '''

899 if _isfinite(deg):

900 q, d = divmod(deg, _90_0)

901 if adeg:

902 d = _MODS.karney._around(d + adeg)

903 t = _sin0cos2(int(q), radians(d), deg)

904 else:

905 t = NAN, NAN

906 return t

907

908

909 def sincos2d_(*degs):

910 '''Yield the C{sine} and C{cosine} of angle(s) in C{degrees}.

911

912 @arg degs: One or more angles (C{degrees}).

913

914 @return: Yield C{sind(B{deg})} and C{cosd(B{deg})} for each angle.

915

916 @see: Function L{sincos2d}.

917 '''

918 for d in degs:

919 s, c = sincos2d(d)

920 yield s

921 yield c

922

923

924 def sincostan3(rad):

925 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{radians}.

926

927 @arg rad: Angle (C{radians}).

928

929 @return: 3-Tuple (C{sin(B{rad})}, C{cos(B{rad})}, C{tan(B{rad})}).

930

931 @see: Function L{sincos2}.

932 '''

933 return _sincostan3(*sincos2(float(rad)))

934

935

936 def _sincostan3(s, c):

937 '''(INTERNAL) Helper for C{sincostan3} and C{sincostan3d}.

938 '''

939 return s, c, _over_1(s, c) # _tanu(s, c, raiser=False)

940

941

942 def sincostan3d(deg):

943 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{degrees}.

944

945 @arg deg: Angle (C{degrees}).

946

947 @return: 3-Tuple (C{sind(B{deg})}, C{cosd(B{deg})}, C{tand(B{deg})}).

948

949 @see: Function L{sincos2d}.

950 '''

951 return _sincostan3(*sincos2d(float(deg)))

952

953

954 def SM2m(sm):

955 '''Convert a length in statute miles to meter (C{m}).

956

957 @arg sm: Length in statute miles (C{scalar}).

958

959 @return: Length in meter (C{float}).

960

961 @raise ValueError: Invalid B{C{sm}}.

962 '''

963 return Meter(Float(sm=sm) * _M.SM)

964

965

966 def tan_2(rad, **semi): # edge=1

967 '''Compute the tangent of half angle.

968

969 @arg rad: Angle (C{radians}).

970 @kwarg semi: Angle or edge name and index

971 for semi-circular error.

972

973 @return: M{tan(rad / 2)} (C{float}).

974

975 @raise ValueError: If B{C{rad}} is semi-circular

976 and B{C{semi}} is given.

977 '''

978 # .formy.excessKarney_, .sphericalTrigonometry.areaOf

979 if semi and isnear0(fabs(rad) - PI):

980 for n, v in semi.items():

981 break

982 n = _SPACE_(n, _radians_) if not isint(v) else \

983 _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)

984 raise _ValueError(n, rad, txt=_semi_circular_)

985

986 return _tan(rad * _0_5) if _isfinite(rad) else NAN

987

988

989 def tan(rad, **raiser_kwds):

990 '''Return the C{tangent} of an angle in C{radians}.

991

992 @arg rad: Angle (C{radians}).

993 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid

994 ValueErrors and optional, additional

995 ValueError keyword argments.

996

997 @return: C{tan(B{rad})}.

998

999 @raise Error: If L{pygeodesy.isnear0}C{(cos(B{rad})}.

1000 '''

1001 try:

1002 return _tanu(*sincos2(rad))

1003 except ZeroDivisionError:

1004 return _nonfinite(tan, rad, **raiser_kwds)

1005

1006

1007 def tan_(*rads, **raiser_kwds):

1008 '''Yield the C{tangent} of angle(s) in C{radians}.

1009

1010 @arg rads: One or more angles (each in C{radians}).

1011

1012 @return: Yield C{tan(B{rad})} for each angle.

1013

1014 @see: Function L{pygeodesy.tan} for futher details.

1015 '''

1016 try:

1017 for r in rads:

1018 yield _tanu(*sincos2(r))

1019 except ZeroDivisionError:

1020 yield _nonfinite(tan_, r, **raiser_kwds)

1021

1022

1023 def tand(deg, **raiser_clamp_kwds):

1024 '''Return the C{tangent} of an angle in C{degrees}.

1025

1026 @arg deg: Angle (C{degrees}).

1027 @kwarg raiser_clamp_kwds: Use C{B{raiser}=False} to avoid

1028 ValueErrors, C{B{clamp}=}L{OVERFLOW} and

1029 optional, additional ValueError keyword

1030 argments.

1031

1032 @return: C{tan(B{deg})}.

1033

1034 @raise Error: If L{pygeodesy.isnear0}C{(cos(B{deg})}.

1035 '''

1036 try:

1037 return _tanu(*sincos2d(deg))

1038 except ZeroDivisionError:

1039 return _nonfinite(tand, deg, **raiser_clamp_kwds)

1040

1041

1042 def tand_(*degs, **raiser_clamp_kwds):

1043 '''Yield the C{tangent} of angle(s) in C{degrees}.

1044

1045 @arg degs: One or more angles (each in C{degrees}).

1046

1047 @return: Yield C{tand(B{deg})} for each angle.

1048

1049 @see: Function L{pygeodesy.tand} for futher details.

1050 '''

1051 try:

1052 for d in degs:

1053 yield _tanu(*sincos2d(d), **raiser_clamp_kwds)

1054 except ZeroDivisionError:

1055 yield _nonfinite(tand_, d, **raiser_clamp_kwds)

1056

1057

1058 def tanPI_2_2(rad):

1059 '''Compute the tangent of half angle, 90 degrees rotated.

1060

1061 @arg rad: Angle (C{radians}).

1062

1063 @return: M{tan((rad + PI/2) / 2)} (C{float}).

1064 '''

1065 return _tan((rad + PI_2) * _0_5) if _isfinite(rad) else (

1066 NAN if isnan(rad) else _copysign(_90_0, rad))

1067

1068

1069 def _tanu(s, c, clamp=OVERFLOW, **unused):

1070 '''(INTERNAL) Helper for functions C{_cotu}, C{sincostan3},

1071 C{sincostan3d}, C{tan}, C{tan_}, C{tand} and C{tand_}.

1072 '''

1073 if s is NAN or isnan(s):

1074 t = NAN

1075 elif isnear0(c):

1076 raise ZeroDivisionError()

1077 else:

1078 t = _over_1(s, c)

1079 if clamp:

1080 t = min(clamp, max(t, -clamp))

1081 return t

1082

1083

1084 def toise2m(toises):

1085 '''Convert a length in French U{toises<https://WikiPedia.org/wiki/Toise>}

1086 to meter.

1087

1088 @arg toises: Length in toises (C{scalar}).

1089

1090 @return: Length in C{meter} (C{float}).

1091

1092 @raise ValueError: Invalid B{C{toises}}.

1093

1094 @see: Function L{fathom2m}.

1095 '''

1096 return Meter(Float(toises=toises) * _M.TOISE)

1097

1098

1099 def truncate(x, ndigits=None):

1100 '''Truncate to the given number of digits.

1101

1102 @arg x: Value to truncate (C{scalar}).

1103 @kwarg ndigits: Number of digits (C{int}),

1104 aka I{precision}.

1105

1106 @return: Truncated B{C{x}} (C{float}).

1107

1108 @see: Python function C{round}.

1109 '''

1110 if isint(ndigits):

1111 p = _10_0**ndigits

1112 x = int(x * p) / p

1113 return x

1114

1115

1116 def unroll180(lon1, lon2, wrap=True):

1117 '''Unroll longitudinal delta and wrap longitude in degrees.

1118

1119 @arg lon1: Start longitude (C{degrees}).

1120 @arg lon2: End longitude (C{degrees}).

1121 @kwarg wrap: If C{True}, wrap and unroll to the M{(-180..+180]}

1122 range (C{bool}).

1123

1124 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees},

1125 C{degrees}).

1126

1127 @see: Capability C{LONG_UNROLL} in U{GeographicLib

1128 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.

1129 '''

1130 d = lon2 - lon1

1131 if wrap:

1132 u = wrap180(d)

1133 if u != d:

1134 return u, (lon1 + u)

1135 return d, lon2

1136

1137

1138 def _unrollon(p1, p2, wrap=False): # unroll180 == .karney._unroll2

1139 '''(INTERNAL) Wrap/normalize, unroll and replace longitude.

1140 '''

1141 lat, lon = p2.lat, p2.lon

1142 if wrap and _Wrap.normal:

1143 lat, lon = _Wrap.latlon(lat, lon)

1144 _, lon = unroll180(p1.lon, lon, wrap=True)

1145 if lat != p2.lat or fabs(lon - p2.lon) > EPS:

1146 p2 = p2.dup(lat=lat, lon=wrap180(lon))

1147 # p2 = p2.copy(); p2.latlon = lat, wrap180(lon)

1148 return p2

1149

1150

1151 def _unrollon3(p1, p2, p3, wrap=False):

1152 '''(INTERNAL) Wrap/normalize, unroll 2 points.

1153 '''

1154 w = wrap

1155 if w: # PYCHOK no cover

1156 w = _Wrap.normal

1157 p2 = _unrollon(p1, p2, wrap=w)

1158 p3 = _unrollon(p1, p3, wrap=w)

1159 p2 = _unrollon(p2, p3)

1160 return p2, p3, w # was wrapped?

1161

1162

1163 def unrollPI(rad1, rad2, wrap=True):

1164 '''Unroll longitudinal delta and wrap longitude in radians.

1165

1166 @arg rad1: Start longitude (C{radians}).

1167 @arg rad2: End longitude (C{radians}).

1168 @kwarg wrap: If C{True}, wrap and unroll to the M{(-PI..+PI]}

1169 range (C{bool}).

1170

1171 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled

1172 (C{radians}, C{radians}).

1173

1174 @see: Capability C{LONG_UNROLL} in U{GeographicLib

1175 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.

1176 '''

1177 r = rad2 - rad1

1178 if wrap:

1179 u = wrapPI(r)

1180 if u != r:

1181 return u, (rad1 + u)

1182 return r, rad2

1183

1184

1185 class _Wrap(object):

1186

1187 _normal = False # default

1188

1189 @property

1190 def normal(self):

1191 '''Get the current L{normal} setting (C{True},

1192 C{False} or C{None}).

1193 '''

1194 return self._normal

1195

1196 @normal.setter # PYCHOK setter!

1197 def normal(self, setting): # PYCHOK no cover

1198 '''Set L{normal} to C{True}, C{False} or C{None}.

1199 '''

1200 m = _MODS.formy

1201 t = {True: (m.normal, m.normal_),

1202 False: (self.wraplatlon, self.wraphilam),

1203 None: (_passargs, _passargs)}.get(setting, ())

1204 if t:

1205 self.latlon, self.philam = t

1206 self._normal = setting

1207

1208 def latlonDMS2(self, lat, lon, **DMS2_kwds): # PYCHOK no cover

1209 if isstr(lat) or isstr(lon):

1210 kwds = _xkwds(DMS2_kwds, clipLon=0, clipLat=0)

1211 lat, lon = _MODS.dms.parseDMS2(lat, lon, **kwds)

1212 return self.latlon(lat, lon)

1213

1214# def normalatlon(self, *latlon):

1215# return _MODS.formy.normal(*latlon)

1216

1217# def normalamphi(self, *philam):

1218# return _MODS.formy.normal_(*philam)

1219

1220 def wraplatlon(self, lat, lon):

1221 return wrap90(lat), wrap180(lon)

1222

1223 latlon = wraplatlon # default

1224

1225 def latlon3(self, lon1, lat2, lon2, wrap):

1226 if wrap:

1227 lat2, lon2 = self.latlon(lat2, lon2)

1228 lon21, lon2 = unroll180(lon1, lon2)

1229 else:

1230 lon21 = lon2 - lon1

1231 return lon21, lat2, lon2

1232

1233 def _latlonop(self, wrap):

1234 if wrap and self._normal is not None:

1235 return self.latlon

1236 else:

1237 return _passargs

1238

1239 def wraphilam(self, phi, lam):

1240 return wrapPI_2(phi), wrapPI(lam)

1241

1242 philam = wraphilam # default

1243

1244 def philam3(self, lam1, phi2, lam2, wrap):

1245 if wrap:

1246 phi2, lam2 = self.philam(phi2, lam2)

1247 lam21, lam2 = unrollPI(lam1, lam2)

1248 else:

1249 lam21 = lam2 - lam1

1250 return lam21, phi2, lam2

1251

1252 def _philamop(self, wrap):

1253 if wrap and self._normal is not None:

1254 return self.philam

1255 else:

1256 return _passargs

1257

1258 def point(self, ll, wrap=True): # in .points._fractional, -.PointsIter.iterate, ...

1259 '''Return C{ll} or a copy, I{normalized} or I{wrap}'d.

1260 '''

1261 if wrap and self._normal is not None:

1262 lat, lon = ll.latlon

1263 if fabs(lon) > _180_0 or fabs(lat) > _90_0:

1264 _n = self.latlon

1265 ll = ll.copy(name=typename(_n))

1266 ll.latlon = _n(lat, lon)

1267 return ll

1268

1269 _Wrap = _Wrap() # PYCHOK singleton

1270

1271

1272# def _wrap(angle, wrap, modulo):

1273# '''(INTERNAL) Angle wrapper M{((wrap-modulo)..+wrap]}.

1274#

1275# @arg angle: Angle (C{degrees}, C{radians} or C{grades}).

1276# @arg wrap: Range (C{degrees}, C{radians} or C{grades}).

1277# @arg modulo: Upper limit (360 C{degrees}, PI2 C{radians} or 400 C{grades}).

1278#

1279# @return: The B{C{angle}}, wrapped (C{degrees}, C{radians} or C{grades}).

1280# '''

1281# a = float(angle)

1282# if not (wrap - modulo) <= a < wrap:

1283# # math.fmod(-1.5, 3.14) == -1.5, but -1.5 % 3.14 == 1.64

1284# # math.fmod(-1.5, 360) == -1.5, but -1.5 % 360 == 358.5

1285# a %= modulo

1286# if a > wrap:

1287# a -= modulo

1288# return a

1289

1290

1291 def wrap90(deg):

1292 '''Wrap degrees to M{[-90..+90]}.

1293

1294 @arg deg: Angle (C{degrees}).

1295

1296 @return: Degrees, wrapped (C{degrees90}).

1297 '''

1298 return _wrapu(wrap180(deg), _180_0, _90_0)

1299

1300

1301 def wrap180(deg):

1302 '''Wrap degrees to M{[-180..+180]}.

1303

1304 @arg deg: Angle (C{degrees}).

1305

1306 @return: Degrees, wrapped (C{degrees180}).

1307 '''

1308 d = float(deg)

1309 w = _umod_360(d)

1310 if w > _180_0:

1311 w -= _360_0

1312 elif d < 0 and w == _180_0:

1313 w = -w

1314 return w

1315

1316

1317 def wrap360(deg): # see .streprs._umod_360

1318 '''Wrap degrees to M{[0..+360)}.

1319

1320 @arg deg: Angle (C{degrees}).

1321

1322 @return: Degrees, wrapped (C{degrees360}).

1323 '''

1324 return _umod_360(float(deg))

1325

1326

1327 def wrapPI(rad):

1328 '''Wrap radians to M{[-PI..+PI]}.

1329

1330 @arg rad: Angle (C{radians}).

1331

1332 @return: Radians, wrapped (C{radiansPI}).

1333 '''

1334 r = float(rad)

1335 w = _umod_PI2(r)

1336 if w > PI:

1337 w -= PI2

1338 elif r < 0 and w == PI:

1339 w = -PI

1340 return w

1341

1342

1343 def wrapPI2(rad):

1344 '''Wrap radians to M{[0..+2PI)}.

1345

1346 @arg rad: Angle (C{radians}).

1347

1348 @return: Radians, wrapped (C{radiansPI2}).

1349 '''

1350 return _umod_PI2(float(rad))

1351

1352

1353 def wrapPI_2(rad):

1354 '''Wrap radians to M{[-PI/2..+PI/2]}.

1355

1356 @arg rad: Angle (C{radians}).

1357

1358 @return: Radians, wrapped (C{radiansPI_2}).

1359 '''

1360 return _wrapu(wrapPI(rad), PI, PI_2)

1361

1362

1363# def wraplatlon(lat, lon):

1364# '''Both C{wrap90(B{lat})} and C{wrap180(B{lon})}.

1365# '''

1366# return wrap90(lat), wrap180(lon)

1367

1368

1369 def wrap_normal(*normal):

1370 '''Define the operation for the keyword argument C{B{wrap}=True},

1371 across L{pygeodesy}: I{wrap}, I{normalize} or I{no-op}. For

1372 backward compatibility, the default is I{wrap}.

1373

1374 @arg normal: If C{True}, I{normalize} lat- and longitude using

1375 L{normal} or L{normal_}, if C{False}, I{wrap} the

1376 lat- and longitude individually by L{wrap90} or

1377 L{wrapPI_2} respectively L{wrap180}, L{wrapPI} or

1378 if C{None}, leave lat- and longitude I{unchanged}.

1379 To get the current setting, do not specify.

1380

1381 @return: The previous L{wrap_normal} setting (C{bool} or C{None}).

1382 '''

1383 t = _Wrap.normal

1384 if normal:

1385 _Wrap.normal = normal[0]

1386 return t

1387

1388

1389# def wraphilam(phi, lam,):

1390# '''Both C{wrapPI_2(B{phi})} and C{wrapPI(B{lam})}.

1391# '''

1392# return wrapPI_2(phi), wrapPI(lam)

1393

1394

1395 def _wrapu(w, H, Q):

1396 '''(INTERNAL) Helper for functions C{wrap180} and C{wrapPI}.

1397 '''

1398 return (w - H) if w > Q else ((w + H) if w < (-Q) else w)

1399

1400

1401 def yard2m(yards, imperial=False):

1402 '''Convert a length in I{International} or U{Imperial Standard

1403 <https://WikiPedia.org/wiki/Imperial_units>} yards to meter.

1404

1405 @arg yards: Length in yards (C{scalar}).

1406 @kwarg imperial: If C{True}, convert from I{Imperial Standard} yards.

1407

1408 @return: Length in C{meter} (C{float}).

1409

1410 @raise ValueError: Invalid B{C{yards}}.

1411 '''

1412 return Meter(Float(yards=yards) * (_M.YARD_UK if imperial else _M.YARD))

1413

1414# **) MIT License

1415#

1416# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

1417#

1418# Permission is hereby granted, free of charge, to any person obtaining a

1419# copy of this software and associated documentation files (the "Software"),

1420# to deal in the Software without restriction, including without limitation

1421# the rights to use, copy, modify, merge, publish, distribute, sublicense,

1422# and/or sell copies of the Software, and to permit persons to whom the

1423# Software is furnished to do so, subject to the following conditions:

1424#

1425# The above copyright notice and this permission notice shall be included

1426# in all copies or substantial portions of the Software.

1427#

1428# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

1429# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

1430# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

1431# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

1432# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

1433# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

1434# OTHER DEALINGS IN THE SOFTWARE.