Coverage for pygeodesy/geod3solve.py: 91%

144 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{Geod3Solve 

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

6as a C{triaxial} geodesic, but intended I{mainly for testing purposes}. 

7 

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

9the C{Geod3Solve} executable or use property L{Geodesic3Solve.Geod3Solve 

10<geod3solve._Geodesic3SolveBase.Geod3Solve>}. 

11''' 

12 

13from pygeodesy.angles import Ang, Deg, isAng, hypot 

14from pygeodesy.basics import _xinstanceof, _MODS # typename 

15from pygeodesy.constants import _0_0, _0_5, _360_0, _over 

16# from pygeodesy.ellipses import Ellipse # _MODS 

17from pygeodesy.errors import GeodesicError, _xkwds_get 

18# from pygeodesy.fmath import hypot # from .angles 

19from pygeodesy.interns import NN, _a12_, _DMAIN_, _s12_, _UNDER_, _UNUSED_ 

20from pygeodesy.karney import Caps, _GTuple, _Xables, sincos2d 

21# from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS 

22from pygeodesy.props import Property, Property_RO, property_RO 

23from pygeodesy.solveBase import _Solve3Base, _ALL_DOCS, _ALL_LAZY 

24from pygeodesy.triaxials.triaxial3 import Triaxial3, Triaxial3s 

25from pygeodesy.units import Degrees, Meter 

26# from pygeodesy.utily import sincos2d # from .karney 

27 

28__all__ = _ALL_LAZY.geod3solve 

29__version__ = '26.02.12' 

30 

31_Triaxial3_WGS84 = Triaxial3s.WGS84_3r # a=6378172, b=6378102, c=6356752 

32 

33 

34class Geodesic3Error(GeodesicError): 

35 '''Error raised for issues in L{geod3solve<pygeodesy.geod3solve>}. 

36 ''' 

37 pass 

38 

39 

40class Geod3Solve8Tuple(_GTuple): 

41 '''8-Tuple C{(bet1, omg1, alp1, bet2, omg2, alp2, s12, a12)} with C{ellipsoidal} 

42 latitudes C{bet1} and C{bet2}, C{ellipsoidal} longitudes C{omg1} and C{omg2}, 

43 forward azimuths C{alp1} and C{alp2} in bearings from North, distance C{s12} in 

44 C{meter}, conventionally and I{approximate} arc length C{a12} in degrees, see 

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

46 ''' 

47 # from Geod3Solve --help option -f ... bet1 omg1 alp1 bet2 omg2 alp2 s12 

48 _Names_ = ('bet1', 'omg1', 'alp1', 'bet2', 'omg2', 'alp2', _s12_, _a12_) 

49 _Units_ = ( Deg, Deg, Deg, Deg, Deg, Deg, Meter, Deg) 

50 

51# @Property_RO 

52# def A12(self): 

53# '''Approximate arc C{A12} as C{Deg}. 

54# ''' 

55# t = self 

56# d = t.s12 or _0_0 

57# if d: 

58# a = hypot(Deg(t.bet2 - t.bet1).degrees, 

59# Deg(t.omg2 - t.omg1).degrees) 

60# d = (-a) if d < 0 else a 

61# return Deg(d) 

62 

63 

64class _Geodesic3SolveBase(_Solve3Base): 

65 '''(INTERNAL) Base class for L{Geodesic3Solve} and L{GeodesicLine3Solve}. 

66 ''' 

67 _a12x = Geod3Solve8Tuple._Names_.index(_a12_) # last 

68 _Error = Geodesic3Error 

69 _Names_Direct = _Names_Distance = \ 

70 _Names_Inverse = Geod3Solve8Tuple._Names_[:_a12x] # 7 only, always 

71 _triaxial3 = _Triaxial3_WGS84 

72 _Xable_name = _Xables.Geod3Solve.__name__ # typename 

73 _Xable_path = _Xables.Geod3Solve() 

74# assert _a12x == len(Geod3Solve8Tuple._Names_) - 1 

75 del _a12x 

76 

77 @Property_RO 

78 def a(self): 

79 '''Get the triaxial's I{major} radius, semi-axis (C{meter}). 

80 ''' 

81 return self._triaxial3.a 

82 

83 @Property_RO 

84 def b(self): 

85 '''Get the triaxial's I{middle} radius, semi-axis (C{meter}). 

86 ''' 

87 return self._triaxial3.b 

88 

89 @Property_RO 

90 def c(self): 

91 '''Get the triaxial's I{minor} radius, semi-axis (C{meter}). 

92 ''' 

93 return self._triaxial3.c 

94 

95 @Property_RO 

96 def _cmdBasic(self): 

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

98 ''' 

99 return (self.Geod3Solve, '-f') + (self._t_option + 

100 self._p_option + 

101 self._u_option) 

102 

103 @property_RO 

104 def _e_option(self): 

105 return () 

106 

107 _mpd = _E_option = _e_option 

108 flattening = f = ellipsoid = datum = None 

109 

110 @Property 

111 def Geod3Solve(self): 

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

113 executable (C{filename}). 

114 ''' 

115 return self._Xable_path 

116 

117 @Geod3Solve.setter # PYCHOK setter! 

118 def Geod3Solve(self, path): 

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

120 executable (C{filename}), the (fully qualified) path to the C{Geod3Solve} executable. 

121 

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

123 isn't the C{Geod3Solve} executable. 

124 ''' 

125 self._setXable(path) 

126 

127 @property_RO 

128 def _t_option(self): 

129 return ('-t',) + self._toStdin(self._triaxial3._abc3) 

130 

131 def toStr(self, **prec_sep): # PYCHOK signature 

132 '''Return this C{Geodesic3Solve} as string. 

133 

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

135 

136 @return: Geodesic3Solve items (C{str}). 

137 ''' 

138 return _Solve3Base.toStr(self, Geod3Solve=self.Geod3Solve, **prec_sep) 

139 

140 @Property_RO 

141 def _u_option(self): 

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

143 

144 

145class Geodesic3Solve(_Geodesic3SolveBase): 

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

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

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

149 

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

151 qualified) path to the C{Geod3Solve} executable. 

152 

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

154 executable for I{every} method call. 

155 ''' 

156 

157 def __init__(self, triaxial3=_Triaxial3_WGS84, path=NN, **name): 

158 '''New C{Solve} instance. 

159 

160 @arg triaxial3: A triaxial (L{Triaxial3}), default C{Triaxial3s.WGS84_3r}. 

161 @kwarg path: Optionally, the (fully qualified) path to the C{GeodSolve} 

162 or C{RhumbSolve} executable (C{filename}). 

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

164 

165 @raise TypeError: Invalid B{C{a_ellipsoid}} or B{C{f}}. 

166 ''' 

167 _xinstanceof(Triaxial3, triaxial3=triaxial3) 

168 if self._triaxial3 != triaxial3: 

169 self._triaxial3 = triaxial3 

170 if name: 

171 self.name = name 

172 if path: 

173 self._setXable(path) 

174 

175 def _a12d(self, r): 

176 '''(INTERNAL) Add arc C{a12} in degrees to C{GDict}. 

177 ''' 

178 d = r.s12 

179 if d: 

180 a, b, c = self.triaxial3.abc3 

181 E = _MODS.ellipses.Ellipse 

182 z = _toAzi(r.alp1) + _toAzi(r.alp2) 

183 s, z = sincos2d(z * _0_5) 

184 d *= hypot(_over(s, E(a, b).perimeter2k), # azimuth! 

185 _over(z, E(b, c).perimeter2k)) * _360_0 

186 r[_a12_] = d or _0_0 

187 return r 

188 

189 def Direct(self, bet1, omg1, alp1, s12, outmask=_UNUSED_, **unit): # PYCHOK unused 

190 '''Return the C{Direct} result at distance C{s12}. 

191 ''' 

192 bet1, omg1, alp1 = _toDegrees(bet1, omg1, alp1, **unit) 

193 r = self._GDictDirect(bet1, omg1, alp1, False, s12) 

194 return self._a12d(r) 

195 

196 def DirectLine(self, bet1, omg1, alp1, caps=Caps.ALL, **unit_name): 

197 '''Set up a L{GeodesicLine3Solve} to compute several points 

198 on a single geodesic. 

199 

200 @arg bet1: Ellipsoidal Latitude of the first point (C{Ang} or B{C{unit}}). 

201 @arg omg1: Ellipsoidal Longitude of the first point (C{Ang} or B{C{unit}}). 

202 @arg alp1: Azimuth at the first point (compass C{degrees}, C{Ang} or C{unit}). 

203 @kwarg caps: Desired capabilities for the L{GeodesicLine3Solve} instance. 

204 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Degrees} 

205 (or L{Degrees}). 

206 

207 @return: A L{GeodesicLine3Solve} instance. 

208 

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

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

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

212 ''' 

213 return GeodesicLine3Solve(self, bet1, omg1, alp1, caps=caps, **unit_name) 

214 

215 Line = DirectLine # ArcDirectLine 

216 

217 def Inverse(self, bet1, omg1, bet2, omg2, outmask=_UNUSED_, **unit): # PYCHOK unused 

218 '''Return the C{Inverse} result. 

219 ''' 

220 r = self._GDictInverse(*_toDegrees(bet1, omg1, bet2, omg2, **unit)) 

221 return self._a12d(r) 

222 

223 def InverseLine(self, bet1, omg1, bet2, omg2, caps=Caps.ALL, **unit_name): 

224 '''Set up a L{GeodesicLine3Solve} to compute several points 

225 on a single geodesic. 

226 

227 @arg bet1: Latitude of the first point (C{Ang} or B{C{unit}}). 

228 @arg omg1: Longitude of the first point (C{Ang} or B{C{unit}}). 

229 @arg bet2: Latitude of the second point (C{Ang} or B{C{unit}}). 

230 @arg omg2: Longitude of the second point (C{Ang} or B{C{unit}}). 

231 @kwarg caps: Desired capabilities for the L{GeodesicLine3Solve} instance. 

232 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Degrees} 

233 (or L{Degrees}). 

234 

235 @return: A L{GeodesicLine3Solve} instance. 

236 

237 @note: Both B{C{bet1}} and B{C{bet2}} should in the range C{[-90, +90]}. 

238 

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

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

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

242 ''' 

243 r = self.Inverse(bet1, omg1, bet2, omg2, **unit_name) 

244 gl = GeodesicLine3Solve(self, r.bet1, r.omg1, r.alp1, caps=caps, **unit_name) 

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

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

247 return gl 

248 

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

250 '''Return this C{Geodesic3Solve} as string. 

251 

252 @kwarg prec_sep: See L{toStr<pygeodesy.solveBase._Solve3Base.toStr>}. 

253 

254 @return: Geodesic3Solve items (C{str}). 

255 ''' 

256 return _Solve3Base.toStr(self, Geod3Solve=self.Geod3Solve, **prec_sep_other) 

257 

258 @property_RO 

259 def triaxial3(self): 

260 '''Get the triaxial (C{Triaxial3}). 

261 ''' 

262 return self._triaxial3 

263 

264 

265class GeodesicLine3Solve(_Geodesic3SolveBase): # _SolveGDictLineBase): 

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

267 as a C{triaxial} version of I{Karney}'s Python class U{GeodesicLine<https://GeographicLib.SourceForge.io/ 

268 C++/doc/python/code.html#geographiclib.geodesicline.GeodesicLine>}. 

269 

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

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

272 

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

274 executable for I{every} method call. 

275 ''' 

276# _a13 = \ 

277# _s13 = NAN # see GeodesicSolve._InverseLine 

278 _solve = None # L{Geodesic3Solve} instance 

279 

280 def __init__(self, geodesic3, bet1, omg1, alp1, caps=Caps.ALL, **unit_name): 

281 '''New L{GeodesicLine3Solve} instance, allowing points to be found along 

282 a geodesic starting at C{(B{bet1}, B{omg1})} with azimuth B{C{alp1}}. 

283 

284 @arg geodesic3: The geodesic to use (L{Geodesic3Solve}). 

285 @arg bet1: Ellipsoidal latitude of the first point (C{Ang} or B{C{unit}}). 

286 @arg omg1: Ellipsoidal longitude of the first point (C{Ang} or B{C{unit}}). 

287 @arg alp1: Azimuth at the first point (compass C{degrees}, C{Ang} or C{unit}). 

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

289 specifying the capabilities the L{GeodesicLine3Solve} instance 

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

291 if updates to the B{C{geodesic3}} should I{not be reflected} in this 

292 L{GeodesicLine3Solve} instance. 

293 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Degrees} 

