Coverage for pygeodesy/geodsolve.py: 98%

86 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-25 15:01 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Wrapper to invoke I{Karney}'s U{GeodSolve 

5<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} utility 

6as an (exact) geodesic, but intended I{mainly for testing purposes}. 

7 

8Set env variable C{PYGEODESY_GEODSOLVE} to the (fully qualified) path of 

9the C{GeodSolve} executable or use property L{GeodesicSolve.GeodSolve 

10<geodsolve._GeodesicSolveBase.GeodSolve>}. 

11''' 

12 

13from pygeodesy.basics import _xinstanceof # typename 

14from pygeodesy.constants import NAN, _0_0 

15# from pygeodesy.errors import GeodesicError # from .karney 

16# from pygeodesy.geodesicx import GeodesicAreaExact # _MODS 

17from pygeodesy.interns import _a12_, _azi1_, _azi2_, _DMAIN_, _lat1_, \ 

18 _lat2_, _lon1_, _lon2_, _m12_, _M12_, \ 

19 _M21_, NN, _s12_, _S12_, _UNDER_ 

20from pygeodesy.karney import Caps, _GTuple, _sincos2d, _Xables, _Azi, \ 

21 _Deg, GeodesicError, _Lat, _Lon, _M, _M2 

22from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS 

23from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple, \ 

24 _Pass 

25from pygeodesy.props import Property, Property_RO, property_RO 

26from pygeodesy.solveBase import _SolveGDictBase, _SolveGDictLineBase 

27# from pygeodesy.units import Azimuth as _Azi, Degrees as _Deg, \ 

28# Meter as _M, Meter2 as _M2 # from .karney 

29from pygeodesy.utily import _unrollon, _Wrap, wrap360 

30 

31__all__ = _ALL_LAZY.geodsolve 

32__version__ = '25.12.31' 

33 

34 

35class GeodSolve12Tuple(_GTuple): 

36 '''12-Tuple C{(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12)} with 

37 angles C{lat1}, C{lon1}, C{azi1}, C{lat2}, C{lon2} and C{azi2} and arc C{a12} all in 

38 C{degrees}, initial C{azi1} and final C{azi2} forward azimuths in bearings, degrees 

39 from North, distance C{s12} and reduced length C{m12} in C{meter}, area C{S12} in 

40 C{meter} I{squared} and geodesic scale factors C{M12} and C{M21}, both C{scalar}, see 

41 U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}. 

42 ''' 

43 # from GeodSolve --help option -f ... lat1 lon1 azi1 lat2 lon2 azi2 s12 a12 m12 M12 M21 S12 

44 _Names_ = (_lat1_, _lon1_, _azi1_, _lat2_, _lon2_, _azi2_, _s12_, _a12_, _m12_, _M12_, _M21_, _S12_) 

45 _Units_ = (_Lat, _Lon, _Azi, _Lat, _Lon, _Azi, _M, _Deg, _Pass, _Pass, _Pass, _M2) 

46 

47 

48class _GeodesicSolveBase(_SolveGDictBase): 

49 '''(INTERNAL) Base class for L{GeodesicSolve} and L{GeodesicLineSolve}. 

50 ''' 

51 _Error = GeodesicError 

52 _Names_Direct = _Names_Distance = \ 

53 _Names_Inverse = GeodSolve12Tuple._Names_ 

54 _Xable_name = _Xables.GeodSolve.__name__ # typename 

55 _Xable_path = _Xables.GeodSolve() 

56 

57 @Property_RO 

58 def _b_option(self): 

59 return ('-b',) if self.reverse2 else () 

60 

61 @Property_RO 

62 def _cmdBasic(self): 

63 '''(INTERNAL) Get the basic C{GeodSolve} cmd (C{tuple}). 

64 ''' 

65 return (self.GeodSolve, '-f') + (self._b_option + 

66 self._e_option + 

67 self._E_option + 

68 self._p_option + 

69 self._u_option) 

70 

71 @Property 

72 def GeodSolve(self): 

73 '''Get the U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} 

74 executable (C{filename}). 

75 ''' 

76 return self._Xable_path 

77 

78 @GeodSolve.setter # PYCHOK setter! 

79 def GeodSolve(self, path): 

80 '''Set the U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} 

81 executable (C{filename}), the (fully qualified) path to the C{GeodSolve} executable. 

82 

83 @raise GeodesicError: Invalid B{C{path}}, B{C{path}} doesn't exist or 

