Coverage for pygeodesy/geod3solve.py: 91%

1

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

3

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

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

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

7

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

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

10<geod3solve._Geodesic3SolveBase.Geod3Solve>}.

11'''

12

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

14 from pygeodesy.basics import _xinstanceof, _MODS # typename

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

16# from pygeodesy.ellipses import Ellipse # _MODS

17 from pygeodesy.errors import GeodesicError, _xkwds_get

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

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

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

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

22 from pygeodesy.props import Property, Property_RO, property_RO

23 from pygeodesy.solveBase import _Solve3Base, _ALL_DOCS, _ALL_LAZY

24 from pygeodesy.triaxials.triaxial3 import Triaxial3, Triaxial3s

25 from 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

34 class Geodesic3Error(GeodesicError):

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

36 '''

37 pass

38

39

40 class 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

64 class _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

145 class 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

265 class 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

395 def _toAzi(alp): # as degrees

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

397

398

399 def _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

409 if __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.