294 (or L{Degrees}). 

295 

296 @raise Geodesic3Error: Invalid path for the C{Geod3Solve} executable or isn't the 

297 C{Geod3Solve} executable, see property C{geodesic3.Geod3Solve}. 

298 

299 @raise TypeError: Invalid B{C{geodesic3}}. 

300 ''' 

301 _xinstanceof(Geodesic3Solve, geodesic3=geodesic3) 

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

303 geodesic3 = geodesic3.copy(deep=False, name=_UNDER_(NN, geodesic3.name)) # NOT _under! 

304 self._bet1, \ 

305 self._omg1, \ 

306 self._alp1 = _toDegrees(bet1, omg1, alp1, **unit_name) 

307 self._caps = caps | Caps._AZIMUTH_LATITUDE_LONG_UNROLL 

308 self._debug = geodesic3._debug & Caps._DEBUG_ALL 

309 self._solve = geodesic3 

310 try: 

311 self.Geod3Solve = geodesic3.Geod3Solve # geodesic or copy of geodesic 

312 except GeodesicError: 

313 pass 

314 if unit_name: 

315 name = _xkwds_get(unit_name, name=None) 

316 if name: 

317 self.name = name 

318 

319 @Property_RO 

320 def alp1(self): 

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

322 ''' 

323 return self._alp1 