84 isn't the C{GeodSolve} executable. 

85 ''' 

86 self._setXable(path) 

87 

88 @Property_RO 

89 def _u_option(self): 

90 return ('-u',) if self.unroll else () 

91 

92 

93class GeodesicSolve(_GeodesicSolveBase): 

94 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} 

95 as an C{Exact} version of I{Karney}'s Python class U{Geodesic<https://GeographicLib.SourceForge.io/C++/doc/ 

96 python/code.html#geographiclib.geodesic.Geodesic>}. 

97 

98 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully 

99 qualified) path to the C{GeodSolve} executable. 

100 

101 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve} 

102 executable for I{every} method call. 

103 ''' 

104 

105 def Area(self, polyline=False, **name): 

106 '''Set up a L{GeodesicAreaExact} to compute area and perimeter 

107 of a polygon. 

108 

109 @kwarg polyline: If C{True}, compute the perimeter only, otherwise 

110 perimeter and area (C{bool}). 

111 @kwarg name: Optional C{B{name}=NN} (C{str}). 

112 

113 @return: A L{GeodesicAreaExact} instance. 

114 

115 @note: The B{C{debug}} setting is passed as C{verbose} 

116 to the returned L{GeodesicAreaExact} instance. 

117 ''' 

118 gaX = _MODS.geodesicx.GeodesicAreaExact(self, polyline=polyline, **name) 

119 if self.verbose or self.debug: # PYCHOK no cover 

120 gaX.verbose = True 

121 return gaX 

122 

123 Polygon = Area # for C{geographiclib} compatibility 

124 

125 def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask 

126 '''Return the destination lat, lon and reverse azimuth (final bearing) 

127 in C{degrees}. 

128 

129 @return: L{Destination3Tuple}C{(lat, lon, final)}. 

130 ''' 

131 r = self._GDictDirect(lat1, lon1, azi1, False, s12, floats=False) 

132 return Destination3Tuple(float(r.lat2), float(r.lon2), wrap360(r.azi2), 

133 iteration=r._iteration) 

134 

135 def _DirectLine(self, ll1, azi12, **caps_name): # PYCHOK no cover 

136 '''(INTERNAL) Short-cut version. 

137 ''' 

138 return self.DirectLine(ll1.lat, ll1.lon, azi12, **caps_name) 

139 

140 def DirectLine(self, lat1, lon1, azi1, caps=Caps.ALL, **name): 

141 '''Set up a L{GeodesicLineSolve} to compute several points 

142 on a single geodesic. 

143 

144 @arg lat1: Latitude of the first point (C{degrees}). 

145 @arg lon1: Longitude of the first point (C{degrees}). 

146 @arg azi1: Azimuth at the first point (compass C{degrees}). 

147 @kwarg caps: Desired capabilities for the L{GeodesicLineSolve} instance. 

148 @kwarg name: Optional C{B{name}=NN} (C{str}). 

149 

150 @return: A L{GeodesicLineSolve} instance. 

151 

152 @note: If the point is at a pole, the azimuth is defined by keeping 

153 B{C{lon1}} fixed, writing C{B{lat1} = ±(90 − ε)}, and taking 

154 the limit C{ε → 0+}. 

155 

156 @see: C++ U{GeodesicExact.Line 

157 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} 

158 and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}. 

159 ''' 

160 return GeodesicLineSolve(self, lat1, lon1, azi1, caps=caps, **name) 

161 

162 Line = ArcDirectLine = DirectLine 

163 

164 def _Inverse(self, ll1, ll2, wrap, **outmask): # PYCHOK no cover 

165 '''(INTERNAL) Short-cut version, see .ellipsoidalBaseDI.intersecant2. 

166 ''' 

167 if wrap: 

168 ll2 = _unrollon(ll1, _Wrap.point(ll2)) 

169 return self.Inverse(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **outmask) 

170 

171 def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask 

172 '''Return the distance in C{meter} and the forward and 

173 reverse azimuths (initial and final bearing) in C{degrees}. 

174 

175 @return: L{Distance3Tuple}C{(distance, initial, final)}. 

176 ''' 

177 r = self._GDictInverse(lat1, lon1, lat2, lon2, floats=False) 

178 return Distance3Tuple(float(r.s12), wrap360(r.azi1), wrap360(r.azi2), 

179 iteration=r._iteration) 

180 

181 def _InverseLine(self, ll1, ll2, wrap, **caps_name): # PYCHOK no cover 

