Coverage for pygeodesy/utily.py: 94%
335 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-15 15:48 -0500
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-15 15:48 -0500
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.01.14'
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_GE = _float( 0.31608), # German Fuss, 1 / 3.163_756_011_1364
47 FOOT_FR = _float( 0.3248406), # French Pied-du-Roi or pied, 1 / 3.078_432_929_8739
48 FOOT_US = _float( 0.3048006096012192), # US Survey foot, 1_200 / 3_937
49 FURLONG = _float( 201.168), # furlong, 220 * yard2m(1) = 10 * m2chain(1)
50 HA = _float(10000.0), # hectare, 100 * 100, squared
51 KM = _M_KM, # kilo meter
52 NM = _M_NM, # nautical mile
53 SM = _M_SM, # statute mile
54 TOISE = _float( 1.9490436), # French toise, 6 pieds = 6 / 3.078_432_929_8739
55 YARD_UK = _float( 0.9144)) # yard, 254 * 12 * 3 / 10_000 = 3 * _M.FOOT
58def _abs1nan(x):
59 '''(INTERNAL) Bracket C{-1 < x < 1 or isnan(x)}.
60 '''
61 return _N_1_0 < x < _1_0 or isnan(x)
64def acos1(x):
65 '''Return C{math.acos(max(-1, min(1, B{x})))}.
66 '''
67 return acos(x) if _abs1nan(x) else (PI if x < 0 else _0_0)
70def acre2ha(acres):
71 '''Convert acres to hectare.
73 @arg acres: Value in acres (C{scalar}).
75 @return: Value in C{hectare} (C{float}).
77 @raise ValueError: Invalid B{C{acres}}.
78 '''
79 return m2ha(acre2m2(acres))
82def acre2m2(acres):
83 '''Convert acres to I{square} meter.
85 @arg acres: Value in acres (C{scalar}).
87 @return: Value in C{meter^2} (C{float}).
89 @raise ValueError: Invalid B{C{acres}}.
90 '''
91 return Meter2(Float(acres=acres) * _M.ACRE)
94def agdf(phi):
95 '''Inverse U{Gudermannian function
96 <https://WikiPedia.org/wiki/Gudermannian_function>}.
98 @arg phi: Angle (C{radians}).
100 @return: Gudermannian (psi, C{float}).
102 @see: Function L{gdf}.
103 '''
104 return asinh(tan(phi))
107def asin1(x):
108 '''Return C{math.asin(max(-1, min(1, B{x})))}.
109 '''
110 return asin(x) if _abs1nan(x) else _copysign(PI_2, x)
113def atan1(y, x=_1_0):
114 '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}
115 using C{atan2} for consistency and to avoid C{ZeroDivisionError}.
116 '''
117 return _atan1u(y, x, atan2)
120def atan1d(y, x=_1_0):
121 '''Return C{atan(B{y} / B{x})} angle in C{degrees} M{[-90..+90]}
122 using C{atan2d} for consistency and to avoid C{ZeroDivisionError}.
124 @see: Function L{pygeodesy.atan2d}.
125 '''
126 return _atan1u(y, x, atan2d)
129def _atan1u(y, x, _2u):
130 '''(INTERNAL) Helper for functions C{atan1} and C{atan1d}.
131 '''
132 if x < 0:
133 x = -x
134 y = -y
135 return _2u(y, x or _0_0)
138atan2 = _atan2
139'''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.
141 @see: I{Karney}'s C++ function U{Math.atan2d
142 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
143'''
146def atan2b(y, x):
147 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360], counter-clockwise}.
149 @see: Function L{pygeodesy.atan2d}.
150 '''
151 b = atan2d(y, x)
152 if b < 0:
153 b += _360_0
154 return b or _0_0 # unsigned-0
157def atan2d(y, x, reverse=False):
158 '''Return C{atan2(B{y}, B{x})} in degrees M{[-180..+180]},
159 optionally I{reversed} (by 180 degrees for C{azimuth}s).
161 @see: I{Karney}'s C++ function U{Math.atan2d
162 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
163 '''
164 d = degrees(_atan2(y, x)) # preserves signed-0
165 return _azireversed(d) if reverse else d
168def _azireversed(azi): # in .rhumbBase
169 '''(INTERNAL) Return the I{reverse} B{C{azi}} in degrees M{[-180..+180]}.
170 '''
171 return azi - _copysign(_180_0, azi)
174def chain2m(chains):
175 '''Convert I{UK} chains to meter.
177 @arg chains: Value in chains (C{scalar}).
179 @return: Value in C{meter} (C{float}).
181 @raise ValueError: Invalid B{C{chains}}.
182 '''
183 return Meter(Float(chains=chains) * _M.CHAIN)
186def circle4(earth, lat):
187 '''Get the equatorial or a parallel I{circle of latitude}.
189 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum
190 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
191 @arg lat: Geodetic latitude (C{degrees90}, C{str}).
193 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}.
195 @raise RangeError: Latitude B{C{lat}} outside valid range and
196 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
198 @raise TypeError: Invalid B{C{earth}}.
200 @raise ValueError: invalid B{C{earth}} or B{C{lat}}.
201 '''
202 E = _MODS.datums._earth_ellipsoid(earth)
203 return E.circle4(lat)
206def _circle4radius(earth, lat, **radius):
207 '''(INTERNAL) Get C{circle4(earth, lat).radius}.
208 '''
209 e = _xkwds_get(radius, radius=earth) if radius else earth
210 return e if e is R_M and not lat else circle4(e, lat).radius
213def cot(rad, **raiser_kwds):
214 '''Return the C{cotangent} of an angle in C{radians}.
216 @arg rad: Angle (C{radians}).
217 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
218 ValueErrors and optional, additional
219 ValueError keyword argments.
221 @return: C{cot(B{rad})}.
223 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{rad})}.
224 '''
225 try:
226 return _cotu(*sincos2(rad))
227 except ZeroDivisionError:
228 return _nonfinite(cot, rad, **raiser_kwds)
231def cot_(*rads, **raiser_kwds):
232 '''Yield the C{cotangent} of angle(s) in C{radians}.
234 @arg rads: One or more angles (each in C{radians}).
236 @return: Yield C{cot(B{rad})} for each angle.
238 @see: Function L{pygeodesy.cot} for further details.
239 '''
240 try:
241 for r in rads:
242 yield _cotu(*sincos2(r))
243 except ZeroDivisionError:
244 yield _nonfinite(cot_, r, **raiser_kwds)
247def cotd(deg, **raiser_kwds):
248 '''Return the C{cotangent} of an angle in C{degrees}.
250 @arg deg: Angle (C{degrees}).
251 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
252 ValueErrors and optional, additional
253 ValueError keyword argments.
255 @return: C{cot(B{deg})}.
257 @raise Error: If L{pygeodesy.isnear0}C{(sin(B{deg})}.
258 '''
259 try:
260 return _cotu(*sincos2d(deg))
261 except ZeroDivisionError:
262 return _nonfinite(cotd, deg, **raiser_kwds)
265def cotd_(*degs, **raiser_kwds):
266 '''Yield the C{cotangent} of angle(s) in C{degrees}.
268 @arg degs: One or more angles (each in C{degrees}).
270 @return: Yield C{cotd(B{deg})} for each angle.
272 @see: Function L{pygeodesy.cotd} for further details.
273 '''
274 try:
275 for d in degs:
276 yield _cotu(*sincos2d(d))
277 except ZeroDivisionError:
278 yield _nonfinite(cotd_, d, **raiser_kwds)
281def _cotu(s, c):
282 '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.
283 '''
284 return _tanu(c, s)
287def degrees90(rad):
288 '''Convert radians to degrees and wrap M{[-90..+90)}.
290 @arg rad: Angle (C{radians}).
292 @return: Angle, wrapped (C{degrees90}).
293 '''
294 return wrap90(degrees(rad))
297def degrees180(rad):
298 '''Convert radians to degrees and wrap M{[-180..+180)}.
300 @arg rad: Angle (C{radians}).
302 @return: Angle, wrapped (C{degrees180}).
303 '''
304 return wrap180(degrees(rad))
307def degrees360(rad):
308 '''Convert radians to degrees and wrap M{[0..+360)}.
310 @arg rad: Angle (C{radians}).
312 @return: Angle, wrapped (C{degrees360}).
313 '''
314 return _umod_360(degrees(rad))
317def degrees2grades(deg):
318 '''Convert degrees to I{grades} (aka I{gons} or I{gradians}).
320 @arg deg: Angle (C{degrees}).
322 @return: Angle (C{grades}).
323 '''
324 return Float(grades=Degrees(deg) * _G.DEG)
327def degrees2m(deg, earth=R_M, lat=0, **radius):
328 '''Convert an angle to a distance along the equator or along a parallel
329 at (geodetic) latitude.
331 @arg deg: The angle (C{degrees}).
332 @arg earth: The earth radius (C{meter}) or an ellipsoid or datum
333 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
334 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
335 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.
337 @return: Distance (C{meter}, same units as B{C{earth}} or polar and
338 equatorial radii) or C{0.0} for near-polar B{C{lat}}.
340 @raise RangeError: Latitude B{C{lat}} outside valid range and
341 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
343 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
345 @raise ValueError: Invalid B{C{deg}}, B{C{earth}} or B{C{lat}}.
347 @see: Function L{radians2m} and L{m2degrees}.
348 '''
349 m = _circle4radius(earth, lat, **radius)
350 return _Radians2m(Lamd(deg=deg, clip=0), m)
353def fathom2m(fathoms):
354 '''Convert I{Imperial} fathom to meter.
356 @arg fathoms: Value in fathoms (C{scalar}).
358 @return: Value in C{meter} (C{float}).
360 @raise ValueError: Invalid B{C{fathoms}}.
362 @see: Function L{toise2m}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
363 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
364 '''
365 return Meter(Float(fathoms=fathoms) * _M.FATHOM)
368def ft2m(feet, usurvey=False, pied=False, fuss=False):
369 '''Convert I{International}, I{US Survey}, I{French} or I{German}
370 B{C{feet}} to C{meter}.
372 @arg feet: Value in feet (C{scalar}).
373 @kwarg usurvey: If C{True}, convert I{US Survey} foot else ...
374 @kwarg pied: If C{True}, convert French I{pied-du-Roi} else ...
375 @kwarg fuss: If C{True}, convert German I{Fuss}, otherwise
376 I{International} foot to C{meter}.
378 @return: Value in C{meter} (C{float}).
380 @raise ValueError: Invalid B{C{feet}}.
381 '''
382 return Meter(Feet(feet) * (_M.FOOT_US if usurvey else
383 (_M.FOOT_FR if pied else
384 (_M.FOOT_GE if fuss else _M.FOOT))))
387def furlong2m(furlongs):
388 '''Convert a furlong to meter.
390 @arg furlongs: Value in furlongs (C{scalar}).
392 @return: Value in C{meter} (C{float}).
394 @raise ValueError: Invalid B{C{furlongs}}.
395 '''
396 return Meter(Float(furlongs=furlongs) * _M.FURLONG)
399def gdf(psi):
400 '''U{Gudermannian function
401 <https://WikiPedia.org/wiki/Gudermannian_function>}.
403 @arg psi: Gudermannian (C{float}).
405 @return: Angle (C{radians}).
407 @see: Function L{agdf}.
408 '''
409 return atan1(sinh(psi))
412def grades(rad):
413 '''Convert radians to I{grades} (aka I{gons} or I{gradians}).
415 @arg rad: Angle (C{radians}).
417 @return: Angle (C{grades}).
418 '''
419 return Float(grades=Radians(rad) * _G.RAD)
422def grades400(rad):
423 '''Convert radians to I{grades} (aka I{gons} or I{gradians}) and wrap M{[0..+400)}.
425 @arg rad: Angle (C{radians}).
427 @return: Angle, wrapped (C{grades}).
428 '''
429 return Float(grades400=wrapPI2(rad) * _G.RAD)
432def grades2degrees(gon):
433 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{degrees}.
435 @arg gon: Angle (C{grades}).
437 @return: Angle (C{degrees}).
438 '''
439 return Degrees(Float(gon=gon) / _G.DEG)
442def grades2radians(gon):
443 '''Convert I{grades} (aka I{gons} or I{gradians}) to C{radians}.
445 @arg gon: Angle (C{grades}).
447 @return: Angle (C{radians}).
448 '''
449 return Radians(Float(gon=gon) / _G.RAD)
452def ha2acre(ha):
453 '''Convert hectare to acre.
455 @arg ha: Value in hectare (C{scalar}).
457 @return: Value in acres (C{float}).
459 @raise ValueError: Invalid B{C{ha}}.
460 '''
461 return m2acre(ha2m2(ha))
464def ha2m2(ha):
465 '''Convert hectare to I{square} meter.
467 @arg ha: Value in hectare (C{scalar}).
469 @return: Value in C{meter^2} (C{float}).
471 @raise ValueError: Invalid B{C{ha}}.
472 '''
473 return Meter2(Float(ha=ha) * _M.HA)
476def hav(rad):
477 '''Return the U{haversine<https://WikiPedia.org/wiki/Haversine_formula>} of an angle.
479 @arg rad: Angle (C{radians}).
481 @return: C{sin(B{rad} / 2)**2}.
482 '''
483 return sin(rad * _0_5)**2
486def km2m(km):
487 '''Convert kilo meter to meter (m).
489 @arg km: Value in kilo meter (C{scalar}).
491 @return: Value in meter (C{float}).
493 @raise ValueError: Invalid B{C{km}}.
494 '''
495 return Meter(Float(km=km) * _M.KM)
498def _loneg(lon):
499 '''(INTERNAL) "Complement" of C{lon}.
500 '''
501 return _180_0 - lon
504def m2acre(meter2):
505 '''Convert I{square} meter to acres.
507 @arg meter2: Value in C{meter^2} (C{scalar}).
509 @return: Value in acres (C{float}).
511 @raise ValueError: Invalid B{C{meter2}}.
512 '''
513 return Float(acre=Meter2(meter2) / _M.ACRE)
516def m2chain(meter):
517 '''Convert meter to I{UK} chains.
519 @arg meter: Value in meter (C{scalar}).
521 @return: Value in C{chains} (C{float}).
523 @raise ValueError: Invalid B{C{meter}}.
524 '''
525 return Float(chain=Meter(meter) / _M.CHAIN) # * 0.049_709_695_378_986_715
528def m2degrees(distance, earth=R_M, lat=0, **radius):
529 '''Convert a distance to an angle along the equator or along a parallel
530 at (geodetic) latitude.
532 @arg distance: Distance (C{meter}, same units as B{C{radius}}).
533 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum
534 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
535 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
536 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.
538 @return: Angle (C{degrees}) or C{INF} or C{NINF} for near-polar B{C{lat}}.
540 @raise RangeError: Latitude B{C{lat}} outside valid range and
541 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
543 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
545 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.
547 @see: Function L{m2radians} and L{degrees2m}.
548 '''
549 return degrees(m2radians(distance, earth=earth, lat=lat, **radius))
552def m2fathom(meter):
553 '''Convert meter to I{Imperial} fathoms.
555 @arg meter: Value in meter (C{scalar}).
557 @return: Value in C{fathoms} (C{float}).
559 @raise ValueError: Invalid B{C{meter}}.
561 @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
562 and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
563 '''
564 return Float(fathom=Meter(meter) / _M.FATHOM) # * 0.546_806_649
567def m2ft(meter, usurvey=False, pied=False, fuss=False):
568 '''Convert meter to I{International}, I{US Survey}, I{French} or
569 or I{German} feet (C{ft}).
571 @arg meter: Value in meter (C{scalar}).
572 @kwarg usurvey: If C{True}, convert to I{US Survey} foot else ...
573 @kwarg pied: If C{True}, convert to French I{pied-du-Roi} else ...
574 @kwarg fuss: If C{True}, convert to German I{Fuss}, otherwise to
575 I{International} foot.
577 @return: Value in C{feet} (C{float}).
579 @raise ValueError: Invalid B{C{meter}}.
580 '''
581 # * 3.280_833_333_333_3333, US Survey 3_937 / 1_200
582 # * 3.280_839_895_013_1235, Int'l 10_000 / (254 * 12)
583 return Float(feet=Meter(meter) / (_M.FOOT_US if usurvey else
584 (_M.FOOT_FR if pied else
585 (_M.FOOT_GE if fuss else _M.FOOT))))
588def m2furlong(meter):
589 '''Convert meter to furlongs.
591 @arg meter: Value in meter (C{scalar}).
593 @return: Value in C{furlongs} (C{float}).
595 @raise ValueError: Invalid B{C{meter}}.
596 '''
597 return Float(furlong=Meter(meter) / _M.FURLONG) # * 0.004_970_969_54
600def m2ha(meter2):
601 '''Convert I{square} meter to hectare.
603 @arg meter2: Value in C{meter^2} (C{scalar}).
605 @return: Value in hectare (C{float}).
607 @raise ValueError: Invalid B{C{meter2}}.
608 '''
609 return Float(ha=Meter2(meter2) / _M.HA)
612def m2km(meter):
613 '''Convert meter to kilo meter (Km).
615 @arg meter: Value in meter (C{scalar}).
617 @return: Value in Km (C{float}).
619 @raise ValueError: Invalid B{C{meter}}.
620 '''
621 return Float(km=Meter(meter) / _M.KM)
624def m2NM(meter):
625 '''Convert meter to nautical miles (NM).
627 @arg meter: Value in meter (C{scalar}).
629 @return: Value in C{NM} (C{float}).
631 @raise ValueError: Invalid B{C{meter}}.
632 '''
633 return Float(NM=Meter(meter) / _M.NM) # * 5.399_568_04e-4
636def m2radians(distance, earth=R_M, lat=0, **radius):
637 '''Convert a distance to an angle along the equator or along a parallel
638 at (geodetic) latitude.
640 @arg distance: Distance (C{meter}, same units as B{C{radius}}).
641 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum
642 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
643 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
644 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.
646 @return: Angle (C{radians}) or C{INF} or C{NINF} for near-polar B{C{lat}}.
648 @raise RangeError: Latitude B{C{lat}} outside valid range and
649 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
651 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
653 @raise ValueError: Invalid B{C{distance}}, B{C{earth}} or B{C{lat}}.
655 @see: Function L{m2degrees} and L{radians2m}.
656 '''
657 m = _circle4radius(earth, lat, **radius)
658 return _copysign_0_0(distance) if m < EPS0 else \
659 Radians(Float(distance=distance) / m)
662def m2SM(meter):
663 '''Convert meter to statute miles (SM).
665 @arg meter: Value in meter (C{scalar}).
667 @return: Value in C{SM} (C{float}).
669 @raise ValueError: Invalid B{C{meter}}.
670 '''
671 return Float(SM=Meter(meter) / _M.SM) # * 6.213_699_49e-4 == 1 / 1_609.344
674def m2toise(meter):
675 '''Convert meter to French U{toises<https://WikiPedia.org/wiki/Toise>}.
677 @arg meter: Value in meter (C{scalar}).
679 @return: Value in C{toises} (C{float}).
681 @raise ValueError: Invalid B{C{meter}}.
683 @see: Function L{m2fathom}.
684 '''
685 return Float(toise=Meter(meter) / _M.TOISE) # * 0.513_083_632_632_119
688def m2yard(meter):
689 '''Convert meter to I{UK} yards.
691 @arg meter: Value in meter (C{scalar}).
693 @return: Value in C{yards} (C{float}).
695 @raise ValueError: Invalid B{C{meter}}.
696 '''
697 return Float(yard=Meter(meter) / _M.YARD_UK) # * 1.093_613_298_337_707_8
700def NM2m(nm):
701 '''Convert nautical miles to meter (m).
703 @arg nm: Value in nautical miles (C{scalar}).
705 @return: Value in meter (C{float}).
707 @raise ValueError: Invalid B{C{nm}}.
708 '''
709 return Meter(Float(nm=nm) * _M.NM)
712def _nonfinite(where, x, raiser=True, **kwds): # PYCHOK no cover
713 '''(INTERNAL) Raise a C{_ValueError} or return C{INF} or C{NINF}.
714 '''
715 if raiser:
716 t = typename(where)
717 t = _MODS.streprs.Fmt.PAREN(t, x)
718 raise _ValueError(t, **kwds)
719 return _copysignINF(x)
722def radians2m(rad, earth=R_M, lat=0, **radius):
723 '''Convert an angle to a distance along the equator or along a parallel
724 at (geodetic) latitude.
726 @arg rad: The angle (C{radians}).
727 @kwarg earth: Mean earth radius (C{meter}) or an ellipsoid or datum
728 (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
729 @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
730 @kwarg radius: For backward compatibility C{B{radius}=B{earth}}.
732 @return: Distance (C{meter}, same units as B{C{earth}} or polar and
733 equatorial radii) or C{0.0} for near-polar B{C{lat}}.
735 @raise RangeError: Latitude B{C{lat}} outside valid range and
736 L{rangerrors<pygeodesy.rangerrors>} is C{True}.
738 @raise TypeError: Invalid B{C{earth}} or B{C{radius}}.
740 @raise ValueError: Invalid B{C{rad}}, B{C{earth}} or B{C{lat}}.
742 @see: Function L{degrees2m} and L{m2radians}.
743 '''
744 m = _circle4radius(earth, lat, **radius)
745 return _Radians2m(Lam(rad=rad, clip=0), m)
748def _Radians2m(rad, m):
749 '''(INTERNAL) Helper for C{degrees2m} and C{radians2m}.
750 '''
751 return _copysign_0_0(rad) if m < EPS0 else (rad * m)
754def radiansPI(deg):
755 '''Convert and wrap degrees to radians M{[-PI..+PI]}.
757 @arg deg: Angle (C{degrees}).
759 @return: Radians, wrapped (C{radiansPI})
760 '''
761 return wrapPI(radians(deg))
764def radiansPI2(deg):
765 '''Convert and wrap degrees to radians M{[0..+2PI)}.
767 @arg deg: Angle (C{degrees}).
769 @return: Radians, wrapped (C{radiansPI2})
770 '''
771 return _umod_PI2(radians(deg))
774def radiansPI_2(deg):
775 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}.
777 @arg deg: Angle (C{degrees}).
779 @return: Radians, wrapped (C{radiansPI_2})
780 '''
781 return wrapPI_2(radians(deg))
784def _sin0cos2(q, r, sign):
785 '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3} and
786 C{sin} zero I{signed} with B{C{sign}}, like Karney's U{Math.sind and .cosd
787 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}
788 '''
789 q &= 3 # -1: 3, -2: 2, -3: 1, -4: 0 ...
790 if r < PI_2: # Quarter turn
791 a = fabs(r)
792 if a:
793 s = sin(r)
794 if a == PI_4:
795 s, c = _copysign(_SIN_45, s), _COS_45
796 elif a == PI_6:
797 s, c = _copysign(_SIN_30, s), _COS_30
798 else:
799 c = cos(r)
800 else:
801 s, c = _0_0, _1_0
802 else:
803 s, c = _1_0, _0_0
804 t = s, c, -s, -c, s
805 s = t[q] or _copysign_0_0(sign)
806 c = t[q + 1] or _0_0
807 return s, c
810def SinCos2(x, unit=Radians):
811 '''Get C{sin} and C{cos} of I{typed} angle.
813 @arg x: Angle (L{Degrees}, L{Radians} or scalar C{radians}).
814 @kwarg unit: The C{B{x}} unit (L{Degrees}, L{Radians}).
816 @return: 2-Tuple (C{sin(B{x})}, C{cos(B{x})}).
817 '''
818 return sincos2d(x) if unit is Degrees or unit is degrees or isinstanceof(x, Degrees, Degrees_) else (
819# sincos2(x) if unit is Radians or unit is radians or isinstanceof(x, Radians, Radians_) else
820 sincos2(Radians(x))) # assume C{radians}
823def sincos2(rad):
824 '''Return the C{sine} and C{cosine} of an angle in C{radians}.
826 @arg rad: Angle (C{radians}).
828 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}).
830 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/
831 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd
832 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
833 python/geographiclib/geomath.py#l155>} and C++ U{sincosd
834 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
835 include/GeographicLib/Math.hpp#l558>}.
836 '''
837 if _isfinite(rad):
838 q, r = divmod(rad, PI_2)
839 t = _sin0cos2(int(q), r, rad)
840 else:
841 t = NAN, NAN
842 return t
845def sincos2_(*rads):
846 '''Yield the C{sine} and C{cosine} of angle(s) in C{radians}.
848 @arg rads: One or more angles (C{radians}).
850 @return: Yield C{sin(B{rad})} and C{cos(B{rad})} for each angle.
852 @see: Function L{sincos2}.
853 '''
854 for r in rads:
855 s, c = sincos2(r)
856 yield s
857 yield c
860def sincos2d(deg, adeg=_0_0):
861 '''Return the C{sine} and C{cosine} of an angle in C{degrees}.
863 @arg deg: Angle (C{degrees}).
864 @kwarg adeg: Optional correction (C{degrees}).
866 @return: 2-Tuple (C{sin(B{deg_})}, C{cos(B{deg_})} with C{B{deg_} =
867 B{deg} + B{adeg}}).
869 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/
870 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd
871 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
872 python/geographiclib/geomath.py#l155>} and C++ U{sincosd
873 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/
874 include/GeographicLib/Math.hpp#l558>}.
875 '''
876 if _isfinite(deg):
877 q, d = divmod(deg, _90_0)
878 if adeg:
879 d = _MODS.karney._around(d + adeg)
880 t = _sin0cos2(int(q), radians(d), deg)
881 else:
882 t = NAN, NAN
883 return t
886def sincos2d_(*degs):
887 '''Yield the C{sine} and C{cosine} of angle(s) in C{degrees}.
889 @arg degs: One or more angles (C{degrees}).
891 @return: Yield C{sind(B{deg})} and C{cosd(B{deg})} for each angle.
893 @see: Function L{sincos2d}.
894 '''
895 for d in degs:
896 s, c = sincos2d(d)
897 yield s
898 yield c
901def sincostan3(rad):
902 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{radians}.
904 @arg rad: Angle (C{radians}).
906 @return: 3-Tuple (C{sin(B{rad})}, C{cos(B{rad})}, C{tan(B{rad})}).
908 @see: Function L{sincos2}.
909 '''
910 return _sincostan3(*sincos2(float(rad)))
913def _sincostan3(s, c):
914 '''(INTERNAL) Helper for C{sincostan3} and C{sincostan3d}.
915 '''
916 return s, c, _over_1(s, c) # _tanu(s, c, raiser=False)
919def sincostan3d(deg):
920 '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{degrees}.
922 @arg deg: Angle (C{degrees}).
924 @return: 3-Tuple (C{sind(B{deg})}, C{cosd(B{deg})}, C{tand(B{deg})}).
926 @see: Function L{sincos2d}.
927 '''
928 return _sincostan3(*sincos2d(float(deg)))
931def SM2m(sm):
932 '''Convert statute miles to meter (m).
934 @arg sm: Value in statute miles (C{scalar}).
936 @return: Value in meter (C{float}).
938 @raise ValueError: Invalid B{C{sm}}.
939 '''
940 return Meter(Float(sm=sm) * _M.SM)
943def tan_2(rad, **semi): # edge=1
944 '''Compute the tangent of half angle.
946 @arg rad: Angle (C{radians}).
947 @kwarg semi: Angle or edge name and index
948 for semi-circular error.
950 @return: M{tan(rad / 2)} (C{float}).
952 @raise ValueError: If B{C{rad}} is semi-circular
953 and B{C{semi}} is given.
954 '''
955 # .formy.excessKarney_, .sphericalTrigonometry.areaOf
956 if semi and isnear0(fabs(rad) - PI):
957 for n, v in semi.items():
958 break
959 n = _SPACE_(n, _radians_) if not isint(v) else \
960 _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)
961 raise _ValueError(n, rad, txt=_semi_circular_)
963 return _tan(rad * _0_5) if _isfinite(rad) else NAN
966def tan(rad, **raiser_kwds):
967 '''Return the C{tangent} of an angle in C{radians}.
969 @arg rad: Angle (C{radians}).
970 @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
971 ValueErrors and optional, additional
972 ValueError keyword argments.
974 @return: C{tan(B{rad})}.
976 @raise Error: If L{pygeodesy.isnear0}C{(cos(B{rad})}.
977 '''
978 try:
979 return _tanu(*sincos2(rad))
980 except ZeroDivisionError:
981 return _nonfinite(tan, rad, **raiser_kwds)
984def tan_(*rads, **raiser_kwds):
985 '''Yield the C{tangent} of angle(s) in C{radians}.
987 @arg rads: One or more angles (each in C{radians}).
989 @return: Yield C{tan(B{rad})} for each angle.
991 @see: Function L{pygeodesy.tan} for futher details.
992 '''
993 try:
994 for r in rads:
995 yield _tanu(*sincos2(r))
996 except ZeroDivisionError:
997 yield _nonfinite(tan_, r, **raiser_kwds)
1000def tand(deg, **raiser_clamp_kwds):
1001 '''Return the C{tangent} of an angle in C{degrees}.
1003 @arg deg: Angle (C{degrees}).
1004 @kwarg raiser_clamp_kwds: Use C{B{raiser}=False} to avoid
1005 ValueErrors, C{B{clamp}=}L{OVERFLOW} and
1006 optional, additional ValueError keyword
1007 argments.
1009 @return: C{tan(B{deg})}.
1011 @raise Error: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
1012 '''
1013 try:
1014 return _tanu(*sincos2d(deg))
1015 except ZeroDivisionError:
1016 return _nonfinite(tand, deg, **raiser_clamp_kwds)
1019def tand_(*degs, **raiser_clamp_kwds):
1020 '''Yield the C{tangent} of angle(s) in C{degrees}.
1022 @arg degs: One or more angles (each in C{degrees}).
1024 @return: Yield C{tand(B{deg})} for each angle.
1026 @see: Function L{pygeodesy.tand} for futher details.
1027 '''
1028 try:
1029 for d in degs:
1030 yield _tanu(*sincos2d(d), **raiser_clamp_kwds)
1031 except ZeroDivisionError:
1032 yield _nonfinite(tand_, d, **raiser_clamp_kwds)
1035def tanPI_2_2(rad):
1036 '''Compute the tangent of half angle, 90 degrees rotated.
1038 @arg rad: Angle (C{radians}).
1040 @return: M{tan((rad + PI/2) / 2)} (C{float}).
1041 '''
1042 return _tan((rad + PI_2) * _0_5) if _isfinite(rad) else (
1043 NAN if isnan(rad) else _copysign(_90_0, rad))
1046def _tanu(s, c, clamp=OVERFLOW, **unused):
1047 '''(INTERNAL) Helper for functions C{_cotu}, C{sincostan3},
1048 C{sincostan3d}, C{tan}, C{tan_}, C{tand} and C{tand_}.
1049 '''
1050 if s is NAN or isnan(s):
1051 t = NAN
1052 elif isnear0(c):
1053 raise ZeroDivisionError()
1054 else:
1055 t = _over_1(s, c)
1056 if clamp:
1057 t = min(clamp, max(t, -clamp))
1058 return t
1061def toise2m(toises):
1062 '''Convert French U{toises<https://WikiPedia.org/wiki/Toise>} to meter.
1064 @arg toises: Value in toises (C{scalar}).
1066 @return: Value in C{meter} (C{float}).
1068 @raise ValueError: Invalid B{C{toises}}.
1070 @see: Function L{fathom2m}.
1071 '''
1072 return Meter(Float(toises=toises) * _M.TOISE)
1075def truncate(x, ndigits=None):
1076 '''Truncate to the given number of digits.
1078 @arg x: Value to truncate (C{scalar}).
1079 @kwarg ndigits: Number of digits (C{int}),
1080 aka I{precision}.
1082 @return: Truncated B{C{x}} (C{float}).
1084 @see: Python function C{round}.
1085 '''
1086 if isint(ndigits):
1087 p = _10_0**ndigits
1088 x = int(x * p) / p
1089 return x
1092def unroll180(lon1, lon2, wrap=True):
1093 '''Unroll longitudinal delta and wrap longitude in degrees.
1095 @arg lon1: Start longitude (C{degrees}).
1096 @arg lon2: End longitude (C{degrees}).
1097 @kwarg wrap: If C{True}, wrap and unroll to the M{(-180..+180]}
1098 range (C{bool}).
1100 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees},
1101 C{degrees}).
1103 @see: Capability C{LONG_UNROLL} in U{GeographicLib
1104 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.
1105 '''
1106 d = lon2 - lon1
1107 if wrap:
1108 u = wrap180(d)
1109 if u != d:
1110 return u, (lon1 + u)
1111 return d, lon2
1114def _unrollon(p1, p2, wrap=False): # unroll180 == .karney._unroll2
1115 '''(INTERNAL) Wrap/normalize, unroll and replace longitude.
1116 '''
1117 lat, lon = p2.lat, p2.lon
1118 if wrap and _Wrap.normal:
1119 lat, lon = _Wrap.latlon(lat, lon)
1120 _, lon = unroll180(p1.lon, lon, wrap=True)
1121 if lat != p2.lat or fabs(lon - p2.lon) > EPS:
1122 p2 = p2.dup(lat=lat, lon=wrap180(lon))
1123 # p2 = p2.copy(); p2.latlon = lat, wrap180(lon)
1124 return p2
1127def _unrollon3(p1, p2, p3, wrap=False):
1128 '''(INTERNAL) Wrap/normalize, unroll 2 points.
1129 '''
1130 w = wrap
1131 if w: # PYCHOK no cover
1132 w = _Wrap.normal
1133 p2 = _unrollon(p1, p2, wrap=w)
1134 p3 = _unrollon(p1, p3, wrap=w)
1135 p2 = _unrollon(p2, p3)
1136 return p2, p3, w # was wrapped?
1139def unrollPI(rad1, rad2, wrap=True):
1140 '''Unroll longitudinal delta and wrap longitude in radians.
1142 @arg rad1: Start longitude (C{radians}).
1143 @arg rad2: End longitude (C{radians}).
1144 @kwarg wrap: If C{True}, wrap and unroll to the M{(-PI..+PI]}
1145 range (C{bool}).
1147 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled
1148 (C{radians}, C{radians}).
1150 @see: Capability C{LONG_UNROLL} in U{GeographicLib
1151 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}.
1152 '''
1153 r = rad2 - rad1
1154 if wrap:
1155 u = wrapPI(r)
1156 if u != r:
1157 return u, (rad1 + u)
1158 return r, rad2
1161class _Wrap(object):
1163 _normal = False # default
1165 @property
1166 def normal(self):
1167 '''Get the current L{normal} setting (C{True},
1168 C{False} or C{None}).
1169 '''
1170 return self._normal
1172 @normal.setter # PYCHOK setter!
1173 def normal(self, setting): # PYCHOK no cover
1174 '''Set L{normal} to C{True}, C{False} or C{None}.
1175 '''
1176 m = _MODS.formy
1177 t = {True: (m.normal, m.normal_),
1178 False: (self.wraplatlon, self.wraphilam),
1179 None: (_passargs, _passargs)}.get(setting, ())
1180 if t:
1181 self.latlon, self.philam = t
1182 self._normal = setting
1184 def latlonDMS2(self, lat, lon, **DMS2_kwds): # PYCHOK no cover
1185 if isstr(lat) or isstr(lon):
1186 kwds = _xkwds(DMS2_kwds, clipLon=0, clipLat=0)
1187 lat, lon = _MODS.dms.parseDMS2(lat, lon, **kwds)
1188 return self.latlon(lat, lon)
1190# def normalatlon(self, *latlon):
1191# return _MODS.formy.normal(*latlon)
1193# def normalamphi(self, *philam):
1194# return _MODS.formy.normal_(*philam)
1196 def wraplatlon(self, lat, lon):
1197 return wrap90(lat), wrap180(lon)
1199 latlon = wraplatlon # default
1201 def latlon3(self, lon1, lat2, lon2, wrap):
1202 if wrap:
1203 lat2, lon2 = self.latlon(lat2, lon2)
1204 lon21, lon2 = unroll180(lon1, lon2)
1205 else:
1206 lon21 = lon2 - lon1
1207 return lon21, lat2, lon2
1209 def _latlonop(self, wrap):
1210 if wrap and self._normal is not None:
1211 return self.latlon
1212 else:
1213 return _passargs
1215 def wraphilam(self, phi, lam):
1216 return wrapPI_2(phi), wrapPI(lam)
1218 philam = wraphilam # default
1220 def philam3(self, lam1, phi2, lam2, wrap):
1221 if wrap:
1222 phi2, lam2 = self.philam(phi2, lam2)
1223 lam21, lam2 = unrollPI(lam1, lam2)
1224 else:
1225 lam21 = lam2 - lam1
1226 return lam21, phi2, lam2
1228 def _philamop(self, wrap):
1229 if wrap and self._normal is not None:
1230 return self.philam
1231 else:
1232 return _passargs
1234 def point(self, ll, wrap=True): # in .points._fractional, -.PointsIter.iterate, ...
1235 '''Return C{ll} or a copy, I{normalized} or I{wrap}'d.
1236 '''
1237 if wrap and self._normal is not None:
1238 lat, lon = ll.latlon
1239 if fabs(lon) > _180_0 or fabs(lat) > _90_0:
1240 _n = self.latlon
1241 ll = ll.copy(name=typename(_n))
1242 ll.latlon = _n(lat, lon)
1243 return ll
1245_Wrap = _Wrap() # PYCHOK singleton
1248# def _wrap(angle, wrap, modulo):
1249# '''(INTERNAL) Angle wrapper M{((wrap-modulo)..+wrap]}.
1250#
1251# @arg angle: Angle (C{degrees}, C{radians} or C{grades}).
1252# @arg wrap: Range (C{degrees}, C{radians} or C{grades}).
1253# @arg modulo: Upper limit (360 C{degrees}, PI2 C{radians} or 400 C{grades}).
1254#
1255# @return: The B{C{angle}}, wrapped (C{degrees}, C{radians} or C{grades}).
1256# '''
1257# a = float(angle)
1258# if not (wrap - modulo) <= a < wrap:
1259# # math.fmod(-1.5, 3.14) == -1.5, but -1.5 % 3.14 == 1.64
1260# # math.fmod(-1.5, 360) == -1.5, but -1.5 % 360 == 358.5
1261# a %= modulo
1262# if a > wrap:
1263# a -= modulo
1264# return a
1267def wrap90(deg):
1268 '''Wrap degrees to M{[-90..+90]}.
1270 @arg deg: Angle (C{degrees}).
1272 @return: Degrees, wrapped (C{degrees90}).
1273 '''
1274 return _wrapu(wrap180(deg), _180_0, _90_0)
1277def wrap180(deg):
1278 '''Wrap degrees to M{[-180..+180]}.
1280 @arg deg: Angle (C{degrees}).
1282 @return: Degrees, wrapped (C{degrees180}).
1283 '''
1284 d = float(deg)
1285 w = _umod_360(d)
1286 if w > _180_0:
1287 w -= _360_0
1288 elif d < 0 and w == _180_0:
1289 w = -w
1290 return w
1293def wrap360(deg): # see .streprs._umod_360
1294 '''Wrap degrees to M{[0..+360)}.
1296 @arg deg: Angle (C{degrees}).
1298 @return: Degrees, wrapped (C{degrees360}).
1299 '''
1300 return _umod_360(float(deg))
1303def wrapPI(rad):
1304 '''Wrap radians to M{[-PI..+PI]}.
1306 @arg rad: Angle (C{radians}).
1308 @return: Radians, wrapped (C{radiansPI}).
1309 '''
1310 r = float(rad)
1311 w = _umod_PI2(r)
1312 if w > PI:
1313 w -= PI2
1314 elif r < 0 and w == PI:
1315 w = -PI
1316 return w
1319def wrapPI2(rad):
1320 '''Wrap radians to M{[0..+2PI)}.
1322 @arg rad: Angle (C{radians}).
1324 @return: Radians, wrapped (C{radiansPI2}).
1325 '''
1326 return _umod_PI2(float(rad))
1329def wrapPI_2(rad):
1330 '''Wrap radians to M{[-PI/2..+PI/2]}.
1332 @arg rad: Angle (C{radians}).
1334 @return: Radians, wrapped (C{radiansPI_2}).
1335 '''
1336 return _wrapu(wrapPI(rad), PI, PI_2)
1339# def wraplatlon(lat, lon):
1340# '''Both C{wrap90(B{lat})} and C{wrap180(B{lon})}.
1341# '''
1342# return wrap90(lat), wrap180(lon)
1345def wrap_normal(*normal):
1346 '''Define the operation for the keyword argument C{B{wrap}=True},
1347 across L{pygeodesy}: I{wrap}, I{normalize} or I{no-op}. For
1348 backward compatibility, the default is I{wrap}.
1350 @arg normal: If C{True}, I{normalize} lat- and longitude using
1351 L{normal} or L{normal_}, if C{False}, I{wrap} the
1352 lat- and longitude individually by L{wrap90} or
1353 L{wrapPI_2} respectively L{wrap180}, L{wrapPI} or
1354 if C{None}, leave lat- and longitude I{unchanged}.
1355 To get the current setting, do not specify.
1357 @return: The previous L{wrap_normal} setting (C{bool} or C{None}).
1358 '''
1359 t = _Wrap.normal
1360 if normal:
1361 _Wrap.normal = normal[0]
1362 return t
1365# def wraphilam(phi, lam,):
1366# '''Both C{wrapPI_2(B{phi})} and C{wrapPI(B{lam})}.
1367# '''
1368# return wrapPI_2(phi), wrapPI(lam)
1371def _wrapu(w, H, Q):
1372 '''(INTERNAL) Helper for functions C{wrap180} and C{wrapPI}.
1373 '''
1374 return (w - H) if w > Q else ((w + H) if w < (-Q) else w)
1377def yard2m(yards):
1378 '''Convert I{UK} yards to meter.
1380 @arg yards: Value in yards (C{scalar}).
1382 @return: Value in C{meter} (C{float}).
1384 @raise ValueError: Invalid B{C{yards}}.
1385 '''
1386 return Float(yards=yards) * _M.YARD_UK
1388# **) MIT License
1389#
1390# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.
1391#
1392# Permission is hereby granted, free of charge, to any person obtaining a
1393# copy of this software and associated documentation files (the "Software"),
1394# to deal in the Software without restriction, including without limitation
1395# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1396# and/or sell copies of the Software, and to permit persons to whom the
1397# Software is furnished to do so, subject to the following conditions:
1398#
1399# The above copyright notice and this permission notice shall be included
1400# in all copies or substantial portions of the Software.
1401#
1402# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1403# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1404# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1405# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1406# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1407# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1408# OTHER DEALINGS IN THE SOFTWARE.