1 2# -*- coding: utf-8 -*- 3 4u'''A Python version of I{Karney}'s C++ class U{GeodesicLineExact 5<https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1GeodesicLineExact.html>}. 6 7Copyright (C) Charles Karney (2012-2021) <Charles@Karney.com> 8and licensed under the MIT/X11 License. For more information, 9see U{GeographicLib<https://GeographicLib.SourceForge.io>}. 10''' 11# make sure int/int division yields float quotient 12from __future__ import division 13 14# A copy of comments from Karney's C{GeodesicLineExact.cpp}: 15# 16# This is a reformulation of the geodesic problem. The 17# notation is as follows: 18# - at a general point (no suffix or 1 or 2 as suffix) 19# - phi = latitude 20# - beta = latitude on auxiliary sphere 21# - omega = longitude on auxiliary sphere 22# - lambda = longitude 23# - alpha = azimuth of great circle 24# - sigma = arc length along great circle 25# - s = distance 26# - tau = scaled distance (= sigma at multiples of PI/2) 27# - at northwards equator crossing 28# - beta = phi = 0 29# - omega = lambda = 0 30# - alpha = alpha0 31# - sigma = s = 0 32# - a 12 suffix means a difference, e.g., s12 = s2 - s1. 33# - s and c prefixes mean sin and cos 34 35# from pygeodesy.basics import copysign0 # from .fmath 36from pygeodesy.fmath import copysign0, fsum_, hypot, norm2 37from pygeodesy.geodesicx.gxbases import _all_caps, _ALL_DOCS, Caps, \ 38 _coSeries, _GeodesicBase, \ 39 _sincos12, _TINY 40from pygeodesy.interns import NAN, NN, PI_2, _COMMASPACE_, \ 41 _name_, _0_0, _1_0, _90_0, _180_0 42# from pygeodesy.lazily import _ALL_DOCS # from .geodesicx.bases 43from pygeodesy.karney import _around, _fix90, GDict, _norm180 44from pygeodesy.props import Property_RO, _update_all 45from pygeodesy.streprs import pairs 46from pygeodesy.utily import atan2d, sincos2, sincos2d 47 48from math import atan2, degrees, floor, radians 49 50__all__ = () 51__version__ = '21.06.30' 52 53_glXs = [] # instances of C{[_]GeodesicLineExact} 54 55 56def _update_glXs(gX_glX): # see .C4Order, ._ef_reset, .GdistDirect 57 '''(INTERNAL) Zap cached/memoized C{Property}s or remove. 58 ''' 59 if _glXs: # can become empty, even None 60 if isinstance(gX_glX, _GeodesicLineExact): 61 _glXs.remove(gX_glX) # most recent 62 else: # PYCHOK no cover 63 for glX in _glXs: # XXX use weakref 64 glX._update(glX._gX is gX_glX) 65 66 67class _GeodesicLineExact(_GeodesicBase): 68 '''(INTERNAL) Base class for L{GeodesicLineExact}. 69 ''' 70 _a13 = _s13 = NAN 71 _azi1 = _0_0 72 _caps = 0 # not initilized 73 _cchi1 = NAN 74 _dn1 = NAN 75 _gX = None # Exact only 76 _k2 = NAN 77 _lat1 = _lon1 = _0_0 78 _salp0 = _calp0 = NAN 79 _salp1 = _calp1 = NAN 80 _somg1 = _comg1 = NAN 81 _ssig1 = _csig1 = NAN 82 83 def __init__(self, gX, lat1, lon1, azi1, caps, _debug, *salp1_calp1, **name): 84 '''(INTERNAL) New C{[_]GeodesicLineExact} instance. 85 ''' 86 if salp1_calp1: 87 salp1, calp1 = salp1_calp1 88 else: 89 azi1 = _norm180(azi1) 90 # guard against salp0 underflow, 91 # also -0 is converted to +0 92 salp1, calp1 = sincos2d(_around(azi1)) 93 94 self._gX = gX # Exact only 95 self._lat1 = lat1 = _fix90(lat1) 96 self._lon1 = lon1 97 self._azi1 = azi1 98 self._salp1 = salp1 99 self._calp1 = calp1 100 if _debug: # PYCHOK no cover 101 caps |= Caps._DEBUG_LINE & _debug 102 # allow lat, azimuth and unrolling of lon 103 self._caps = caps | Caps._LINE 104 self._caps_DISTANCE_IN = caps & (Caps._OUTMASK & Caps.DISTANCE_IN) 105 106 name = name.get(_name_, NN) or gX.name 107 if name: 108 self.name = name 109 110 sbet1, cbet1 = sincos2d(_around(lat1)) 111 sbet1 *= gX.f1 112 # Ensure cbet1 = +epsilon at poles 113 sbet1, cbet1 = norm2(sbet1, cbet1) 114 cbet1 = max(_TINY, cbet1) 115 self._dn1 = gX._dn(sbet1, cbet1) 116 117 # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), 118 self._salp0 = cbet1 * salp1 # alp0 in [0, pi/2 - |bet1|] 119 # Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following 120 # is slightly better (consider the case salp1 = 0). 121 self._calp0 = hypot(calp1, salp1 * sbet1) 122 self._k2 = gX._eF_reset_k2(self._calp0) 123 # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). 124 # sig = 0 is nearest northward crossing of equator. 125 # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). 126 # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 127 # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 128 # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). 129 # With alp0 in (0, pi/2], quadrants for sig and omg coincide. 130 # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. 131 # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. 132 self._somg1 = sbet1 * self._salp0 133 self._comg1 = c = (cbet1 * calp1) if (sbet1 or calp1) else _1_0 134 # Without normalization we have schi1 = somg1. 135 self._cchi1 = gX.f1 * self._dn1 * c 136 self._ssig1, self._csig1 = norm2(sbet1, c) # sig1 in (-pi, pi] 137 # norm2(somg1, comg1) # no need to normalize! 138 # norm2(schi1?, cchi1) # no need to normalize! 139 140 _glXs.append(self) 141 # no need to pre-compute other attrs based on _Caps.X. All are 142 # Property_RO's, computed once and cached/memoized until reset 143 # when C4Order is changed or Elliptic function reset. 144 145 def __del__(self): # XXX use weakref? 146 while _glXs: # may be None 147 try: # PYCHOK no cover 148 _glXs.remove(self) 149 except ValueError: 150 break 151 _update_all(self) 152 self._gX = None 153 154 def _update(self, updated, *attrs): 155 if updated: 156 _update_all(self, *attrs) 157 158 @Property_RO 159 def a13(self): 160 '''Get (spherical) arc length from the first to the reference point (C{degrees}). 161 162 @see: Method L{SetArc}. 163 ''' 164 return self._a13 165 166 @Property_RO 167 def _A4_e2a2(self): 168 '''(INTERNAL) Cached/memoized. 169 ''' 170 return self._calp0 * self._salp0 * self._gX._e2a2 171 172 def ArcPosition(self, a12, outmask=Caps.STANDARD): 173 '''Find the position on the line given B{C{a12}}. 174 175 @arg a12: Spherical arc length from the first point to the 176 second point (C{degrees}). 177 @kwarg outmask: Bit-or'ed combination of L{Caps} values specifying 178 the quantities to be returned. 179 180 @return: A C{dict} with up to 12 items C{lat1, lon1, azi1, lat2, 181 lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1}, 182 C{lon1}, C{azi1} and arc length C{a12} always included, 183 except when C{a12=NAN}. 184 185 @note: By default, C{B{outmask}=STANDARD}, meaning thc C{lat1}, 186 C{lon1}, C{azi1}, C{lat2}, C{lon2}, C{azi2}, C{s12} and 187 C{a12} entries are returned, except when C{a12=NAN}. 188 ''' 189 return self._GDictPosition(True, a12, outmask) 190 191 @Property_RO 192 def azi0(self): 193 '''Get the I{equatorial azimuth}, the azimuth of this geodesic line 194 as it crosses the equator in a northward direction (C{degrees90}). 195 ''' 196 t = self.azi0_sincos2 197 return NAN if NAN in t else atan2d(*t) 198 199 @Property_RO 200 def azi0_sincos2(self): 201 '''Get the sine and cosine of the I{equatorial azimuth} (2-tuple C{(sin, cos)}). 202 ''' 203 return self._salp0, self._calp0 204 205 @Property_RO 206 def azi1(self): 207 '''Get the azimuth at the first point (compass C{degrees}). 208 ''' 209 return self._azi1 210 211 @Property_RO 212 def azi1_sincos2(self): 213 '''Get the sine and cosine of the first point's azimuth (2-tuple C{(sin, cos)}). 214 ''' 215 return self._salp1, self._calp1 216 217 @Property_RO 218 def _B41(self): 219 '''(INTERNAL) Cached/memoized. 220 ''' 221 return _coSeries(self._C4a, self._ssig1, self._csig1) 222 223 @Property_RO 224 def _C4a(self): 225 '''(INTERNAL) Cached/memoized. 226 ''' 227 return self._gX._C4f_k2(self._k2) 228 229 @Property_RO 230 def caps(self): 231 '''Get the capabilities (bit-or'ed C{Caps}). 232 ''' 233 return self._caps 234 235 def caps_(self, caps): 236 '''Check the available capabilities. 237 238 @arg caps: Bit-or'ed combination of L{Caps} values 239 for all capabilities to be checked. 240 241 @return: C{True} if I{all} B{C{caps}} are available, 242 C{False} otherwise (C{bool}). 243 ''' 244 return _all_caps(self.caps, caps) 245 246 @Property_RO 247 def _D0_k2(self): 248 '''(INTERNAL) Cached/memoized. 249 ''' 250 return self._eF.cD / PI_2 * self._k2 251 252 @Property_RO 253 def _D1(self): 254 '''(INTERNAL) Cached/memoized. 255 ''' 256 return self._eF.deltaD(self._ssig1, self._csig1, self._dn1) 257 258 @Property_RO 259 def _E0_b(self): 260 '''(INTERNAL) Cached/memoized. 261 ''' 262 return self._eF.cE / PI_2 * self._gX.b 263 264 @Property_RO 265 def _E1(self): 266 '''(INTERNAL) Cached/memoized. 267 ''' 268 return self._eF.deltaE(self._ssig1, self._csig1, self._dn1) 269 270 @Property_RO 271 def _eF(self): 272 '''(INTERNAL) Cached/memoized. 273 ''' 274 return self._gX._eF # .geodesic._eF 275 276 def _GDictPosition(self, arcmode, s12_a12, outmask): # MCCABE 17 277 '''(INTERNAL) Generate a new position along the geodesic. 278 279 @return: A C{dict} with up to 12 items C{lat1, lon1, azi1, lat2, 280 lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1}, 281 C{lon1}, C{azi1} and arc length C{a12} always included, 282 except when C{a12=NAN}. 283 ''' 284 r = GDict(a12=NAN, s12=NAN) # note both a12 and s12, always 285 if not (arcmode or self._caps_DISTANCE_IN): # PYCHOK no cover 286 return r # Uninitialized or impossible distance requested 287 288 if self.debug: # PYCHOK no cover 289 outmask |= Caps._DEBUG_LINE & self._debug 290 outmask &= self._caps & Caps._OUTMASK 291 292 gX = self._gX 293 eF = self._eF 294 295 if arcmode: # PYCHOK no cover 296 # s12_a12 is spherical arc length 297 E2 = _0_0 298 sig12 = radians(s12_a12) 299 ssig12, csig12 = sincos2(sig12) 300 a = abs(s12_a12) 301 a -= floor(a / _180_0) * _180_0 302 if a == _0_0: 303 ssig12 = _0_0 304 elif a == _90_0: 305 csig12 = _0_0 306 else: # s12_a12 is distance 307 t = s12_a12 / self._E0_b 308 s, c = sincos2(t) # tau12 309 # tau2 = tau1 + tau12 310 E2 = -eF.deltaEinv(*_sincos12(-s, c, *self._stau1_ctau1)) 311 sig12 = fsum_(self._E1, -E2, t) 312 ssig12, csig12 = sincos2(sig12) 313 314 salp0, calp0 = self._salp0, self._calp0 315 ssig1, csig1 = self._ssig1, self._csig1 316 317 # sig2 = sig1 + sig12 318 ssig2, csig2 = _sincos12(-ssig12, csig12, ssig1, csig1) 319 dn2 = eF.fDelta(ssig2, csig2) 320 # sin(bet2) = cos(alp0) * sin(sig2) 321 sbet2 = calp0 * ssig2 322 # Alt: cbet2 = hypot(csig2, salp0 * ssig2); 323 cbet2 = hypot(salp0, calp0 * csig2) 324 if not cbet2: # salp0 = 0, csig2 = 0, break degeneracy 325 cbet2 = csig2 = _TINY 326 # tan(alp0) = cos(sig2) * tan(alp2) 327 salp2 = salp0 328 calp2 = calp0 * csig2 # no need to normalize 329 330 if (outmask & Caps.DISTANCE): 331 if arcmode: 332 E2 = eF.deltaE(ssig2, csig2, dn2) 333 # AB1 = _E0 * (E2 - _E1) 334 # s12 = _b * (_E0 * sig12 + AB1) 335 # = _b * _E0 * (sig12 + (E2 - _E1)) 336 # = _b * _E0 * (E2 - _E1 + sig12) 337 s12 = self._E0_b * fsum_(E2, -self._E1, sig12) 338 else: 339 s12 = s12_a12 340 r.set_(s12=s12) 341 342 if (outmask & Caps._DEBUG_LINE): # PYCHOK no cover 343 r.set_(sig12=sig12, dn2=dn2, E0_b=self._E0_b, E1=self._E1, E2=E2, 344 e2=gX.e2, f1=gX.f1, eFk2=eF.k2, eFa2=eF.alpha2) 345 346 if (outmask & Caps.LONGITUDE): 347 schi1 = self._somg1 348 cchi1 = self._cchi1 349 schi2 = ssig2 * salp0 350 cchi2 = gX.f1 * dn2 * csig2 # schi2 = somg2 without normalization 351 lam12 = salp0 * self._H0_e2_f1 * fsum_(eF.deltaH(ssig2, csig2, dn2), 352 -self._H1, sig12) 353 if (outmask & Caps.LONG_UNROLL): 354 E = copysign0(_1_0, salp0) # east-going? 355 tchi1 = E * schi1 356 tchi2 = E * schi2 357 chi12 = E * fsum_(atan2(ssig1, csig1), -atan2(ssig2, csig2), 358 atan2(tchi2, cchi2), -atan2(tchi1, cchi1), sig12) 359 lon2 = self.lon1 + degrees(chi12 - lam12) 360 else: 361 chi12 = atan2(*_sincos12(schi1, cchi1, schi2, cchi2)) 362 lon2 = _norm180(self._lon1_norm180 + _norm180(degrees(chi12 - lam12))) 363 r.set_(lon2=lon2) 364 if (outmask & Caps._DEBUG_LINE): # PYCHOK no cover 365 r.set_(chi12=chi12, lam12=lam12, H0_e2_f1=self._H0_e2_f1, H1=self._H1, 366 ssig2=ssig2, csig2=csig2) 367 368 if (outmask & Caps.LATITUDE): 369 r.set_(lat2=atan2d(sbet2, gX.f1 * cbet2)) 370 371 if (outmask & Caps.AZIMUTH): 372 r.set_(azi2=atan2d(salp2, calp2, reverse=outmask & Caps.REVERSE2)) 373 374 if (outmask & Caps._REDUCEDLENGTH_GEODESICSCALE): 375 dn1 = self._dn1 376 J12 = self._D0_k2 * fsum_(eF.deltaD(ssig2, csig2, dn2), -self._D1, sig12) 377 if (outmask & Caps._DEBUG_LINE): # PYCHOK no cover 378 r.set_(dn1=dn1, D0_k2=self._D0_k2, D1=self._D1, 379 J12=J12, ssig1=ssig1, csig1=csig1, b=gX.b) 380 if (outmask & Caps.REDUCEDLENGTH): 381 # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure 382 # accurate cancellation in the case of coincident points. 383 r.set_(m12=gX.b * fsum_(dn2 * (csig1 * ssig2), 384 -dn1 * (ssig1 * csig2), 385 -J12 * (csig1 * csig2))) 386 if (outmask & Caps.GEODESICSCALE): 387 t = self._k2 * (ssig2 - ssig1) * (ssig2 + ssig1) / (dn2 + dn1) 388 r.set_(M12=csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1, 389 M21=csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2) 390 391 if (outmask & Caps.AREA): 392 B42 = _coSeries(self._C4a, ssig2, csig2) 393 if calp0 and salp0: 394 # tan(alp) = tan(alp0) * sec(sig) 395 # tan(alp2-alp1) = (tan(alp2) - tan(alp1)) / (tan(alp2) * tan(alp1) + 1) 396 # = calp0 * salp0 * (csig1 - csig2) / (salp0^2 + calp0^2 * csig1 * csig2) 397 # If csig12 > 0, write 398 # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) 399 # else 400 # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 401 # No need to normalize 402 salp12 = (((ssig12 * csig1 / (_1_0 + csig12) + ssig1) * ssig12) if csig12 > 0 else 403 (csig1 * (_1_0 - csig12) + ssig1 * ssig12)) * salp0 * calp0 404 calp12 = salp0**2 + calp0**2 * csig1 * csig2 405 else: 406 # alp12 = alp2 - alp1, used in atan2 so no need to normalize 407 salp12, calp12 = _sincos12(self._salp1, self._calp1, salp2, calp2) 408 # We used to include some patch up code that purported to deal 409 # with nearly meridional geodesics properly. However, this turned 410 # out to be wrong once salp1 = -0 was allowed (via InverseLine). 411 # In fact, the calculation of {s,c}alp12 was already correct 412 # (following the IEEE rules for handling signed zeros). So, 413 # the patch up code was unnecessary (as well as dangerous). 414 A4_e2a2 = self._A4_e2a2 415 r.set_(S12=fsum_(A4_e2a2 * B42, 416 -A4_e2a2 * self._B41, gX.c2 * atan2(salp12, calp12))) 417 if (outmask & Caps._DEBUG_LINE): # PYCHOK no cover 418 r.set_(salp12=salp12, calp12=calp12, B42=B42, A4_e2a2=A4_e2a2, 419 salp0=salp0, calp0=calp0, B41=self._B41, c2=gX.c2) 420 421 r.set_(a12=s12_a12 if arcmode else degrees(sig12), 422 lat1=self.lat1, # == _fix90(lat1) 423 lon1=self.lon1 if (outmask & Caps.LONG_UNROLL) else self._lon1_norm180, 424 azi1=_norm180(self.azi1)) 425 return r 426 427 def _GenPosition(self, arcmode, s12_a12, outmask): 428 '''(INTERNAL) Generate a new position along the geodesic. 429 430 @return: L{Direct9Tuple}C{(a12, lat2, lon2, azi2, 431 s12, m12, M12, M21, S12)}. 432 ''' 433 r = self._GDictPosition(arcmode, s12_a12, outmask) 434 return r.toDirect9Tuple() 435 436 def _GenSet(self, arcmode, s13_a13): 437 '''(INTERNAL) Aka C++ C{GenSetDistance}. 438 ''' 439 if arcmode: 440 self.SetArc(s13_a13) 441 else: 442 self.SetDistance(s13_a13) 443 return self # for gx.GeodesicExact.InverseLine and ._GenDirectLine 444 445 @Property_RO 446 def geodesic(self): 447 '''Get the I{exact} geodesic (L{GeodesicExact}). 448 ''' 449 return self._gX 450 451 @Property_RO 452 def _H0_e2_f1(self): 453 '''(INTERNAL) Cached/memoized. 454 ''' 455 return self._eF.cH / PI_2 * self._gX._e2_f1 456 457 @Property_RO 458 def _H1(self): 459 '''(INTERNAL) Cached/memoized. 460 ''' 461 return self._eF.deltaH(self._ssig1, self._csig1, self._dn1) 462 463 @Property_RO 464 def lat1(self): 465 '''Get the latitude of the first point (C{degrees}). 466 ''' 467 return self._lat1 468 469 @Property_RO 470 def lon1(self): 471 '''Get the longitude of the first point (C{degrees}). 472 ''' 473 return self._lon1 474 475 @Property_RO 476 def _lon1_norm180(self): 477 '''(INTERNAL) Cached/memoized. 478 ''' 479 return _norm180(self._lon1) 480 481 def Position(self, s12, outmask=Caps.STANDARD): 482 '''Find the position on the line given B{C{s12}}. 483 484 @arg s12: Distance from the first point to the second C({meter}). 485 @kwarg outmask: Bit-or'ed combination of L{Caps} values specifying 486 the quantities to be returned. 487 488 @return: A C{dict} with up to 12 items C{lat1, lon1, azi1, lat2, 489 lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1}, 490 C{lon1}, C{azi1} and arc length C{a12} always included, 491 except when C{a12=NAN}. 492 493 @note: By default, C{B{outmask}=STANDARD}, meaning thc C{lat1}, 494 C{lon1}, C{azi1}, C{lat2}, C{lon2}, C{azi2}, C{s12} and 495 C{a12} entries are returned, except when C{a12=NAN}. 496 497 @note: This L{GeodesicLineExact} instance must have been 498 constructed with capability C{Caps.DISTANCE_IN} set. 499 ''' 500 return self._GDictPosition(False, s12, outmask) 501 502 @Property_RO 503 def s13(self): 504 '''Get the distance from the first to the reference point C({meter}). 505 506 @see: Method L{SetDistance}. 507 ''' 508 return self._s13 509 510 def SetArc(self, a13): 511 '''Set reference point 3 in terms of distance to the first point. 512 513 @arg a13: Spherical arc length from the first to the reference 514 point (C{degrees}). 515 516 @return: The distance C{s13} (C{meter}) between the first and 517 the reference point. 518 ''' 519 self._a13 = a13 520 # In case the GeodesicLineExact doesn't have the DISTANCE capability. 521 self._s13 = s13 = self._GDictPosition(True, a13, Caps.DISTANCE).s12 522 return s13 523 524 def SetDistance(self, s13): 525 '''Set reference point 3 in terms of distance to the first point. 526 527 @arg s13: Distance from the first to the reference point C({meter}). 528 529 @return: The arc length C{a13} (C{degrees}) between the first 530 and the reference point or C{NAN}. 531 ''' 532 self._s13 = s13 533 # _a13 will be_NAN if the GeodesicLineExact 534 # doesn't have the DISTANCE_IN capability 535 self._a13 = a13 = self._GDictPosition(False, s13, 0).a12 536 return a13 537 538 @Property_RO 539 def _stau1_ctau1(self): 540 '''(INTERNAL) Cached/memoized. 541 ''' 542 s, c = sincos2(self._E1) 543 # tau1 = sig1 + B11 544 return _sincos12(-s, c, self._ssig1, self._csig1) 545 # unnecessary because Einv inverts E 546 # return -self._gX._eF.deltaEinv(stau1, ctau1) 547 548 def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature 549 '''Return this C{GeodesicExactLine} as string. 550 551 @kwarg prec: The C{float} precision, number of decimal digits (0..9). 552 Trailing zero decimals are stripped for B{C{prec}} values 553 of 1 and above, but kept for negative B{C{prec}} values. 554 @kwarg sep: Optional separator to join (C{str}). 555 556 @return: C{GeodesicExactLine} (C{str}). 557 ''' 558 d = dict(geodesic=self.geodesic, 559 lat1=self.lat1, lon1=self.lon1, azi1=self.azi1, 560 a13=self.a13, s13=self.s13) 561 return sep.join(pairs(d, prec=prec)) 562 563 564__all__ += _ALL_DOCS(_GeodesicLineExact) 565 566# **) MIT License 567# 568# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved. 569# 570# Permission is hereby granted, free of charge, to any person obtaining a 571# copy of this software and associated documentation files (the "Software"), 572# to deal in the Software without restriction, including without limitation 573# the rights to use, copy, modify, merge, publish, distribute, sublicense, 574# and/or sell copies of the Software, and to permit persons to whom the 575# Software is furnished to do so, subject to the following conditions: 576# 577# The above copyright notice and this permission notice shall be included 578# in all copies or substantial portions of the Software. 579# 580# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 581# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 582# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 583# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 584# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 585# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 586# OTHER DEALINGS IN THE SOFTWARE. 587