182 '''(INTERNAL) Short-cut version. 

183 ''' 

184 if wrap: 

185 ll2 = _unrollon(ll1, _Wrap.point(ll2)) 

186 return self.InverseLine(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **caps_name) 

187 

188 def InverseLine(self, lat1, lon1, lat2, lon2, caps=Caps.ALL, **name): # PYCHOK no cover 

189 '''Set up a L{GeodesicLineSolve} to compute several points 

190 on a single geodesic. 

191 

192 @arg lat1: Latitude of the first point (C{degrees}). 

193 @arg lon1: Longitude of the first point (C{degrees}). 

194 @arg lat2: Latitude of the second point (C{degrees}). 

195 @arg lon2: Longitude of the second point (C{degrees}). 

196 @kwarg caps: Desired capabilities for the L{GeodesicLineSolve} instance. 

197 @kwarg name: Optional C{B{name}=NN} (C{str}). 

198 

199 @return: A L{GeodesicLineSolve} instance. 

200 

201 @note: Both B{C{lat1}} and B{C{lat2}} should in the range C{[-90, +90]}. 

202 

203 @see: C++ U{GeodesicExact.InverseLine 

204 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and 

205 Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}. 

206 ''' 

207 r = self.Inverse(lat1, lon1, lat2, lon2) 

208 gl = GeodesicLineSolve(self, lat1, lon1, r.azi1, caps=caps, **name) 

209 gl._a13 = r.a12 # gl.SetArc(r.a12) 

210 gl._s13 = r.s12 # gl.SetDistance(r.s12) 

211 return gl 

212 

213 def toStr(self, **prec_sep_other): # PYCHOK signature 

214 '''Return this C{GeodesicSolve} as string. 

215 

216 @kwarg prec_sep: See L{toStr<pygeodesy.solveBase._SolveBase.toStr>}. 

217 

218 @return: GeodesicSolve items (C{str}). 

219 ''' 

220 return _SolveGDictBase.toStr(self, GeodSolve=self.GeodSolve, **prec_sep_other) 

221 

222 

223class GeodesicLineSolve(_GeodesicSolveBase, _SolveGDictLineBase): 

224 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} 

225 as an C{Exact} version of I{Karney}'s Python class U{GeodesicLine<https://GeographicLib.SourceForge.io/C++/doc/ 

226 python/code.html#geographiclib.geodesicline.GeodesicLine>}. 

227 

228 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully 

229 qualified) path to the C{GeodSolve} executable. 

230 

231 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve} 

232 executable for I{every} method call. 

233 ''' 

234 _a13 = \ 

235 _s13 = NAN # see GeodesicSolve._InverseLine 

236 

237 def __init__(self, geodesic, lat1, lon1, azi1, caps=Caps.ALL, **name): 

238 '''New L{GeodesicLineSolve} instance, allowing points to be found along 

239 a geodesic starting at C{(B{lat1}, B{lon1})} with azimuth B{C{azi1}}. 

240 

241 @arg geodesic: The geodesic to use (L{GeodesicSolve}). 

242 @arg lat1: Latitude of the first point (C{degrees}). 

243 @arg lon1: Longitude of the first point (C{degrees}). 

244 @arg azi1: Azimuth at the first points (compass C{degrees}). 

245 @kwarg caps: Bit-or'ed combination of L{Caps<pygeodesy.karney.Caps>} values 

246 specifying the capabilities the L{GeodesicLineSolve} instance 

247 should possess, C{B{caps}=Caps.ALL} always. Include C{Caps.LINE_OFF} 

248 if updates to the B{C{geodesic}} should I{not be reflected} in this 

249 L{GeodesicLineSolve} instance. 

250 @kwarg name: Optional C{B{name}=NN} (C{str}). 

251 

252 @raise GeodesicError: Invalid path for the C{GeodSolve} executable or isn't the 

253 C{GeodSolve} executable, see property C{geodesic.GeodSolve}. 

254 

255 @raise TypeError: Invalid B{C{geodesic}}. 

256 ''' 

257 _xinstanceof(GeodesicSolve, geodesic=geodesic) 

258 if (caps & Caps.LINE_OFF): # copy to avoid updates 

259 geodesic = geodesic.copy(deep=False, name=_UNDER_(NN, geodesic.name)) # NOT _under! 

260 _SolveGDictLineBase.__init__(self, geodesic, lat1, lon1, caps, azi1=azi1, **name) 

261 try: 

262 self.GeodSolve = geodesic.GeodSolve # geodesic or copy of geodesic 

263 except GeodesicError: 

264 pass 

265 

266 @Property_RO 

267 def a13(self): 

268 '''Get the arc length to reference point 3 (C{degrees}). 