324 

325# azi12 = alp1 # like RhumbLineSolve 

326 

327# @Property_RO 

328# def azi1_sincos2(self): 

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

330# ''' 

331# return _sincos2d(self.alp1) 

332# 

333# azi12_sincos2 = azi1_sincos2 

334 

335 @Property_RO 

336 def bet1(self): 

337 '''Get the I{ellipsoidal} latitude of the first point (C{degrees}). 

338 ''' 

339 return self._bet1 

340 

341 @Property_RO 

342 def _cmdDistance(self): 

343 '''(INTERNAL) Get the C{Geod3Solve} I{-L} cmd (C{tuple}). 

344 ''' 

345 t = self.bet1, self.omg1, self.alp1 

346 return self._cmdBasic + ('-L',) + self._toStdin(t) 

347 

348 @property_RO 

349 def geodesic3(self): 

350 '''Get the C{triaxial} geodesic (L{Geodesic3Solve}). 

351 ''' 

352 return self._solve # see .solveBase._SolveLineBase 

353 

354 def Intersecant2(self, bet0, omg0, radius, **kwds): # PYCHOK no cover 

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

356 self._notImplemented(bet0, omg0, radius, **kwds) 

357 

358 @Property_RO 

359 def omg1(self): 

360 '''Get the I{ellipsoidal} longitude of the first point (C{degrees}). 

361 ''' 

