Coverage for pygeodesy/utily.py: 96%
245 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-31 10:52 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-31 10:52 -0400
2# -*- coding: utf-8 -*-
4u'''Various utility functions.
6After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**, see
7U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>} and
8U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
9'''
10# make sure int/int division yields float quotient, see .basics
11from __future__ import division as _; del _ # PYCHOK semicolon
13from pygeodesy.basics import copysign0, isint
14from pygeodesy.constants import EPS, EPS0, INF, NAN, NEG0, NINF, PI, PI2, PI_2, R_M, \
15 _float as _F, _isfinite, isnan, isnear0, isneg0, _M_KM, \
16 _M_NM, _M_SM, _0_0, _1__90, _0_5, _1_0, _N_1_0, _2__PI, \
17 _10_0, _90_0, _N_90_0, _180_0, _N_180_0, _360_0, _400_0
18from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_
19from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
20from pygeodesy.units import Degrees, Feet, Float, Lam, Lam_, Meter, Meter2, Radians
22from math import acos, asin, atan2, cos, degrees, fabs, radians, sin, tan # pow
24__all__ = _ALL_LAZY.utily
25__version__ = '22.10.23'
27# read constant name "_M_UNIT" as "meter per unit"
28_M_CHAIN = _F( 20.1168) # yard2m(1) * 22
29_M_FATHOM = _F( 1.8288) # yard2m(1) * 2 or _M_NM * 1e-3
30_M_FOOT_GE = _F( 0.31608) # German Fuss (1 / 3.1637560111364)
31_M_FOOT_FR = _F( 0.3248406) # French Pied-du-Roi or pied (1 / 3.0784329298739)
32_M_FOOT_INTL = _F( 0.3048) # Int'l (1 / 3.2808398950131 = 10_000 / (254 * 12))
33_M_FOOT_USRV = _F( 0.3048006096012192) # US Survey (1200 / 3937)
34_M_FURLONG = _F( 201.168) # 220 * yard2m(1) == 10 * m2chain(1)
35# _M_KM = _F(1000.0) # kilo meter
36# _M_NM = _F(1852.0) # nautical mile
37# _M_SM = _F(1609.344) # statute mile
38_M_TOISE = _F( 1.9490436) # French toise, 6 pieds (6 / 3.0784329298739)
39_M_YARD_UK = _F( 0.9144) # 254 * 12 * 3 / 10_000 == 3 * ft2m(1) Int'l
42def acos1(x):
43 '''Return C{math.acos(max(-1, min(1, B{x})))}.
44 '''
45 return acos(x) if fabs(x) < _1_0 else (PI if x < 0 else _0_0)
48def acre2ha(acres):
49 '''Convert acres to hectare.
51 @arg acres: Value in acres (C{scalar}).
53 @return: Value in C{hectare} (C{float}).
55 @raise ValueError: Invalid B{C{acres}}.
56 '''
57 # 0.40468564224 == acre2m2(1) / 10_000
58 return Float(ha=Float(acres) * 0.40468564224)
61def acre2m2(acres):
62 '''Convert acres to I{square} meter.
64 @arg acres: Value in acres (C{scalar}).
66 @return: Value in C{meter^2} (C{float}).
68 @raise ValueError: Invalid B{C{acres}}.
69 '''
70 # 4046.8564224 == chain2m(1) * furlong2m(1)
71 return Meter2(Float(acres) * 4046.8564224)
74def asin1(x):
75 '''Return C{math.asin(max(-1, min(1, B{x})))}.
76 '''
77 return asin(x) if fabs(x) < _1_0 else (-PI_2 if x < 0 else PI_2) # -PI_2, not PI3_2!
80def atand(y_x):
81 '''Return C{atan(B{y_x})} angle in C{degrees}.
83 @see: Function L{pygeodesy.atan2d}.
84 '''
85 return atan2d(y_x, _1_0)
88def atan2b(y, x):
89 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360]}.
91 @see: Function L{pygeodesy.atan2d}.
92 '''
93 d = atan2d(y, x)
94 if d < 0:
95 d += _360_0
96 return d
99def atan2d(y, x, reverse=False):
100 '''Return C{atan2(B{y}, B{x})} in degrees M{[-180..+180]},
101 optionally reversed (by 180 degrees for C{azi2}).
103 @see: I{Karney}'s C++ function U{Math.atan2d
104 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
105 '''
106 if fabs(y) > fabs(x) > 0:
107 if y < 0: # q = 3
108 d = degrees(atan2(x, -y)) - _90_0
109 else: # q = 2
110 d = _90_0 - degrees(atan2(x, y))
111 elif x < 0: # q = 1
112 d = copysign0(_180_0, y) - degrees(atan2(y, -x))
113 elif x > 0: # q = 0
114 d = degrees(atan2(y, x)) if y else _0_0
115 elif isnan(x) or isnan(y):
116 return NAN
117 else: # x == 0
118 d = _N_90_0 if y < 0 else (_90_0 if y > 0 else _0_0)
119 if reverse:
120 d += _180_0 if d < 0 else _N_180_0
121 return d
124def chain2m(chains):
125 '''Convert I{UK} chains to meter.
127 @arg chains: Value in chains (C{scalar}).
129 @return: Value in C{meter} (C{float}).
131 @raise ValueError: Invalid B{C{chains}}.
132 '''
133 return Meter(Float(chains=chains) * _M_CHAIN)
136def circle4(earth, lat):
137 '''Get the equatorial or a parallel I{circle of latitude}.
139 @arg earth: The earth radius, ellipsoid or datum
140 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
141 L{Datum} or L{a_f2Tuple}).
142 @arg lat: Geodetic latitude (C{degrees90}, C{str}).
144 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}
145 instance.
147 @raise RangeError: Latitude B{C{lat}} outside valid range and
148 L{pygeodesy.rangerrors} set to C{True}.
150 @raise TypeError: Invalid B{C{earth}}.
152 @raise ValueError: B{C{earth}} or B{C{lat}}.
153 '''
154 E = _MODS.datums._spherical_datum(earth).ellipsoid
155 return E.circle4(lat)
158def cot(rad, **error_kwds):
159 '''Return the C{cotangent} of an angle in C{radians}.
161 @arg rad: Angle (C{radians}).
162 @kwarg error_kwds: Error to raise (C{ValueError}).
164 @return: C{cot(B{rad})}.
166 @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{rad})}.
167 '''
168 s, c = sincos2(rad)
169 if isnear0(s):
170 raise _valueError(cot, rad, **error_kwds)
171 return c / s
174def cot_(*rads, **error_kwds):
175 '''Return the C{cotangent} of angle(s) in C{radiansresection}.
177 @arg rads: One or more angles (C{radians}).
178 @kwarg error_kwds: Error to raise (C{ValueError}).
180 @return: Yield the C{cot(B{rad})} for each angle.
182 @raise ValueError: See L{pygeodesy.cot}.
183 '''
184 try:
185 for r in rads:
186 yield cot(r)
187 except ValueError:
188 raise _valueError(cot_, r, **error_kwds)
191def cotd(deg, **error_kwds):
192 '''Return the C{cotangent} of an angle in C{degrees}.
194 @arg deg: Angle (C{degrees}).
195 @kwarg error_kwds: Error to raise (C{ValueError}).
197 @return: C{cot(B{deg})}.
199 @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{deg})}.
200 '''
201 s, c = sincos2d(deg)
202 if isnear0(s):
203 raise _valueError(cotd, deg, **error_kwds)
204 return c / s
207def cotd_(*degs, **error_kwds):
208 '''Return the C{cotangent} of angle(s) in C{degrees}.
210 @arg degs: One or more angles (C{degrees}).
211 @kwarg error_kwds: Error to raise (C{ValueError}).
213 @return: Yield the C{cot(B{deg})} for each angle.
215 @raise ValueError: See L{pygeodesy.cotd}.
216 '''
217 try:
218 for d in degs:
219 yield cotd(d)
220 except ValueError:
221 raise _valueError(cotd_, d, **error_kwds)
224def degrees90(rad):
225 '''Convert radians to degrees and wrap M{[-270..+90]}.
227 @arg rad: Angle (C{radians}).
229 @return: Angle, wrapped (C{degrees90}).
230 '''
231 return _wrap(degrees(rad), _90_0, _360_0)
234def degrees180(rad):
235 '''Convert radians to degrees and wrap M{[-180..+180]}.
237 @arg rad: Angle (C{radians}).
239 @return: Angle, wrapped (C{degrees180}).
240 '''
241 return _wrap(degrees(rad), _180_0, _360_0)
244def degrees360(rad):
245 '''Convert radians to degrees and wrap M{[0..+360)}.
247 @arg rad: Angle (C{radians}).
249 @return: Angle, wrapped (C{degrees360}).
250 '''
251 return _wrap(degrees(rad), _360_0, _360_0)
254def degrees2grades(deg):
255 '''Convert degrees to I{grades} (aka I{gons} or I{gradians}).
257 @arg deg: Angle (C{degrees}).
259 @return: Angle (C{grades}).
260 '''
261 return Degrees(deg) * _400_0 / _360_0
264def degrees2m(deg, radius=R_M, lat=0):
265 '''Convert an angle to a distance along the equator or
266 along the parallel at an other (geodetic) latitude.
268 @arg deg: The angle (C{degrees}).
269 @kwarg radius: Mean earth radius, ellipsoid or datum
270 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
271 L{Datum} or L{a_f2Tuple}).
272 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
274 @return: Distance (C{meter}, same units as B{C{radius}}
275 or equatorial and polar radii) or C{0.0} for
276 near-polar B{C{lat}}.
278 @raise RangeError: Latitude B{C{lat}} outside valid range and
279 L{pygeodesy.rangerrors} set to C{True}.
281 @raise TypeError: Invalid B{C{radius}}.
283 @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or
284 B{C{lat}}.
286 @see: Function L{radians2m} and L{m2degrees}.
287 '''
288 return _radians2m(Lam_(deg=deg, clip=0), radius, lat)
291def fathom2m(fathoms):
292 '''Convert I{Imperial} fathom to meter.
294 @arg fathoms: Value in fathoms (C{scalar}).
296 @return: Value in C{meter} (C{float}).
298 @raise ValueError: Invalid B{C{fathoms}}.
300 @see: Function L{toise2m}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
301 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
302 '''
303 return Meter(Float(fathoms=fathoms) * _M_FATHOM)
306def ft2m(feet, usurvey=False, pied=False, fuss=False):
307 '''Convert I{International}, I{US Survey}, I{French} or I{German}
308 B{C{feet}} to C{meter}.
310 @arg feet: Value in feet (C{scalar}).
311 @kwarg usurvey: Convert I{US Survey} foot if C{True} else ...
312 @kwarg pied: Convert French I{pied-du-Roi} if C{True} else ...
313 @kwarg fuss: Convert German I{Fuss} if C{True}, otherwise
314 I{International} feet to C{meter}.
316 @return: Value in C{meter} (C{float}).
318 @raise ValueError: Invalid B{C{feet}}.
319 '''
320 return Meter(Feet(feet) * (_M_FOOT_USRV if usurvey else
321 (_M_FOOT_FR if pied else
322 (_M_FOOT_GE if fuss else _M_FOOT_INTL))))
325def furlong2m(furlongs):
326 '''Convert a furlong to meter.
328 @arg furlongs: Value in furlongs (C{scalar}).
330 @return: Value in C{meter} (C{float}).
332 @raise ValueError: Invalid B{C{furlongs}}.
333 '''
334 return Meter(Float(furlongs=furlongs) * _M_FURLONG)
337def grades(rad):
338 '''Convert radians to I{grades} (aka I{gons} or I{gradians}).
340 @arg rad: Angle (C{radians}).
342 @return: Angle (C{grades}).
343 '''
344 return Float(grades=Float(rad=rad) * _400_0 / PI2)
347def grades400(rad):
348 '''Convert radians to I{grades} (aka I{gons} or I{gradians}) and wrap M{[0..+400)}.
350 @arg rad: Angle (C{radians}).
352 @return: Angle, wrapped (C{grades}).
353 '''
354 return Float(grades400=_wrap(grades(rad), _400_0, _400_0))
357def grades2degrees(gon):
358 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{degrees}.
360 @arg gon: Angle (C{grades}).
362 @return: Angle (C{degrees}).
363 '''
364 return Degrees(Float(gon=gon) * _360_0 / _400_0)
367def grades2radians(gon):
368 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{radians}.
370 @arg gon: Angle (C{grades}).
372 @return: Angle (C{radians}).
373 '''
374 return Radians(Float(gon=gon) * PI2 / _400_0)
377def km2m(km):
378 '''Convert kilo meter to meter (m).
380 @arg km: Value in kilo meter (C{scalar}).
382 @return: Value in meter (C{float}).
384 @raise ValueError: Invalid B{C{km}}.
385 '''
386 return Meter(Float(km=km) * _M_KM)
389def m2chain(meter):
390 '''Convert meter to I{UK} chains.
392 @arg meter: Value in meter (C{scalar}).
394 @return: Value in C{chains} (C{float}).
396 @raise ValueError: Invalid B{C{meter}}.
397 '''
398 return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049709695378986715
401def m2degrees(distance, radius=R_M, lat=0):
402 '''Convert a distance to an angle along the equator or
403 along the parallel at an other (geodetic) latitude.
405 @arg distance: Distance (C{meter}, same units as B{C{radius}}).
406 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
407 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or
408 L{a_f2Tuple}).
409 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
411 @return: Angle (C{degrees}) or C{INF} for near-polar B{C{lat}}.
413 @raise RangeError: Latitude B{C{lat}} outside valid range and
414 L{pygeodesy.rangerrors} set to C{True}.
416 @raise TypeError: Invalid B{C{radius}}.
418 @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
419 or B{C{lat}}.
421 @see: Function L{m2radians} and L{degrees2m}.
422 '''
423 return degrees(m2radians(distance, radius=radius, lat=lat))
426def m2fathom(meter):
427 '''Convert meter to I{Imperial} fathoms.
429 @arg meter: Value in meter (C{scalar}).
431 @return: Value in C{fathoms} (C{float}).
433 @raise ValueError: Invalid B{C{meter}}.
435 @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
436 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
437 '''
438 return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546806649
441def m2ft(meter, usurvey=False, pied=False, fuss=False):
442 '''Convert meter to I{International}, I{US Survey}, I{French} or
443 or I{German} feet (C{ft}).
445 @arg meter: Value in meter (C{scalar}).
446 @kwarg usurvey: Convert to I{US Survey} foot if C{True} else ...
447 @kwarg pied: Convert to French I{pied-du-Roi} if C{True} else ...
448 @kwarg fuss: Convert to German I{Fuss} if C{True}, otherwise to
449 I{International} foot.
451 @return: Value in C{feet} (C{float}).
453 @raise ValueError: Invalid B{C{meter}}.
454 '''
455 # * 3.2808333333333333, US Survey 3937 / 1200
456 # * 3.2808398950131235, Int'l 10_000 / (254 * 12)
457 return Float(feet=Meter(meter) / (_M_FOOT_USRV if usurvey else
458 (_M_FOOT_FR if pied else
459 (_M_FOOT_GE if fuss else _M_FOOT_INTL))))
462def m2furlong(meter):
463 '''Convert meter to furlongs.
465 @arg meter: Value in meter (C{scalar}).
467 @return: Value in C{furlongs} (C{float}).
469 @raise ValueError: Invalid B{C{meter}}.
470 '''
471 return Float(furlong=Meter(meter) / _M_FURLONG) # * 0.00497096954
474def m2km(meter):
475 '''Convert meter to kilo meter (km).
477 @arg meter: Value in meter (C{scalar}).
479 @return: Value in km (C{float}).
481 @raise ValueError: Invalid B{C{meter}}.
482 '''
483 return Float(km=Meter(meter) / _M_KM)
486def m2NM(meter):
487 '''Convert meter to nautical miles (NM).
489 @arg meter: Value in meter (C{scalar}).
491 @return: Value in C{NM} (C{float}).
493 @raise ValueError: Invalid B{C{meter}}.
494 '''
495 return Float(NM=Meter(meter) / _M_NM) # * 5.39956804e-4
498def m2radians(distance, radius=R_M, lat=0):
499 '''Convert a distance to an angle along the equator or
500 along the parallel at an other (geodetic) latitude.
502 @arg distance: Distance (C{meter}, same units as B{C{radius}}).
503 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
504 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or
505 L{a_f2Tuple}).
506 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
508 @return: Angle (C{radians}) or C{INF} for near-polar B{C{lat}}.
510 @raise RangeError: Latitude B{C{lat}} outside valid range and
511 L{pygeodesy.rangerrors} set to C{True}.
513 @raise TypeError: Invalid B{C{radius}}.
515 @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
516 or B{C{lat}}.
518 @see: Function L{m2degrees} and L{radians2m}.
519 '''
520 m = circle4(radius, lat).radius
521 return INF if m < EPS0 else Radians(Float(distance=distance) / m)
524def m2SM(meter):
525 '''Convert meter to statute miles (SM).
527 @arg meter: Value in meter (C{scalar}).
529 @return: Value in C{SM} (C{float}).
531 @raise ValueError: Invalid B{C{meter}}.
532 '''
533 return Float(SM=Meter(meter) / _M_SM) # * 6.21369949e-4 == 1 / 1609.344
536def m2toise(meter):
537 '''Convert meter to French U{toises<https://WikiPedia.org/wiki/Toise>}.
539 @arg meter: Value in meter (C{scalar}).
541 @return: Value in C{toises} (C{float}).
543 @raise ValueError: Invalid B{C{meter}}.
545 @see: Function L{m2fathom}.
546 '''
547 return Float(toise=Meter(meter) / _M_TOISE) # * 0.513083632632119
550def m2yard(meter):
551 '''Convert meter to I{UK} yards.
553 @arg meter: Value in meter (C{scalar}).
555 @return: Value in C{yards} (C{float}).
557 @raise ValueError: Invalid B{C{meter}}.
558 '''
559 return Float(yard=Meter(meter) / _M_YARD_UK) # * 1.0936132983377078
562def NM2m(nm):
563 '''Convert nautical miles to meter (m).
565 @arg nm: Value in nautical miles (C{scalar}).
567 @return: Value in meter (C{float}).
569 @raise ValueError: Invalid B{C{nm}}.
570 '''
571 return Meter(Float(nm=nm) * _M_NM)
574def radians2m(rad, radius=R_M, lat=0):
575 '''Convert an angle to a distance along the equator or
576 along the parallel at an other (geodetic) latitude.
578 @arg rad: The angle (C{radians}).
579 @kwarg radius: Mean earth radius, ellipsoid or datum
580 (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
581 L{Datum} or L{a_f2Tuple}).
582 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
584 @return: Distance (C{meter}, same units as B{C{radius}}
585 or equatorial and polar radii) or C{0.0} for
586 near-polar B{C{lat}}.
588 @raise RangeError: Latitude B{C{lat}} outside valid range and
589 L{pygeodesy.rangerrors} set to C{True}.
591 @raise TypeError: Invalid B{C{radius}}.
593 @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or
594 B{C{lat}}.
596 @see: Function L{degrees2m} and L{m2radians}.
597 '''
598 return _radians2m(Lam(rad=rad, clip=0), radius, lat)
601def _radians2m(rad, radius, lat):
602 '''(INTERNAL) Helper for C{degrees2m} and C{radians2m}.
603 '''
604 m = circle4(radius, lat).radius
605 return _0_0 if m < EPS0 else (rad * m)
608def radiansPI(deg):
609 '''Convert and wrap degrees to radians M{[-PI..+PI]}.
611 @arg deg: Angle (C{degrees}).
613 @return: Radians, wrapped (C{radiansPI})
614 '''
615 return _wrap(radians(deg), PI, PI2)
618def radiansPI2(deg):
619 '''Convert and wrap degrees to radians M{[0..+2PI)}.
621 @arg deg: Angle (C{degrees}).
623 @return: Radians, wrapped (C{radiansPI2})
624 '''
625 return _wrap(radians(deg), PI2, PI2)
628def radiansPI_2(deg):
629 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}.
631 @arg deg: Angle (C{degrees}).
633 @return: Radians, wrapped (C{radiansPI_2})
634 '''
635 return _wrap(radians(deg), PI_2, PI2)
638def _sin0cos2(q, r, sign):
639 '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3}
640 and C{sin} zero I{signed} with B{C{sign}}.
641 '''
642 if r < PI_2:
643 s, c = sin(r), cos(r)
644 t = s, c, -s, -c, s
645 else: # r == PI_2
646 t = _1_0, _0_0, _N_1_0, _0_0, _1_0
647# else: # r == 0, testUtility failures
648# t = _0_0, _1_0, _0_0, _N_1_0, _0_0
649# q &= 3
650 s = t[q] or (NEG0 if sign < 0 else _0_0)
651 c = t[q + 1] or _0_0
652 return s, c
655def sincos2(rad):
656 '''Return the C{sine} and C{cosine} of an angle in C{radians}.
658 @arg rad: Angle (C{radians}).
660 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}).
662 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/
663 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd
664 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
665 python/geographiclib/geomath.py#l155>} and C++ U{sincosd
666 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
667 include/GeographicLib/Math.hpp#l558>}.
668 '''
669 if _isfinite(rad):
670 q = int(rad * _2__PI) # int(math.floor)
671 if q < 0:
672 q -= 1
673 t = _sin0cos2(q & 3, rad - q * PI_2, rad)
674 else:
675 t = NAN, NAN
676 return t
679def sincos2_(*rads):
680 '''Return the C{sine} and C{cosine} of angle(s) in C{radians}.
682 @arg rads: One or more angles (C{radians}).
684 @return: Yield the C{sin(B{rad})} and C{cos(B{rad})} for each angle.
686 @see: function L{sincos2}.
687 '''
688 for r in rads:
689 s, c = sincos2(r)
690 yield s
691 yield c
694def sincos2d(deg, **adeg):
695 '''Return the C{sine} and C{cosine} of an angle in C{degrees}.
697 @arg deg: Angle (C{degrees}).
698 @kwarg adeg: Optional correction (C{degrees}).
700 @return: 2-Tuple (C{sin(B{deg_})}, C{cos(B{deg_})}, C{B{deg_} =
701 B{deg} + B{adeg}}).
703 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/
704 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd
705 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
706 python/geographiclib/geomath.py#l155>} and C++ U{sincosd
707 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
708 include/GeographicLib/Math.hpp#l558>}.
709 '''
710 if _isfinite(deg):
711 q = int(deg * _1__90) # int(math.floor)
712 if q < 0:
713 q -= 1
714 d = deg - q * _90_0
715 if adeg:
716 t = _MODS.errors._xkwds_get(adeg, adeg=_0_0)
717 d = _MODS.karney._around(d + t)
718 t = _sin0cos2(q & 3, radians(d), deg)
719 else:
720 t = NAN, NAN
721 return t
724def sincos2d_(*degs):
725 '''Return the C{sine} and C{cosine} of angle(s) in C{degrees}.
727 @arg degs: One or more angles (C{degrees}).
729 @return: Yield the C{sin(B{deg})} and C{cos(B{deg})} for each angle.
731 @see: Function L{sincos2d}.
732 '''
733 for d in degs:
734 s, c = sincos2d(d)
735 yield s
736 yield c
739def sincostan3(rad):
740 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{radians}.
742 @arg rad: Angle (C{radians}).
744 @return: 3-Tuple (C{sin(B{rad})}, C{cos(B{rad})}, C{tan(B{rad})}).
746 @see: Function L{sincos2}.
747 '''
748 rad %= PI2 # see ._wrap comments
749 if rad:
750 s, c = sincos2(rad)
751 t = (s / c) if c else (NINF if s < 0
752 else (INF if s > 0 else s))
753 else: # sin(-0.0) == tan(-0.0) = -0.0
754 c = _1_0
755 s = t = NEG0 if isneg0(rad) else _0_0
756 return s, c, t
759def SM2m(sm):
760 '''Convert statute miles to meter (m).
762 @arg sm: Value in statute miles (C{scalar}).
764 @return: Value in meter (C{float}).
766 @raise ValueError: Invalid B{C{sm}}.
767 '''
768 return Meter(Float(sm=sm) * _M_SM)
771def tan_2(rad, **semi): # edge=1
772 '''Compute the tangent of half angle.
774 @arg rad: Angle (C{radians}).
775 @kwarg semi: Angle or edge name and index
776 for semi-circular error.
778 @return: M{tan(rad / 2)} (C{float}).
780 @raise ValueError: If B{C{rad}} is semi-circular
781 and B{C{semi}} is given.
782 '''
783 # .formy.excessKarney_, .sphericalTrigonometry.areaOf
784 if semi and isnear0(fabs(rad) - PI):
785 for n, v in semi.items():
786 break
787 n = _SPACE_(n, _radians_) if not isint(v) else \
788 _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)
789 raise _MODS.errors._ValueError(n, rad, txt=_semi_circular_)
791 return tan(rad * _0_5)
794def tand(deg, **error_kwds):
795 '''Return the C{tangent} of an angle in C{degrees}.
797 @arg deg: Angle (C{degrees}).
798 @kwarg error_kwds: Error to raise (C{ValueError}).
800 @return: C{tan(B{deg})}.
802 @raise ValueError: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
803 '''
804 s, c = sincos2d(deg)
805 if isnear0(c):
806 raise _valueError(tand, deg, **error_kwds)
807 return s / c
810def tand_(*degs, **error_kwds):
811 '''Return the C{tangent} of angle(s) in C{degrees}.
813 @arg degs: One or more angles (C{degrees}).
814 @kwarg error_kwds: Error to raise (C{ValueError}).
816 @return: Yield the C{tan(B{deg})} for each angle.
818 @raise ValueError: See L{pygeodesy.tand}.
819 '''
820 for d in degs:
821 yield tand(d, **error_kwds)
824def tanPI_2_2(rad):
825 '''Compute the tangent of half angle, 90 degrees rotated.
827 @arg rad: Angle (C{radians}).
829 @return: M{tan((rad + PI/2) / 2)} (C{float}).
830 '''
831 return tan((rad + PI_2) * _0_5)
834def toise2m(toises):
835 '''Convert French U{toises<https://WikiPedia.org/wiki/Toise>} to meter.
837 @arg toises: Value in toises (C{scalar}).
839 @return: Value in C{meter} (C{float}).
841 @raise ValueError: Invalid B{C{toises}}.
843 @see: Function L{fathom2m}.
844 '''
845 return Meter(Float(toises=toises) * _M_TOISE)
848def truncate(x, ndigits=None):
849 '''Truncate to the given number of digits.
851 @arg x: Value to truncate (C{scalar}).
852 @kwarg ndigits: Number of digits (C{int}),
853 aka I{precision}.
855 @return: Truncated B{C{x}} (C{float}).
857 @see: Python function C{round}.
858 '''
859 if isint(ndigits):
860 p = _10_0**ndigits
861 x = int(x * p) / p
862 return x
865def unroll180(lon1, lon2, wrap=True):
866 '''Unroll longitudinal delta and wrap longitude in degrees.
868 @arg lon1: Start longitude (C{degrees}).
869 @arg lon2: End longitude (C{degrees}).
870 @kwarg wrap: Wrap and unroll to the M{(-180..+180]} range (C{bool}).
872 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees},
873 C{degrees}).
875 @see: Capability C{LONG_UNROLL} in U{GeographicLib
876 <https://GeographicLib.SourceForge.io/C++/doc/python/interface.html#outmask>}.
877 '''
878 d = lon2 - lon1
879 if wrap and fabs(d) > _180_0:
880 u = _wrap(d, _180_0, _360_0)
881 if u != d:
882 return u, lon1 + u
883 return d, lon2
886def _unrollon(p1, p2): # unroll180 == .karney._unroll2
887 '''(INTERNAL) Wrap, unroll and replace longitude if different.
888 '''
889 _, lon = unroll180(p1.lon, p2.lon, wrap=True)
890 if fabs(lon - p2.lon) > EPS:
891 p2 = p2.dup(lon=wrap180(lon))
892 return p2
895def unrollPI(rad1, rad2, wrap=True):
896 '''Unroll longitudinal delta and wrap longitude in radians.
898 @arg rad1: Start longitude (C{radians}).
899 @arg rad2: End longitude (C{radians}).
900 @kwarg wrap: Wrap and unroll to the M{(-PI..+PI]} range (C{bool}).
902 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled (C{radians},
903 C{radians}).
905 @see: Capability C{LONG_UNROLL} in U{GeographicLib
906 <https://GeographicLib.SourceForge.io/C++/doc/python/interface.html#outmask>}.
907 '''
908 r = rad2 - rad1
909 if wrap and fabs(r) > PI:
910 u = _wrap(r, PI, PI2)
911 if u != r:
912 return u, rad1 + u
913 return r, rad2
916def _valueError(where, x, **kwds):
917 '''(INTERNAL) Return a C{_ValueError}.
918 '''
919 x = _MODS.streprs.Fmt.PAREN(where.__name__, x)
920 return _MODS.errors._ValueError(x, **kwds)
923def _wrap(angle, wrap, modulo):
924 '''(INTERNAL) Angle wrapper M{((wrap-modulo)..+wrap]}.
926 @arg angle: Angle (C{degrees}, C{radians} or C{grades}).
927 @arg wrap: Range (C{degrees}, C{radians} or C{grades}).
928 @arg modulo: Upper limit (360 C{degrees}, PI2 C{radians} or 400 C{grades}).
930 @return: The B{C{angle}}, wrapped (C{degrees}, C{radians} or C{grades}).
931 '''
932 a = float(angle)
933 if not (wrap - modulo) <= a < wrap:
934 # math.fmod(-1.5, 3.14) == -1.5, but -1.5 % 3.14 == 1.64
935 # math.fmod(-1.5, 360) == -1.5, but -1.5 % 360 == 358.5
936 a %= modulo
937 if a > wrap:
938 a -= modulo
939 return a
942def wrap90(deg):
943 '''Wrap degrees to M{[-270..+90]}.
945 @arg deg: Angle (C{degrees}).
947 @return: Degrees, wrapped (C{degrees90}).
948 '''
949 return _wrap(deg, _90_0, _360_0)
952def wrap180(deg):
953 '''Wrap degrees to M{[-180..+180]}.
955 @arg deg: Angle (C{degrees}).
957 @return: Degrees, wrapped (C{degrees180}).
958 '''
959 return _wrap(deg, _180_0, _360_0)
962def wrap360(deg): # see .streprs._umod_360
963 '''Wrap degrees to M{[0..+360)}.
965 @arg deg: Angle (C{degrees}).
967 @return: Degrees, wrapped (C{degrees360}).
968 '''
969 return _wrap(deg, _360_0, _360_0)
972def wrapPI(rad):
973 '''Wrap radians to M{[-PI..+PI]}.
975 @arg rad: Angle (C{radians}).
977 @return: Radians, wrapped (C{radiansPI}).
978 '''
979 return _wrap(rad, PI, PI2)
982def wrapPI2(rad):
983 '''Wrap radians to M{[0..+2PI)}.
985 @arg rad: Angle (C{radians}).
987 @return: Radians, wrapped (C{radiansPI2}).
988 '''
989 return _wrap(rad, PI2, PI2)
992def wrapPI_2(rad):
993 '''Wrap radians to M{[-3PI/2..+PI/2]}.
995 @arg rad: Angle (C{radians}).
997 @return: Radians, wrapped (C{radiansPI_2}).
998 '''
999 return _wrap(rad, PI_2, PI2)
1002def yard2m(yards):
1003 '''Convert I{UK} yards to meter.
1005 @arg yards: Value in yards (C{scalar}).
1007 @return: Value in C{meter} (C{float}).
1009 @raise ValueError: Invalid B{C{yards}}.
1010 '''
1011 return Float(yards=yards) * _M_YARD_UK
1013# **) MIT License
1014#
1015# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
1016#
1017# Permission is hereby granted, free of charge, to any person obtaining a
1018# copy of this software and associated documentation files (the "Software"),
1019# to deal in the Software without restriction, including without limitation
1020# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1021# and/or sell copies of the Software, and to permit persons to whom the
1022# Software is furnished to do so, subject to the following conditions:
1023#
1024# The above copyright notice and this permission notice shall be included
1025# in all copies or substantial portions of the Software.
1026#
1027# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1028# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1029# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1030# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1031# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1032# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1033# OTHER DEALINGS IN THE SOFTWARE.