269 

270 @see: Methods L{Arc} and L{SetArc}. 

271 ''' 

272 return self._a13 

273 

274 def Arc(self): # PYCHOK no cover 

275 '''Return the arc length to reference point 3 (C{degrees} or C{NAN}). 

276 

277 @see: Method L{SetArc} and property L{a13}. 

278 ''' 

279 return self.a13 

280 

281 def ArcPosition(self, a12, outmask=Caps.STANDARD): # PYCHOK no cover 

282 '''Find the position on the line given B{C{a12}}. 

283 

284 @arg a12: Spherical arc length from the first point to the 

285 second point (C{degrees}). 

286 

287 @return: A C{GDict} with 12 items C{lat1, lon1, azi1, lat2, lon2, 

288 azi2, m12, a12, s12, M12, M21, S12}. 

289 ''' 

290 return self._GDictInvoke(self._cmdArc, self._Names_Distance, a12)._unCaps(outmask) 

291 

292 @Property_RO 

293 def azi1(self): 

294 '''Get the azimuth at the first point (compass C{degrees}). 

295 ''' 

296 return self._lla1.azi1 

297 

298 azi12 = azi1 # like RhumbLineSolve 

299 

300 @Property_RO 

301 def azi1_sincos2(self): 

302 '''Get the sine and cosine of the first point's azimuth (2-tuple C{(sin, cos)}). 

303 ''' 

304 return _sincos2d(self.azi1) 

305 

306 azi12_sincos2 = azi1_sincos2 

307 

308 @Property_RO 

309 def _cmdArc(self): 

310 '''(INTERNAL) Get the C{GeodSolve} I{-a -L} cmd (C{tuple}). 

311 ''' 

312 return self._cmdDistance + ('-a',) 

313 

314 def Distance(self): 

315 '''Return the distance to reference point 3 (C{meter} or C{NAN}). 

316 ''' 

317 return self.s13 

318 

319 @property_RO 

320 def geodesic(self): 

321 '''Get the geodesic (L{GeodesicSolve}). 

322 ''' 

323 return self._solve # see .solveBase._SolveLineBase 

324 

325 def Intersecant2(self, lat0, lon0, radius, **kwds): # PYCHOK no cover 

326 '''B{Not implemented}, throws a C{NotImplementedError} always.''' 

327 self._notImplemented(lat0, lon0, radius, **kwds) 

328 

329 def PlumbTo(self, lat0, lon0, **kwds): # PYCHOK no cover 

330 '''B{Not implemented}, throws a C{NotImplementedError} always.''' 

331 self._notImplemented(lat0, lon0, **kwds) 

332 

333 def Position(self, s12, outmask=Caps.STANDARD): 

334 '''Find the position on the line given at distance B{C{s12}}. 

335 

336 @arg s12: Distance from the first point to the second (C{meter}). 

337 

338 @return: A C{GDict} with 12 items C{lat1, lon1, azi1, lat2, lon2, 

339 azi2, m12, a12, s12, M12, M21, S12}, possibly C{a12=NAN}. 

340 ''' 

341 return self._GDictInvoke(self._cmdDistance, self._Names_Distance, s12)._unCaps(outmask) 

342 

343 @Property_RO 

344 def s13(self): 

345 '''Get the distance to reference point 3 (C{meter} or C{NAN}). 

346 

347 @see: Methods L{Distance} and L{SetDistance}. 

348 ''' 

349 return self._s13 

350 

351 def SetArc(self, a13): # PYCHOK no cover 

352 '''Set reference point 3 in terms relative to the first point. 

353 

354 @arg a13: Spherical arc length from the first to the reference 

355 point (C{degrees}). 

356 

357 @return: The distance C{s13} (C{meter}) between the first and 

358 the reference point or C{NAN}. 

359 ''' 

360 if self._a13 != a13: 

361 self._a13 = a13 

362 self._s13 = self.ArcPosition(a13, outmask=Caps.DISTANCE).s12 # if a13 else _0_0 

363# _update_all(self) 

364 return self._s13 

365 

366 def SetDistance(self, s13): # PYCHOK no cover 

367 '''Set reference point 3 in terms relative to the first point. 

