1 2# -*- coding: utf-8 -*- 3 4u'''Wrapper to invoke I{Karney}'s U{GeodSolve 5<https://GeographicLib.SourceForge.io/html/GeodSolve.1.html>} utility 6as an (exact) geodesic, but intended I{for testing purposes only}. 7 8Set env variable C{PYGEODESY_GEODSOLVE} to the (fully qualified) path 9of the C{GeodSolve} executable. 10''' 11 12from pygeodesy.basics import map2, _xinstanceof 13from pygeodesy.datums import _ellipsoidal_datum 14from pygeodesy.ellipsoids import Ellipsoids, Ellipsoid2 15from pygeodesy.geodesicx.gxbases import _all_caps, Caps, _GeodesicBase 16from pygeodesy.interns import DIG, NN, _0_, _COMMASPACE_, _SPACE_ 17from pygeodesy.interns import _not_ # PYCHOK used! 18from pygeodesy.karney import GDict, GeodesicError, GeodSolve12Tuple 19from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, printf, _sys_version_info2 20from pygeodesy.lazily import _getenv # PYCHOK used! 21from pygeodesy.named import callername 22from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple 23from pygeodesy.props import Property, Property_RO, property_RO 24from pygeodesy.streprs import Fmt, fstr, fstrzs, pairs, strs 25from pygeodesy.units import Precision_ 26from pygeodesy.utily import sincos2d, unroll180, wrap360 27 28from subprocess import PIPE as _PIPE, Popen as _Popen, STDOUT as _STDOUT 29 30__all__ = _ALL_LAZY.geodsolve 31__version__ = '21.09.07' 32 33_PYGEODESY_GEODSOLVE_ = 'PYGEODESY_GEODSOLVE' # PYCHOK used! 34 35_len_N = len(GeodSolve12Tuple._Names_) 36_SLASH_ = '/' 37_stdin_ = 'stdin' 38 39 40def _cmd_stdin_(cmd, stdin): # PYCHOK no cover 41 '''(INTERNAL) Cmd line, stdin and caller as sC{str}. 42 ''' 43 c = Fmt.PAREN(callername(up=3)) 44 t = (c,) if stdin is None else (_SLASH_, str(stdin), c) 45 return _SPACE_.join(cmd + t) 46 47 48class _GeodesicSolveBase(_GeodesicBase): 49 '''(NTERNAL) Base class for L{GeodesicSolve} and L{GeodesicLineSolve}. 50 ''' 51 _E = Ellipsoids.WGS84 52 _Exact = True 53 _GeodSolve = _getenv(_PYGEODESY_GEODSOLVE_, _PYGEODESY_GEODSOLVE_) 54 _invokation = 0 55 _prec = Precision_(prec=DIG) 56 _reverse2 = False 57 _status = None 58 _text_True = dict() if _sys_version_info2 < (3, 7) else dict(text=True) 59 _unroll = False 60 _verbose = False 61 62 @Property_RO 63 def a(self): 64 '''Get the I{equatorial} radius, semi-axis (C{meter}). 65 ''' 66 return self.ellipsoid.a 67 68 @Property_RO 69 def _b_option(self): 70 return ('-b',) if self.reverse2 else () 71 72 @Property_RO 73 def _cmdBasic(self): 74 '''(INTERNAL) Get the C{GeodSolve} basic cmd (C{tuple}). 75 ''' 76 return (self.GeodSolve,) + self._b_option \ 77 + self._e_option \ 78 + self._E_option \ 79 + self._p_option \ 80 + self._u_option + ('-f',) 81 82 @Property_RO 83 def _e_option(self): 84 if self.ellipsoid is Ellipsoids.WGS84: 85 return () # default 86 a, f = strs((self.a, self.f), fmt=Fmt.F, prec=DIG + 3) # not .G! 87 return ('-e', a, f) 88 89 @Property_RO 90 def _E_option(self): 91 return ('-E',) if self.Exact else () 92 93 @Property_RO 94 def ellipsoid(self): 95 '''Get the ellipsoid (C{Ellipsoid}). 96 ''' 97 return self._E 98 99 @Property 100 def Exact(self): 101 '''Get the C{GeodesicExact} usage (C{bool}). 102 ''' 103 return self._Exact 104 105 @Exact.setter # PYCHOK setter! 106 def Exact(self, Exact): 107 '''Set the C{GeodesicExact} usage (C{bool}). 108 109 @arg Exact: If C{True} use C{GeodesicExact}, 110 otherwise use C{Geodesic} (C{bool}). 111 ''' 112 Exact = bool(Exact) 113 self._update(Exact != self.Exact) 114 self._Exact = Exact 115 116 @Property_RO 117 def f(self): 118 '''Get the ellipsoid's I{flattening} (C{float}), M{(a - b) / a}, C{0} for spherical, negative for prolate. 119 ''' 120 return self.ellipsoid.f 121 122 def _GDictInvoke(self, cmd, floats, *args): 123 '''(INTERNAL) Invoke C{GeodSolve}, return C{GDict}. 124 ''' 125 i = fstr(args, prec=DIG, fmt=Fmt.F, sep=_SPACE_) if args else None # not Fmt.G! 126 t = self._invoke(cmd, stdin=i).lstrip().split() # 12-/+ tuple 127 if len(t) > _len_N: # instrumented? 128 # unzip the name=value pairs to names and values 129 n, v = zip(*(p.split('=') for p in t[:-_len_N])) 130 v += tuple(t[-_len_N:]) 131 n += GeodSolve12Tuple._Names_ 132 else: 133 n, v = GeodSolve12Tuple._Names_, t 134 if self.verbose: # PYCHOK no cover 135 self._print(_COMMASPACE_.join(map(Fmt.EQUAL, n, map(fstrzs, v)))) 136 if floats: 137 v = map(float, v) 138 return GDict(zip(n, v)) 139 140 @Property 141 def GeodSolve(self): 142 '''Get the U{GeodSolve<https://GeographicLib.SourceForge.io/html/GeodSolve.1.html>} 143 executable (C{filename}). 144 ''' 145 return self._GeodSolve 146 147 @GeodSolve.setter # PYCHOK setter! 148 def GeodSolve(self, path): 149 '''Set the U{GeodSolve<https://GeographicLib.SourceForge.io/html/GeodSolve.1.html>} 150 executable (C{filename}). 151 152 @arg path: The (fully qualified) path to the C{GeodSolve} executable (C{str}). 153 154 @raise GeodesicError: Invalid B{C{path}}, B{C{path}} doesn't exist or 155 isn't the C{GeodSolve} executable. 156 ''' 157 hold = self.GeodSolve 158 self._update(path != hold) 159 self._GeodSolve = path 160 try: 161 self.version # test path and ... 162 if self.status: # ... return code 163 raise GeodesicError(GeodSolve=path, status=self.status, txt=_not_(_0_)) 164 hold = path 165 finally: # restore in case of error 166 self._update(hold != self.GeodSolve) 167 self._GeodSolve = hold 168 169 @property_RO 170 def invokation(self): 171 '''Get the most recent C{GeodSolve} invokation number (C{int}). 172 ''' 173 return self._invokation 174 175 def invoke(self, *options, **stdin): 176 '''Invoke the C{GeodSolve} executable and return the result. 177 178 @arg options: No, one or several C{GeodSolve} command line 179 options (C{str}s). 180 @kwarg stdin: Optional input to pass to C{GeodSolve.stdin} (C{str}). 181 182 @return: The C{GeodSolve.stdout} and C{.stderr} output (C{str}). 183 184 @raise GeodesicError: On any error, including a non-zero return 185 code from C{GeodSolve}. 186 187 @note: The C{GeodSolve} return code is in L{status}. 188 ''' 189 c = (self.GeodSolve,) + map2(str, options) 190 i = stdin.get(_stdin_, None) 191 r = self._invoke(c, stdin=i) 192 s = self.status 193 if s: 194 raise GeodesicError(cmd=_cmd_stdin_(c, i), status=s, 195 txt=_not_(_0_)) 196 if self.verbose: # PYCHOK no cover 197 self._print(r) 198 return r 199 200 def _invoke(self, cmd, stdin=None): 201 '''(INTERNAL) Invoke the C{GeodSolve} executable, with the 202 given B{C{cmd}} line and optional input to B{C{stdin}}. 203 ''' 204 self._invokation += 1 205 self._status = t = None 206 if self.verbose: # PYCHOK no cover 207 t = _cmd_stdin_(cmd, stdin) 208 self._print(t) 209 try: 210 p = _Popen(cmd, creationflags=0, 211 # executable =sys.executable, 212 # shell =True, 213 stdin =_PIPE, 214 stdout =_PIPE, # PYCHOK kwArgs 215 stderr =_STDOUT, 216 **self._text_True) 217 # invoke and write to stdin 218 r = p.communicate(stdin)[0] 219 if isinstance(r, bytes): # Python 3+ 220 r = r.decode('utf-8') 221 222 if len(r) < 6 or r[:5] in ('Error', 'ERROR'): 223 raise ValueError(r) 224 225 self._status = p.returncode 226 except (IOError, OSError, TypeError, ValueError) as x: 227 raise GeodesicError(cmd=t or _cmd_stdin_(cmd, stdin), 228 txt=str(x)) 229 return r.strip() 230 231 @Property_RO 232 def _p_option(self): 233 return '-p', str(self.prec - 5) # -p is distance prec 234 235 @Property 236 def prec(self): 237 '''Get the precision, number of decimal digits (C{int}). 238 ''' 239 return self._prec 240 241 @prec.setter # PYCHOK setter! 242 def prec(self, prec): 243 '''Set the precision for C{angles} in C{degrees}, like C{lat}, 244 C{lon}, C{azimuth} and C{arc}. 245 246 @arg prec: Number of decimal digits (C{int}, C{0}..L{DIG}). 247 248 @note: The precision for C{distance = B{prec} - 5} or up to 249 10 decimal digits for C{nanometer} and for C{area = 250 B{prec} - 12} or at most C{millimeter} I{squared}. 251 ''' 252 prec = Precision_(prec=prec, high=DIG) 253 self._update(prec != self.prec) 254 self._prec = prec 255 256 def _print(self, line): # PYCHOK no cover 257 '''(INTERNAL) Print a status line. 258 ''' 259 if self.status is not None: 260 line = _SPACE_(line, Fmt.PAREN(self.status)) 261 printf('%s %d: %s', self.named2, self.invokation, line) 262 263 @Property 264 def reverse2(self): 265 '''Get the C{azi2} direction (C{bool}). 266 ''' 267 return self._reverse2 268 269 @reverse2.setter # PYCHOK setter! 270 def reverse2(self, reverse2): 271 '''Set the direction for C{azi2} (C{bool}). 272 273 @arg reverse2: If C{True} reverse C{azi2} (C{bool}). 274 ''' 275 reverse2 = bool(reverse2) 276 self._update(reverse2 != self.reverse2) 277 self._reverse2 = reverse2 278 279 @property_RO 280 def status(self): 281 '''Get the most recent C{GeodSolve} return code (C{int}, C{str}) 282 or C{None}. 283 ''' 284 return self._status 285 286 def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature 287 '''Return this C{GeodesicSolve} as string. 288 289 @kwarg prec: The C{float} precision, number of decimal digits (0..9). 290 Trailing zero decimals are stripped for B{C{prec}} values 291 of 1 and above, but kept for negative B{C{prec}} values. 292 @kwarg sep: Optional separator to join (C{str}). 293 294 @return: GeodesicSolve items (C{str}). 295 ''' 296 d = dict(ellipsoid=self.ellipsoid, GeodSolve=self.GeodSolve, 297 invokation=self.invokation, status=self.status) 298 return sep.join(pairs(d, prec=prec)) 299 300 @Property 301 def unroll(self): 302 '''Get the C{lon2} unroll'ing (C{bool}). 303 ''' 304 return self._unroll 305 306 @unroll.setter # PYCHOK setter! 307 def unroll(self, unroll): 308 '''Set unroll'ing for C{lon2} (C{bool}). 309 310 @arg unroll: If C{True} unroll C{lon2}, 311 otherwise don't (C{bool}). 312 ''' 313 unroll = bool(unroll) 314 self._update(unroll != self.unroll) 315 self._unroll = unroll 316 317 @Property_RO 318 def _u_option(self): 319 return '-u' if self.unroll else () 320 321 @Property 322 def verbose(self): 323 '''Get the C{verbose} option (C{bool}). 324 ''' 325 return self._verbose 326 327 @verbose.setter # PYCHOK setter! 328 def verbose(self, verbose): 329 '''Set the C{verbose} option. 330 331 @arg verbose: Print a message around each 332 C{GeodSolve} invokation (C{bool}). 333 ''' 334 self._verbose = bool(verbose) 335 336 @property_RO 337 def version(self): 338 '''Get the result of C{"GeodSolve --version"}. 339 ''' 340 return self.invoke('--version') 341 342 343class GeodesicSolve(_GeodesicSolveBase): 344 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/html/GeodSolve.1.html>} 345 as an C{Exact} version of I{Karney}'s Python class U{Geodesic<https://GeographicLib.SourceForge.io/html/ 346 python/code.html#geographiclib.geodesic.Geodesic>}. 347 348 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully 349 qualified) path to the C{GeodSolve} executable. 350 351 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve} 352 executable for I{every} method call. 353 ''' 354 def __init__(self, a_ellipsoid=Ellipsoids.WGS84, f=None, name=NN): 355 '''New L{GeodesicSolve} instance. 356 357 @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{datum}), or 358 the equatorial radius of the ellipsoid (C{meter}). 359 @arg f: The flattening of the ellipsoid (C{scalar}) if B{C{a_ellipsoid}} 360 is specified as C{meter}. 361 @kwarg name: Optional name (C{str}). 362 ''' 363 if a_ellipsoid in (GeodesicSolve._E, None): 364 pass # ignore f, default WGS84 365 elif f is None: 366 self._E = _ellipsoidal_datum(a_ellipsoid, name=name, raiser=True).ellipsoid 367 else: 368 self._E = Ellipsoid2(a_ellipsoid, f, name=name) 369 370 if name: 371 self.name = name 372 373 def Area(self, polyline=False, name=NN): 374 '''Set up an L{GeodesicAreaExact} to compute area and 375 perimeter of a polygon. 376 377 @kwarg polyline: If C{True} perimeter only, otherwise 378 area and perimeter (C{bool}). 379 @kwarg name: Optional name (C{str}). 380 381 @return: A L{GeodesicAreaExact} instance. 382 383 @note: The B{C{debug}} setting is passed as C{verbose} 384 to the returned L{GeodesicAreaExact} instance. 385 ''' 386 from pygeodesy.geodesicx.gxarea import GeodesicAreaExact 387 gaX = GeodesicAreaExact(self, polyline=polyline, 388 name=name or self.name) 389 if self.verbose or self.debug: # PYCHOK no cover 390 gaX.verbose = True 391 return gaX 392 393 Polygon = Area # for C{geographiclib} compatibility 394 395 @Property_RO 396 def _cmdDirect(self): 397 '''(INTERNAL) Get the C{GeodSolve} I{Direct} cmd (C{tuple}). 398 ''' 399 return self._cmdBasic 400 401 @Property_RO 402 def _cmdInverse(self): 403 '''(INTERNAL) Get the C{GeodSolve} I{Inverse} cmd (C{tuple}). 404 ''' 405 return self._cmdBasic + ('-i',) 406 407 def Direct(self, lat1, lon1, azi1, s12, *unused): 408 '''Return the C{Direct} result. 409 ''' 410 return self._GDictInvoke(self._cmdDirect, True, lat1, lon1, azi1, s12) 411 412 def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask 413 '''Return the destination lat, lon and reverse azimuth 414 (final bearing) in C{degrees}. 415 416 @return: L{Destination3Tuple}C{(lat, lon, final)}. 417 ''' 418 d = self._GDictInvoke(self._cmdDirect, False, lat1, lon1, azi1, s12) 419 return Destination3Tuple(float(d.lat2), float(d.lon2), wrap360(d.azi2)) 420 421 def _GDictDirect(self, lat, lon, azi, arcmode, s12_a12, *unused): # for .geodesicx.gxarea 422 '''(INTERNAL) Get C{_GenDirect}-like result as C{GDict}. 423 ''' 424 if arcmode: 425 raise GeodesicError(arcmode=arcmode, txt=str(NotImplemented)) 426 return self._GDictInvoke(self._cmdDirect, True, lat, lon, azi, s12_a12) 427 428 def _GDictInverse(self, lat1, lon1, lat2, lon2, *unused): # for .geodesicx.gxarea 429 '''(INTERNAL) Get C{_GenInverse}-like result as C{GDict}, but 430 I{without} C{_SALPs_CALPs_}. 431 ''' 432 return self._GDictInvoke(self._cmdInverse, True, lat1, lon1, lat2, lon2) 433 434 def Inverse(self, lat1, lon1, lat2, lon2, *unused): 435 '''Return the C{Inverse} result. 436 ''' 437 return self._GDictInvoke(self._cmdInverse, True, lat1, lon1, lat2, lon2) 438 439 def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False): 440 '''Return the non-negative, I{angular} distance in C{degrees}. 441 ''' 442 # see .FrechetKarney.distance, .HausdorffKarney._distance 443 # and .HeightIDWkarney._distances 444 _, lon2 = unroll180(lon1, lon2, wrap=wrap) # self.LONG_UNROLL 445 d = self._GDictInvoke(self._cmdInverse, False, lat1, lon1, lat2, lon2) 446 # XXX self.DISTANCE needed for 'a12'? 447 return abs(float(d.a12)) 448 449 def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask 450 '''Return the distance in C{meter} and the forward and 451 reverse azimuths (initial and final bearing) in C{degrees}. 452 453 @return: L{Distance3Tuple}C{(distance, initial, final)}. 454 ''' 455 d = self._GDictInvoke(self._cmdInverse, False, lat1, lon1, lat2, lon2) 456 return Distance3Tuple(float(d.s12), wrap360(d.azi1), wrap360(d.azi2)) 457 458 def Line(self, lat1, lon1, azi1, caps=Caps.ALL): 459 '''Set up an L{GeodesicLineSolve} to compute several points 460 on a single geodesic. 461 462 @arg lat1: Latitude of the first point (C{degrees}). 463 @arg lon1: Longitude of the first point (C{degrees}). 464 @arg azi1: Azimuth at the first point (compass C{degrees}). 465 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying 466 the capabilities the L{GeodesicLineSolve} instance 467 should possess, always C{Caps.ALL}. 468 469 @return: A L{GeodesicLineSolve} instance. 470 471 @note: If the point is at a pole, the azimuth is defined by keeping 472 B{C{lon1}} fixed, writing C{B{lat1} = ±(90 − ε)}, and taking 473 the limit C{ε → 0+}. 474 475 @see: C++ U{GeodesicExact.Line 476 <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1GeodesicExact.html>} 477 and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/html/python/code.html>}. 478 ''' 479 return GeodesicLineSolve(self, lat1, lon1, azi1, caps=caps, name=self.name) 480 481 _Line = Line 482 483 484class GeodesicLineSolve(_GeodesicSolveBase): 485 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/html/GeodSolve.1.html>} 486 as an C{Exact} version of I{Karney}'s Python class U{GeodesicLine<https://GeographicLib.SourceForge.io/html/ 487 python/code.html#geographiclib.geodesicline.GeodesicLine>}. 488 489 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully 490 qualified) path to the C{GeodSolve} executable. 491 492 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve} 493 executable for I{every} method call. 494 ''' 495 _caps = 0 496 _gS = None # Solve only 497 _lla1 = () 498 499 def __init__(self, geodesic, lat1, lon1, azi1, caps=Caps.ALL, name=NN): 500 '''New L{GeodesicLineSolve} instance, allowing points to be found along 501 a geodesic starting at C{(B{lat1}, B{lon1})} with azimuth B{C{azi1}}. 502 503 @arg geodesic: The geodesic to use (L{GeodesicSolve}). 504 @arg lat1: Latitude of the first point (C{degrees}). 505 @arg lon1: Longitude of the first point (C{degrees}). 506 @arg azi1: Azimuth at the first points (compass C{degrees}). 507 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying 508 the capabilities the L{GeodesicLineSolve} instance 509 should possess, always C{Caps.ALL}. 510 @kwarg name: Optional name (C{str}). 511 512 @raise TypeError: Invalid B{C{geodesic}}. 513 ''' 514 _xinstanceof(GeodesicSolve, geodesic=geodesic) 515 516 self._gS = gS = geodesic 517 self._lla1 = lat1, lon1, azi1 518 self._caps = caps | Caps._LINE 519 520 n = name or gS.name 521 if n: 522 self.name = n 523 524 self._debug = gS._debug 525 526 self.Exact = gS.Exact 527 self.prec = gS.prec 528 self.reverse2 = gS.reverse2 529 self.unroll = gS.unroll 530 self.verbose = gS.verbose 531 try: 532 self.GeodSolve = gS.GeodSolve 533 except GeodesicError: 534 pass 535 536 def ArcPosition(self, a12, *unused): 537 '''Find the position on the line given B{C{a12}}. 538 539 @arg a12: Spherical arc length from the first point to the 540 second point (C{degrees}). 541 542 @return: A C{dict} with 12 items C{lat1, lon1, azi1, lat2, lon2, 543 azi2, m12, a12, s12, M12, M21, S12}. 544 ''' 545 return self._GDictInvoke(self._cmdArc, True, a12) 546 547 @Property_RO 548 def azi1(self): 549 '''Get the azimuth at the first point (compass C{degrees}). 550 ''' 551 return self._lla1[2] 552 553 @Property_RO 554 def azi1_sincos2(self): 555 '''Get the sine and cosine of the first point's azimuth (2-tuple C{(sin, cos)}). 556 ''' 557 return sincos2d(self.azi1) 558 559 @Property_RO 560 def caps(self): 561 '''Get the capabilities (bit-or'ed C{Caps}). 562 ''' 563 return self._caps 564 565 def caps_(self, caps): 566 '''Check the available capabilities. 567 568 @arg caps: Bit-or'ed combination of L{Caps} values 569 for all capabilities to be checked. 570 571 @return: C{True} if I{all} B{C{caps}} are available, 572 C{False} otherwise (C{bool}). 573 ''' 574 return _all_caps(self.caps, caps) 575 576 @Property_RO 577 def _cmdArc(self): 578 '''(INTERNAL) Get the C{GeodSolve} I{-a -L} cmd (C{tuple}). 579 ''' 580 return self._cmdDistance + ('-a',) 581 582 @Property_RO 583 def _cmdDistance(self): 584 '''(INTERNAL) Get the C{GeodSolve} I{-L} cmd (C{tuple}). 585 ''' 586 return self._cmdBasic + ('-L',) + strs(self._lla1, prec=DIG, fmt=Fmt.F) 587 588 @Property_RO 589 def ellipsoid(self): 590 '''Get the ellipsoid (C{Ellipsoid}). 591 ''' 592 return self._gS.ellipsoid 593 594 @Property_RO 595 def lat1(self): 596 '''Get the latitude of the first point (C{degrees}). 597 ''' 598 return self._lla1[0] 599 600 @Property_RO 601 def lon1(self): 602 '''Get the longitude of the first point (C{degrees}). 603 ''' 604 return self._lla1[1] 605 606 def Position(self, s12, *unused): 607 '''Find the position on the line given B{C{s12}}. 608 609 @arg s12: Distance from the first point to the second C({meter}). 610 611 @return: A C{dict} with 12 items C{lat1, lon1, azi1, lat2, lon2, 612 azi2, m12, a12, s12, M12, M21, S12}, possibly C{a12=NAN}. 613 ''' 614 return self._GDictInvoke(self._cmdDistance, True, s12) 615 616 617__all__ += _ALL_DOCS(_GeodesicSolveBase) 618 619if __name__ == '__main__': 620 621 gS = GeodesicSolve(name='Test') 622 if gS.GeodSolve in (_PYGEODESY_GEODSOLVE_, None): # not set 623 gS.GeodSolve = '/opt/local/Cellar/geographiclib/1.51/bin/GeodSolve' # HomeBrew 624 # gS.verbose = True 625 printf('version: %s', gS.version) 626 627 r = gS.Direct(40.6, -73.8, 51, 5.5e6) 628 printf('Direct: %r', r, nl=1) # GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0) 629 printf('Direct3: %r', gS.Direct3(40.6, -73.8, 51, 5.5e6)) # Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397) 630 631 printf('Inverse: %r', gS.Inverse( 40.6, -73.8, 51.6, -0.5), nl=1) # GDict(M12=0.64473, M21=0.645046, S12=40041368848742.53125, a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, s12=5551759.400319) 632 printf('Inverse1: %r', gS.Inverse1(40.6, -73.8, 51.6, -0.5)) # 49.94131021789904 633 printf('Inverse3: %r', gS.Inverse3(40.6, -73.8, 51.6, -0.5)) # Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777) 634 635 glS = GeodesicLineSolve(gS, 40.6, -73.8, 51) 636 p = glS.Position(5.5e6) 637 printf('Position: %s %r', p == r, p, nl=1) 638 p = glS.ArcPosition(49.475527) 639 printf('ArcPosition: %s %r', p == r, p) 640 641# % python3 -m pygeodesy.geodsolve 642# version: /opt/local/Cellar/geographiclib/1.51/bin/GeodSolve: GeographicLib version 1.51 643# 644# Direct: GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0) 645# Direct3: Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397) 646# 647# Inverse: GDict(M12=0.64473, M21=0.645046, S12=40041368848742.53125, a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, s12=5551759.400319) 648# Inverse1: 49.94131021789904 649# Inverse3: Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777) 650# 651# Position: True GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0) 652# ArcPosition: False GDict(M12=0.650911, M21=0.651229, S12=39735074737272.734375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, m12=4844148.669561, s12=5499999.948497) 653 654# **) MIT License 655# 656# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved. 657# 658# Permission is hereby granted, free of charge, to any person obtaining a 659# copy of this software and associated documentation files (the "Software"), 660# to deal in the Software without restriction, including without limitation 661# the rights to use, copy, modify, merge, publish, distribute, sublicense, 662# and/or sell copies of the Software, and to permit persons to whom the 663# Software is furnished to do so, subject to the following conditions: 664# 665# The above copyright notice and this permission notice shall be included 666# in all copies or substantial portions of the Software. 667# 668# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 669# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 670# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 671# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 672# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 673# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 674# OTHER DEALINGS IN THE SOFTWARE. 675