Coverage for pygeodesy/utily.py: 94%

342 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-25 15:01 -0400

1 

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

3 

4u'''Various utility functions. 

5 

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

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

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

9and published under the same MIT Licence**. 

10''' 

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

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

13 

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

15from 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 

21from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get 

22from pygeodesy.internals import _Enum, _passargs, typename 

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

24from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS 

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

26 Meter, Meter2, Radians # Radians_ 

27 

28from 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 

60def _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 

66def 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 

72def 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 

84def 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 

96def 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 

109def 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 

115def 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 

122def 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 

131def _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 

140atan2 = _atan2 

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

142''' 

143 

144 

145def 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 

156def 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 

167def 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 

178def _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 

184def 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 

196def 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 

216def _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 

223def 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 

241def 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 

257def 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 

275def 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 

291def _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 

297def 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 

307def 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 

317def 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 

327def 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 

337def 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 

362def 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 

377def 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 

398def 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 

410def 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 

423def 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 

433def 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 

443def 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 

453def 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 

463def 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 

475def 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 

487def 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 

497def 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 

509def _loneg(lon): 

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

511 ''' 

512 return _180_0 - lon 

513 

514 

515def 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 

527def 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 

539def 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 

563def 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 

578def 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 

601def 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 

613def 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 

625def 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 

637def 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 

649def _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 

657def 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 

681def 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 

693def 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 

708def 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 

723def 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 

735def _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 

745def 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 

770def 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 

780def 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 

790def 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 

800def _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 

807def _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 

833def 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 

846def 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 

868def 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 

883def 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 

909def 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 

924def 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 

936def _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 

942def 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 

954def 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 

966def 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 

989def 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 

1007def 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 

1023def 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 

1042def 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 

1058def 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 

1069def _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 

1084def 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 

1099def 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 

1116def 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 

1138def _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 

1151def _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 

1163def 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 

1185class _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 

1291def 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 

1301def 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 

1317def 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 

1327def 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 

1343def 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 

1353def 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 

1369def 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 

1395def _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 

1401def 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.