368 

369 @arg s13: Distance from the first to the reference point (C{meter}). 

370 

371 @return: The arc length C{a13} (C{degrees}) between the first and 

372 the reference point or C{NAN}. 

373 ''' 

374 if self._s13 != s13: 

375 self._s13 = s13 

376 self._a13 = self.Position(s13, outmask=Caps.DISTANCE).a12 if s13 else _0_0 

377# _update_all(self) 

378 return self._a13 # NAN for GeodesicLineExact without Cap.DISTANCE_IN 

379 

380 def toStr(self, **prec_sep_other): # PYCHOK signature 

381 '''Return this C{GeodesicLineSolve} as string. 

382 

383 @kwarg prec_sep: See L{toStr<pygeodesy.solveBase._SolveBase.toStr>}. 

384 

385 @return: GeodesicLineSolve items (C{str}). 

386 ''' 

387 return _SolveGDictLineBase.toStr(self, geodesic=self._solve, **prec_sep_other) 

388 

389 

390__all__ += _ALL_DOCS(_GeodesicSolveBase) 

391 

392if __name__ == _DMAIN_: 

393 

394 def _main(): 

395 from pygeodesy import printf 

396 from sys import argv 

397 

398 gS = GeodesicSolve(name='Test') 

399 gS.verbose = v = '--verbose' in argv # or '-v' in argv 

400 

401 if not _Xables.X_OK(gS.GeodSolve): # not set 

402 gS.GeodSolve = _Xables.GeodSolve(_Xables.bin_) 

403 printf('version: %s', gS.version, nt=v) 

404 

405 r = gS.Direct(40.6, -73.8, 51, 5.5e6) 

406 printf('Direct: %r', r) 

407 printf('Direct3: %r', gS.Direct3(40.6, -73.8, 51, 5.5e6), nt=v) 

408 

409 printf('Inverse: %r', gS.Inverse( 40.6, -73.8, 51.6, -0.5)) 

410 printf('Inverse1: %r', gS.Inverse1(40.6, -73.8, 51.6, -0.5)) 

411 printf('Inverse3: %r', gS.Inverse3(40.6, -73.8, 51.6, -0.5), nt=v) 

412 

413 glS = GeodesicLineSolve(gS, 40.6, -73.8, 51, name='LineTest') 

414 printf('Line: %r', glS) 

415 p = glS.Position(5.5e6) 

416 printf('Position: %r %s', p, p == r) 

417 p = glS.ArcPosition(49.475527) 

418 printf('ArcPosition: %r %s', p, p == r) 

419 

420 _main() 

421 

422# % python3 -m pygeodesy.geodsolve 

423 

424# version: /opt/local/bin/GeodSolve: GeographicLib version 2.7 

425# Direct: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, M12=0.650911, M21=0.651229, s12=5500000.0, S12=39735075134877.078125) 

426# Direct3: Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397) 

427# Inverse: GDict(a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, M12=0.64473, M21=0.645046, s12=5551759.400319, S12=40041368848742.53125) 

428# Inverse1: 49.94131021789904 

429# Inverse3: Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777) 

430# Line: GeodesicLineSolve(geodesic=GeodesicSolve(ellipsoid=Ellipsoid(name='WGS84', a=6378137, f=0.00335281, f_=298.25722356, b=6356752.31424518), GeodSolve='/opt/local/bin/GeodSolve', invokation=6, status=0), invokation=1, status=0) 

431# Position: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, s12=5500000.0) False 

432# ArcPosition: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, s12=5499999.948497) False 

433 

434 

435# % python3 -m pygeodesy.geodsolve --verbose 

436 

437# GeodesicSolve 'Test'@1: /opt/local/bin/GeodSolve --version (invoke) 

438# GeodesicSolve 'Test'@1: '/opt/local/bin/GeodSolve: GeographicLib version 2.7' (0, stdout/-err) 

439# GeodesicSolve 'Test'@1: /opt/local/bin/GeodSolve: GeographicLib version 2.7 (0) 

440# version: /opt/local/bin/GeodSolve: GeographicLib version 2.7 

441# 

442# GeodesicSolve 'Test'@2: /opt/local/bin/GeodSolve -f -E -p 10 \ 40.6 -73.8 51.0 5500000.0 (Direct) 

443# GeodesicSolve 'Test'@2: '40.600000000000001 -73.799999999999997 51.000000000000000 51.884564505606761 -1.141172861200843 107.189397162605871 5500000.0000000000 49.475527463251460 4844148.7031014860 0.65091056699808614 0.65122865892196569 39735075134877.078' (0, stdout/-err) 

444# GeodesicSolve 'Test'@2: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200843, azi2=107.189397162605871, s12=5500000.0, a12=49.47552746325146, m12=4844148.703101486, M12=0.65091056699808614, M21=0.65122865892196569, S12=39735075134877.078 (0) 

445# Direct: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, M12=0.650911, M21=0.651229, s12=5500000.0, S12=39735075134877.078125) 

446# GeodesicSolve 'Test'@3: /opt/local/bin/GeodSolve -f -E -p 10 \ 40.6 -73.8 51.0 5500000.0 (Direct3) 

447# GeodesicSolve 'Test'@3: '40.600000000000001 -73.799999999999997 51.000000000000000 51.884564505606761 -1.141172861200843 107.189397162605871 5500000.0000000000 49.475527463251460 4844148.7031014860 0.65091056699808614 0.65122865892196569 39735075134877.078' (0, stdout/-err) 

448# GeodesicSolve 'Test'@3: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200843, azi2=107.189397162605871, s12=5500000.0, a12=49.47552746325146, m12=4844148.703101486, M12=0.65091056699808614, M21=0.65122865892196569, S12=39735075134877.078 (0) 

449# Direct3: Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397) 

450# 

451# GeodesicSolve 'Test'@4: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.6 -73.8 51.6 -0.5 (Inverse) 

452# GeodesicSolve 'Test'@4: '40.600000000000001 -73.799999999999997 51.198882845579824 51.600000000000001 -0.500000000000000 107.821776735514248 5551759.4003186813 49.941310217899037 4877684.6027061967 0.64472969205948238 0.64504567852134398 40041368848742.531' (0, stdout/-err) 

453# GeodesicSolve 'Test'@4: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186813, a12=49.941310217899037, m12=4877684.6027061967, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0) 

454# Inverse: GDict(a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, M12=0.64473, M21=0.645046, s12=5551759.400319, S12=40041368848742.53125) 

455# GeodesicSolve 'Test'@5: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.6 -73.8 51.6 -0.5 (Inverse1) 

456# GeodesicSolve 'Test'@5: '40.600000000000001 -73.799999999999997 51.198882845579824 51.600000000000001 -0.500000000000000 107.821776735514248 5551759.4003186813 49.941310217899037 4877684.6027061967 0.64472969205948238 0.64504567852134398 40041368848742.531' (0, stdout/-err) 

457# GeodesicSolve 'Test'@5: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186813, a12=49.941310217899037, m12=4877684.6027061967, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0) 

458# Inverse1: 49.94131021789904 

459# GeodesicSolve 'Test'@6: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.6 -73.8 51.6 -0.5 (Inverse3) 

460# GeodesicSolve 'Test'@6: '40.600000000000001 -73.799999999999997 51.198882845579824 51.600000000000001 -0.500000000000000 107.821776735514248 5551759.4003186813 49.941310217899037 4877684.6027061967 0.64472969205948238 0.64504567852134398 40041368848742.531' (0, stdout/-err) 

461# GeodesicSolve 'Test'@6: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186813, a12=49.941310217899037, m12=4877684.6027061967, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0) 

462# Inverse3: Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777) 

463# 

464# Line: GeodesicLineSolve(geodesic=GeodesicSolve(ellipsoid=Ellipsoid(name='WGS84', a=6378137, f=0.00335281, f_=298.25722356, b=6356752.31424518), GeodSolve='/opt/local/bin/GeodSolve', invokation=6, status=0), invokation=1, status=0) 

465# Position: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, s12=5500000.0) False 

466# ArcPosition: GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, s12=5499999.948497) False 

467 

468# **) MIT License 

469# 

470# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved. 

471# 

472# Permission is hereby granted, free of charge, to any person obtaining a 

473# copy of this software and associated documentation files (the "Software"), 

474# to deal in the Software without restriction, including without limitation 

475# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

476# and/or sell copies of the Software, and to permit persons to whom the 

477# Software is furnished to do so, subject to the following conditions: 

478# 

479# The above copyright notice and this permission notice shall be included 

480# in all copies or substantial portions of the Software. 

481# 

482# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

483# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

484# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

485# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

486# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

487# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

488# OTHER DEALINGS IN THE SOFTWARE.