Coverage for pygeodesy/utily.py: 94%
342 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-25 15:01 -0400
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-25 15:01 -0400
2# -*- coding: utf-8 -*-
4u'''Various utility functions.
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 ;
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_
28from math import acos, asin, asinh, atan2 as _atan2, cos, degrees, fabs, \
29 radians, sin, sinh, tan as _tan # pow
31__all__ = _ALL_LAZY.utily
32__version__ = '26.03.20'
34_SIN_30 = _0_5
35_SIN_45 = _COS_45
37_G = _Enum( # grades per ...
38 DEG = _float( 400.0 / _360_0), # degree
39 RAD = _float( 400.0 / PI2)) # radian
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
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)
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)
72def acre2ha(acres):
73 '''Convert an area in I{acres} to I{hectares}.
75 @arg acres: Area in acres (C{scalar}).
77 @return: Area in hectares (C{float}).
79 @raise ValueError: Invalid B{C{acres}}.
80 '''
81 return m2ha(acre2m2(acres))
84def acre2m2(acres):
85 '''Convert an area in I{acres} to I{square} meter.
87 @arg acres: Area in acres (C{scalar}).
89 @return: Area in C{square} meter (C{float}).
91 @raise ValueError: Invalid B{C{acres}}.
92 '''
93 return Meter2(Float(acres=acres) * _M.ACRE)
96def agdf(phi):
97 '''Inverse U{Gudermannian function
98 <https://WikiPedia.org/wiki/Gudermannian_function>}.
100 @arg phi: Angle (C{radians}).
102 @return: Gudermannian (psi, C{float}).
104 @see: Function L{gdf}.
105 '''
106 return asinh(tan(phi))
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)
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)
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}.
126 @see: Function L{pygeodesy.atan2d}.
127 '''
128 return _atan1u(y, x, atan2d)
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)
140atan2 = _atan2
141'''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.
142'''
145def atan2b(y, x):
146 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360), counter-clockwise}.
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
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).
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
167def atan2p(y, x): # in .fmath.polar2
168 '''Return C{atan2(B{y}, B{x})} in radians M{[0..+PI2), counter-clockwise}.
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
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)
184def chain2m(chains):
185 '''Convert a length in I{UK} chains to meter.
187 @arg chains: Length in chains (C{scalar}).
189 @return: Length in C{meter} (C{float}).
191 @raise ValueError: Invalid B{C{chains}}.
192 '''
193 return Meter(Float(chains=chains) * _M.CHAIN)
196def circle4(earth, lat):
197 '''Get the equatorial or a parallel I{circle of latitude}.
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}).
203 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}.
205 @raise RangeError: Latitude B{C{lat}} outside valid range and
206 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
208 @raise TypeError: Invalid B{C{earth}}.
210 @raise ValueError: invalid B{C{earth}} or B{C{lat}}.
211 '''
212 E = _MODS.datums._earth_ellipsoid(earth)
213 return E.circle4(lat)
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
223def cot(rad, **raiser_kwds):
224 '''Return the C{cotangent} of an angle in C{radians}.
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.
231 @return: C{cot(B{rad})}.
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)
241def cot_(*rads, **raiser_kwds):
242 '''Yield the C{cotangent} of angle(s) in C{radians}.
244 @arg rads: One or more angles (each in C{radians}).
246 @return: Yield C{cot(B{rad})} for each angle.
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)
257def cotd(deg, **raiser_kwds):
258 '''Return the C{cotangent} of an angle in C{degrees}.
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.
265 @return: C{cot(B{deg})}.
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)
275def cotd_(*degs, **raiser_kwds):
276 '''Yield the C{cotangent} of angle(s) in C{degrees}.
278 @arg degs: One or more angles (each in C{degrees}).
280 @return: Yield C{cotd(B{deg})} for each angle.
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)
291def _cotu(s, c):
292 '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.
293 '''
294 return _tanu(c, s)
297def degrees90(rad):
298 '''Convert radians to degrees and wrap M{[-90..+90)}.
300 @arg rad: Angle (C{radians}).
302 @return: Angle, wrapped (C{degrees90}).
303 '''
304 return wrap90(degrees(rad))
307def degrees180(rad):
308 '''Convert radians to degrees and wrap M{[-180..+180)}.
310 @arg rad: Angle (C{radians}).
312 @return: Angle, wrapped (C{degrees180}).
313 '''
314 return wrap180(degrees(rad))
317def degrees360(rad):
318 '''Convert radians to degrees and wrap M{[0..+360)}.
320 @arg rad: Angle (C{radians}).
322 @return: Angle, wrapped (C{degrees360}).
323 '''
324 return _umod_360(degrees(rad))
327def degrees2grades(deg):
328 '''Convert degrees to I{grades} (aka I{gons} or I{gradians}).
330 @arg deg: Angle (C{degrees}).
332 @return: Angle (C{grades}).
333 '''
334 return Float(grades=Degrees(deg) * _G.DEG)
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.
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}}.
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}}.
350 @raise RangeError: Latitude B{C{lat}} outside valid range, only if
351 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
353 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
355 @raise ValueError: Invalid B{C{deg}}, B{C{earth}} or B{C{lat}}.
357 @see: Function L{radians2m} and L{m2degrees}.
358 '''
359 return _rad2m(Lamd(deg=deg, clip=0), earth, lat, **radius)
362def fathom2m(fathoms):
363 '''Convert a length in I{Imperial} fathoms to meter.
365 @arg fathoms: Length in fathoms (C{scalar}).
367 @return: Length in C{meter} (C{float}).
369 @raise ValueError: Invalid B{C{fathoms}}.
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)
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.
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}.
388 @return: Length in C{meter} (C{float}).
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)))))
398def furlong2m(furlongs):
399 '''Convert a length in I{furlongs} to meter.
401 @arg furlongs: Length in furlongs (C{scalar}).
403 @return: Length in C{meter} (C{float}).
405 @raise ValueError: Invalid B{C{furlongs}}.
406 '''
407 return Meter(Float(furlongs=furlongs) * _M.FURLONG)
410def gdf(psi):
411 '''U{Gudermannian function
412 <https://WikiPedia.org/wiki/Gudermannian_function>}.
414 @arg psi: Gudermannian (C{float}).
416 @return: Angle (C{radians}).
418 @see: Function L{agdf}.
419 '''
420 return atan1(sinh(psi))
423def grades(rad):
424 '''Convert radians to I{grades} (aka I{gons} or I{gradians}).
426 @arg rad: Angle (C{radians}).
428 @return: Angle (C{grades}).
429 '''
430 return Float(grades=Radians(rad) * _G.RAD)
433def grades400(rad):
434 '''Convert radians to I{grades} (aka I{gons} or I{gradians}) and wrap M{[0..+400)}.
436 @arg rad: Angle (C{radians}).
438 @return: Angle, wrapped (C{grades}).
439 '''
440 return Float(grades400=wrapPI2(rad) * _G.RAD)
443def grades2degrees(gon):
444 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{degrees}.
446 @arg gon: Angle (C{grades}).
448 @return: Angle (C{degrees}).
449 '''
450 return Degrees(Float(gon=gon) / _G.DEG)
453def grades2radians(gon):
454 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{radians}.
456 @arg gon: Angle (C{grades}).
458 @return: Angle (C{radians}).
459 '''
460 return Radians(Float(gon=gon) / _G.RAD)
463def ha2acre(ha):
464 '''Convert an area in I{hectares} to I{acres}.
466 @arg ha: Area in hectares (C{scalar}).
468 @return: Area in C{acres} (C{float}).
470 @raise ValueError: Invalid B{C{ha}}.
471 '''
472 return m2acre(ha2m2(ha))
475def ha2m2(ha):
476 '''Convert an area in I{hectares} to I{square} meter.
478 @arg ha: Area in hectares (C{scalar}).
480 @return: Area in C{square} meter (C{float}).
482 @raise ValueError: Invalid B{C{ha}}.
483 '''
484 return Meter2(Float(ha=ha) * _M.HA)
487def hav(rad):
488 '''Return the U{haversine<https://WikiPedia.org/wiki/Haversine_formula>} of an angle.
490 @arg rad: Angle (C{radians}).
492 @return: C{sin(B{rad} / 2)**2}.
493 '''
494 return sin(rad * _0_5)**2
497def km2m(km):
498 '''Convert a length in I{kilo meter} to meter (C{m}).
500 @arg km: Length in kilo meter (C{scalar}).
502 @return: Length in meter (C{float}).
504 @raise ValueError: Invalid B{C{km}}.
505 '''
506 return Meter(Float(km=km) * _M.KM)
509def _loneg(lon):
510 '''(INTERNAL) "Complement" of C{lon}.
511 '''
512 return _180_0 - lon
515def m2acre(meter2):
516 '''Convert an area in I{square} meter to I{acres}.
518 @arg meter2: Area in C{square} meter (C{scalar}).
520 @return: Area in acres (C{float}).
522 @raise ValueError: Invalid B{C{meter2}}.
523 '''
524 return Float(acre=Meter2(meter2) / _M.ACRE)
527def m2chain(meter):
528 '''Convert a length in meter to I{UK} chains.
530 @arg meter: Length in meter (C{scalar}).
532 @return: Length in C{chains} (C{float}).
534 @raise ValueError: Invalid B{C{meter}}.
535 '''
536 return Float(chain=Meter(meter) / _M.CHAIN) # * 0.049_709_695_378_986_715
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.
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}}.
549 @return: Angle (C{degrees}) or C{0.0,} for near-polar B{C{lat}}.
551 @raise RangeError: Latitude B{C{lat}} outside valid range, only if
552 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
554 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
556 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.
558 @see: Function L{m2radians} and L{degrees2m}.
559 '''
560 return Degrees(degrees(_m2rad(distance, earth, lat, **radius)))
563def m2fathom(meter):
564 '''Convert a length in meter to I{Imperial} fathoms.
566 @arg meter: Length in meter (C{scalar}).
568 @return: Length in C{fathoms} (C{float}).
570 @raise ValueError: Invalid B{C{meter}}.
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
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}).
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.
589 @return: Length in C{feet} (C{float}).
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)))))
601def m2furlong(meter):
602 '''Convert a length in meter to furlongs.
604 @arg meter: Length in meter (C{scalar}).
606 @return: Length in C{furlongs} (C{float}).
608 @raise ValueError: Invalid B{C{meter}}.
609 '''
610 return Float(furlong=Meter(meter) / _M.FURLONG) # * 0.004_970_969_54
613def m2ha(meter2):
614 '''Convert an area in I{square} meter to I{hectares}.
616 @arg meter2: Area in C{square} meter (C{scalar}).
618 @return: Area in hectares (C{float}).
620 @raise ValueError: Invalid B{C{meter2}}.
621 '''
622 return Float(ha=Meter2(meter2) / _M.HA)
625def m2km(meter):
626 '''Convert a length in meter to kilo meter (C{Km}).
628 @arg meter: Length in meter (C{scalar}).
630 @return: Length in C{Km} (C{float}).
632 @raise ValueError: Invalid B{C{meter}}.
633 '''
634 return Float(km=Meter(meter) / _M.KM)
637def m2NM(meter):
638 '''Convert a length in meter to nautical miles (C{NM}).
640 @arg meter: Length in meter (C{scalar}).
642 @return: Length in C{NM} (C{float}).
644 @raise ValueError: Invalid B{C{meter}}.
645 '''
646 return Float(NM=Meter(meter) / _M.NM) # * 5.399_568_04e-4
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
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.
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}}.
667 @return: Angle (C{radians}) or C{0.0} for near-polar B{C{lat}}.
669 @raise RangeError: Latitude B{C{lat}} outside valid range, only if
670 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
672 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
674 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.
676 @see: Function L{m2degrees} and L{radians2m}.
677 '''
678 return Radians(_m2rad(distance, earth, lat, **radius))
681def m2SM(meter):
682 '''Convert a length in meter to statute miles (SM).
684 @arg meter: Length in meter (C{scalar}).
686 @return: Length in C{SM} (C{float}).
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
693def m2toise(meter):
694 '''Convert a length in meter to French U{toises
695 <https://WikiPedia.org/wiki/Toise>}.
697 @arg meter: Length in meter (C{scalar}).
699 @return: Length in C{toises} (C{float}).
701 @raise ValueError: Invalid B{C{meter}}.
703 @see: Function L{m2fathom}.
704 '''
705 return Float(toise=Meter(meter) / _M.TOISE) # * 0.513_083_632_632_119
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.
712 @arg meter: Length in meter (C{scalar}).
713 @kwarg imperial: If C{True}, convert to I{Imperial Standard} yards.
715 @return: Length in C{yards} (C{float}).
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
723def NM2m(nm):
724 '''Convert a length in nautical miles to meter (C{m}).
726 @arg nm: Length in nautical miles (C{scalar}).
728 @return: Length in meter (C{float}).
730 @raise ValueError: Invalid B{C{nm}}.
731 '''
732 return Meter(Float(nm=nm) * _M.NM)
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)
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.
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}}.
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}}.
758 @raise RangeError: Latitude B{C{lat}} outside valid range, only if
759 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
761 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
763 @raise ValueError: Invalid B{C{rad}}, B{C{earth}} or B{C{lat}}.
765 @see: Function L{degrees2m} and L{m2radians}.
766 '''
767 return _rad2m(Lam(rad=rad, clip=0), earth, lat, **radius)
770def radiansPI(deg):
771 '''Convert and wrap degrees to radians M{[-PI..+PI]}.
773 @arg deg: Angle (C{degrees}).
775 @return: Radians, wrapped (C{radiansPI})
776 '''
777 return wrapPI(radians(deg))
780def radiansPI2(deg):
781 '''Convert and wrap degrees to radians M{[0..+2PI)}.
783 @arg deg: Angle (C{degrees}).
785 @return: Radians, wrapped (C{radiansPI2})
786 '''
787 return _umod_PI2(radians(deg))
790def radiansPI_2(deg):
791 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}.
793 @arg deg: Angle (C{degrees}).
795 @return: Radians, wrapped (C{radiansPI_2})
796 '''
797 return wrapPI_2(radians(deg))
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))
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
833def SinCos2(x, unit=Radians):
834 '''Get C{sin} and C{cos} of I{typed} angle.
836 @arg x: Angle (L{Degrees}, L{Radians} or scalar C{radians}).
837 @kwarg unit: The C{B{x}} unit (L{Degrees}, L{Radians}).
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}
846def sincos2(rad):
847 '''Return the C{sine} and C{cosine} of an angle in C{radians}.
849 @arg rad: Angle (C{radians}).
851 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}).
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
868def sincos2_(*rads):
869 '''Yield the C{sine} and C{cosine} of angle(s) in C{radians}.
871 @arg rads: One or more angles (C{radians}).
873 @return: Yield C{sin(B{rad})} and C{cos(B{rad})} for each angle.
875 @see: Function L{sincos2}.
876 '''
877 for r in rads:
878 s, c = sincos2(r)
879 yield s
880 yield c
883def sincos2d(deg, adeg=_0_0):
884 '''Return the C{sine} and C{cosine} of an angle in C{degrees}.
886 @arg deg: Angle (C{degrees}).
887 @kwarg adeg: Optional correction (C{degrees}).
889 @return: 2-Tuple (C{sin(B{deg_})}, C{cos(B{deg_})} with C{B{deg_} =
890 B{deg} + B{adeg}}).
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
909def sincos2d_(*degs):
910 '''Yield the C{sine} and C{cosine} of angle(s) in C{degrees}.
912 @arg degs: One or more angles (C{degrees}).
914 @return: Yield C{sind(B{deg})} and C{cosd(B{deg})} for each angle.
916 @see: Function L{sincos2d}.
917 '''
918 for d in degs:
919 s, c = sincos2d(d)
920 yield s
921 yield c
924def sincostan3(rad):
925 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{radians}.
927 @arg rad: Angle (C{radians}).
929 @return: 3-Tuple (C{sin(B{rad})}, C{cos(B{rad})}, C{tan(B{rad})}).
931 @see: Function L{sincos2}.
932 '''
933 return _sincostan3(*sincos2(float(rad)))
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)
942def sincostan3d(deg):
943 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{degrees}.
945 @arg deg: Angle (C{degrees}).
947 @return: 3-Tuple (C{sind(B{deg})}, C{cosd(B{deg})}, C{tand(B{deg})}).
949 @see: Function L{sincos2d}.
950 '''
951 return _sincostan3(*sincos2d(float(deg)))
954def SM2m(sm):
955 '''Convert a length in statute miles to meter (C{m}).
957 @arg sm: Length in statute miles (C{scalar}).
959 @return: Length in meter (C{float}).
961 @raise ValueError: Invalid B{C{sm}}.
962 '''
963 return Meter(Float(sm=sm) * _M.SM)
966def tan_2(rad, **semi): # edge=1
967 '''Compute the tangent of half angle.
969 @arg rad: Angle (C{radians}).
970 @kwarg semi: Angle or edge name and index
971 for semi-circular error.
973 @return: M{tan(rad / 2)} (C{float}).
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_)
986 return _tan(rad * _0_5) if _isfinite(rad) else NAN
989def tan(rad, **raiser_kwds):
990 '''Return the C{tangent} of an angle in C{radians}.
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.
997 @return: C{tan(B{rad})}.
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)
1007def tan_(*rads, **raiser_kwds):
1008 '''Yield the C{tangent} of angle(s) in C{radians}.
1010 @arg rads: One or more angles (each in C{radians}).
1012 @return: Yield C{tan(B{rad})} for each angle.
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)
1023def tand(deg, **raiser_clamp_kwds):
1024 '''Return the C{tangent} of an angle in C{degrees}.
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.
1032 @return: C{tan(B{deg})}.
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)
1042def tand_(*degs, **raiser_clamp_kwds):
1043 '''Yield the C{tangent} of angle(s) in C{degrees}.
1045 @arg degs: One or more angles (each in C{degrees}).
1047 @return: Yield C{tand(B{deg})} for each angle.
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)
1058def tanPI_2_2(rad):
1059 '''Compute the tangent of half angle, 90 degrees rotated.
1061 @arg rad: Angle (C{radians}).
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))
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
1084def toise2m(toises):
1085 '''Convert a length in French U{toises<https://WikiPedia.org/wiki/Toise>}
1086 to meter.
1088 @arg toises: Length in toises (C{scalar}).
1090 @return: Length in C{meter} (C{float}).
1092 @raise ValueError: Invalid B{C{toises}}.
1094 @see: Function L{fathom2m}.
1095 '''
1096 return Meter(Float(toises=toises) * _M.TOISE)
1099def truncate(x, ndigits=None):
1100 '''Truncate to the given number of digits.
1102 @arg x: Value to truncate (C{scalar}).
1103 @kwarg ndigits: Number of digits (C{int}),
1104 aka I{precision}.
1106 @return: Truncated B{C{x}} (C{float}).
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
1116def unroll180(lon1, lon2, wrap=True):
1117 '''Unroll longitudinal delta and wrap longitude in degrees.
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}).
1124 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees},
1125 C{degrees}).
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
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
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?
1163def unrollPI(rad1, rad2, wrap=True):
1164 '''Unroll longitudinal delta and wrap longitude in radians.
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}).
1171 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled
1172 (C{radians}, C{radians}).
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
1185class _Wrap(object):
1187 _normal = False # default
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
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
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)
1214# def normalatlon(self, *latlon):
1215# return _MODS.formy.normal(*latlon)
1217# def normalamphi(self, *philam):
1218# return _MODS.formy.normal_(*philam)
1220 def wraplatlon(self, lat, lon):
1221 return wrap90(lat), wrap180(lon)
1223 latlon = wraplatlon # default
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
1233 def _latlonop(self, wrap):
1234 if wrap and self._normal is not None:
1235 return self.latlon
1236 else:
1237 return _passargs
1239 def wraphilam(self, phi, lam):
1240 return wrapPI_2(phi), wrapPI(lam)
1242 philam = wraphilam # default
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
1252 def _philamop(self, wrap):
1253 if wrap and self._normal is not None:
1254 return self.philam
1255 else:
1256 return _passargs
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
1269_Wrap = _Wrap() # PYCHOK singleton
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
1291def wrap90(deg):
1292 '''Wrap degrees to M{[-90..+90]}.
1294 @arg deg: Angle (C{degrees}).
1296 @return: Degrees, wrapped (C{degrees90}).
1297 '''
1298 return _wrapu(wrap180(deg), _180_0, _90_0)
1301def wrap180(deg):
1302 '''Wrap degrees to M{[-180..+180]}.
1304 @arg deg: Angle (C{degrees}).
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
1317def wrap360(deg): # see .streprs._umod_360
1318 '''Wrap degrees to M{[0..+360)}.
1320 @arg deg: Angle (C{degrees}).
1322 @return: Degrees, wrapped (C{degrees360}).
1323 '''
1324 return _umod_360(float(deg))
1327def wrapPI(rad):
1328 '''Wrap radians to M{[-PI..+PI]}.
1330 @arg rad: Angle (C{radians}).
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
1343def wrapPI2(rad):
1344 '''Wrap radians to M{[0..+2PI)}.
1346 @arg rad: Angle (C{radians}).
1348 @return: Radians, wrapped (C{radiansPI2}).
1349 '''
1350 return _umod_PI2(float(rad))
1353def wrapPI_2(rad):
1354 '''Wrap radians to M{[-PI/2..+PI/2]}.
1356 @arg rad: Angle (C{radians}).
1358 @return: Radians, wrapped (C{radiansPI_2}).
1359 '''
1360 return _wrapu(wrapPI(rad), PI, PI_2)
1363# def wraplatlon(lat, lon):
1364# '''Both C{wrap90(B{lat})} and C{wrap180(B{lon})}.
1365# '''
1366# return wrap90(lat), wrap180(lon)
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}.
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.
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
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)
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)
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.
1405 @arg yards: Length in yards (C{scalar}).
1406 @kwarg imperial: If C{True}, convert from I{Imperial Standard} yards.
1408 @return: Length in C{meter} (C{float}).
1410 @raise ValueError: Invalid B{C{yards}}.
1411 '''
1412 return Meter(Float(yards=yards) * (_M.YARD_UK if imperial else _M.YARD))
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.