362 return self._omg1 

363 

364 def PlumbTo(self, bet0, omg0, **kwds): # PYCHOK no cover 

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

366 self._notImplemented(bet0, omg0, **kwds) 

367 

368 def Position(self, s12, outmask=Caps.ALL): # PYCHOK usused 

369 '''Find the position on the line given B{C{s12}}. 

370 

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

372 

373 @return: A C{GDict} with 7 items C{bet1, omg1, alp1, bet2, omg2, alp2, s12}. 

374 ''' 

375 r = self._GDictInvoke(self._cmdDistance, self._Names_Distance, s12) # ._unCaps(outmask) 

376 return self.geodesic3._a12d(r) 

377 

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

379 '''Return this C{GeodesicLine3Solve} as string. 

380 

381 @kwarg prec_sep: See L{toStr<pygeodesy.solveBase._Solve3Base.toStr>}. 

382 

383 @return: GeodesicLine3Solve items (C{str}). 

384 ''' 

385 return _Solve3Base.toStr(self, bet1=self.bet1, omg1=self.omg1, alp1=self.alp1, 

386 geodesic3=self.geodesic3, **prec_sep_other) 

387 

388 @property_RO 

389 def triaxial3(self): 

390 '''Get the triaxial (C{Triaxial3}). 

391 ''' 

392 return self.geodesic3.triaxial3 

393 

394 

395def _toAzi(alp): # as degrees 

396 return alp.degrees0 if isAng(alp) else alp 

397 

398 

399def _toDegrees(*angs, **unit_name): 

400 unit = _xkwds_get(unit_name, unit=Degrees) 

401 for ang in angs: 

402 if not isAng(ang): 

403 ang = Ang.fromScalar(ang, unit=unit) 

404 yield ang.degrees 

405 

406 

407__all__ += _ALL_DOCS(_Geodesic3SolveBase) 

408 

409if __name__ == _DMAIN_: 

410 

411 def _main(): 

412 from pygeodesy import printf 

413 from sys import argv 

414 

415 gS = Geodesic3Solve(name='Test') 

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

417 

418 if not _Xables.X_OK(gS.Geod3Solve): # not set 

419 gS.Geod3Solve = _Xables.Geod3Solve(_Xables.bin_) 

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

421 

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

423 printf('Direct: %r', r, nt=v) 

424 

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

426 

427 glS = GeodesicLine3Solve(gS, 40.6, -73.8, 51, name='LineTest') 

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

429 p = glS.Position(5.5e6) 

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

431 

432 _main() 

433 

434# % python3 -m pygeodesy.geod3solve 

435 

436# version: /opt/local/bin/Geod3Solve: GeographicLib version 2.7 

437# Direct: GDict(a12=49.410276, alp1=51.0, alp2=107.340251, bet1=40.6, bet2=51.895816, omg1=-73.8, omg2=-1.038308, s12=5500000.0) 

438# Inverse: GDict(a12=49.816802, alp1=51.235527, alp2=107.918138, bet1=40.6, bet2=51.6, omg1=-73.8, omg2=-0.5, s12=5545275.651379) 

439# Line: GeodesicLine3Solve(alp1=51.0, bet1=40.6, geodesic3=Geodesic3Solve(Geod3Solve='/opt/local/bin/Geod3Solve', invokation=3, status=0), invokation=1, omg1=-73.8, status=0) 

440# Position: GDict(a12=49.410276, alp1=51.0, alp2=107.340251, bet1=40.6, bet2=51.895816, omg1=-73.8, omg2=-1.038308, s12=5500000.0) True 

441 

442 

443# % python3 -m pygeodesy.geod3solve --verbose 

444 

445# Geodesic3Solve 'Test'@1: /opt/local/bin/Geod3Solve --version (invoke) 

446# Geodesic3Solve 'Test'@1: '/opt/local/bin/Geod3Solve: GeographicLib version 2.7' (0, stdout/-err) 

447# Geodesic3Solve 'Test'@1: /opt/local/bin/Geod3Solve: GeographicLib version 2.7 (0) 

448# version: /opt/local/bin/Geod3Solve: GeographicLib version 2.7 

449# 

450# Geodesic3Solve 'Test'@2: /opt/local/bin/Geod3Solve -f -t 6378172.0 6378102.0 6356752.0 -p 10 \ 40.6 -73.8 51.0 5500000.0 (Direct) 

451# Geodesic3Solve 'Test'@2: '40.600000000000001 -73.799999999999997 51.000000000000007 51.895816223972680 -1.038308043217667 107.340251322641734 5500000.0000000000' (0, stdout/-err) 

452# Geodesic3Solve 'Test'@2: bet1=40.600000000000001, omg1=-73.799999999999997, alp1=51.000000000000007, bet2=51.89581622397268, omg2=-1.038308043217667, alp2=107.340251322641734, s12=5500000.0 (0) 

453# Direct: GDict(a12=49.410276, alp1=51.0, alp2=107.340251, bet1=40.6, bet2=51.895816, omg1=-73.8, omg2=-1.038308, s12=5500000.0) 

454# 

455# Geodesic3Solve 'Test'@3: /opt/local/bin/Geod3Solve -f -t 6378172.0 6378102.0 6356752.0 -p 10 -i \ 40.6 -73.8 51.6 -0.5 (Inverse) 

456# Geodesic3Solve 'Test'@3: '40.600000000000001 -73.799999999999997 51.235527494379824 51.600000000000001 -0.500000000000000 107.918137616344865 5545275.6513788253' (0, stdout/-err) 

457# Geodesic3Solve 'Test'@3: bet1=40.600000000000001, omg1=-73.799999999999997, alp1=51.235527494379824, bet2=51.600000000000001, omg2=-0.5, alp2=107.918137616344865, s12=5545275.6513788253 (0) 

458# Inverse: GDict(a12=49.816802, alp1=51.235527, alp2=107.918138, bet1=40.6, bet2=51.6, omg1=-73.8, omg2=-0.5, s12=5545275.651379) 

459# 

460# Line: GeodesicLine3Solve(alp1=51.0, bet1=40.6, geodesic3=Geodesic3Solve(Geod3Solve='/opt/local/bin/Geod3Solve', invokation=3, status=0), invokation=1, omg1=-73.8, status=0) 

461# Position: GDict(a12=49.410276, alp1=51.0, alp2=107.340251, bet1=40.6, bet2=51.895816, omg1=-73.8, omg2=-1.038308, s12=5500000.0) True 

462 

463 

464# Examples <https://GeographicLib.SourceForge.io/C++/doc/Geod3Solve.1.html> 

465 

466# % echo 40:38:23N 073:46:44W-19.43W X 01:21:33N 103:59:22E-19.43W | tr X '\n' | Cart3Convert -G | Cart3Convert -E -r | tr '\n' ' ' | Geod3Solve -i -p 0 -f 

467# 40.57193 -54.38111 3.20824 1.35529 123.41971 177.48319 15347602 

468# % echo 40:38:23N 073:46:44W-19.43W X 01:21:33N 103:59:22E-19.43W | tr X '\n' | Cart3Convert -G | Cart3Convert -E -r | tr '\n' ' ' | Geod3Solve -i -p 0 

469# 3.20824 177.48319 15347602 

470# % echo 40:38:23N 073:46:44W-19.43W X 01:21:33N 103:59:22E-19.43W | tr X '\n' | Cart3Convert -G | Cart3Convert -E -r | tr '\n' ' ' | Geod3Solve -i -p 9 

471# 3.20824419242752 177.48319457984906 15347602.214888915 

472 

473# % Geod3Solve -L 40.57193 -54.38111 3.20824 

474# 15347602 

475# 1.35529159 123.41971119 177.48319768 

476# 15347602 

477# 1.35529159 123.41971119 177.48319768 

478# % Geod3Solve -L 40.57193 -54.38111 3.20824 -f 

479# 15347602 

480# 40.57193000 -54.38111000 3.20824000 1.35529159 123.41971119 177.48319768 15347602.000 

481# % Geod3Solve -L 40.57193 -54.38111 3.20824 -f -p 9 

482# 15347602 

483# 40.57193000000000 -54.38111000000000 3.20824000000000 1.35529159010289 123.41971119123770 177.48319768195134 15347602.000000000 

484 

485# **) MIT License 

486# 

487# Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved. 

488# 

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

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

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

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

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

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

495# 

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

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

498# 

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

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

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

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

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

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

505# OTHER DEALINGS IN THE SOFTWARE.