1

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

3

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

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

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

7

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

9 of the C{GeodSolve} executable.

10'''

11

12 from pygeodesy.basics import _xinstanceof

13# from pygeodesy.constants import NAN, _0_0 # from .karney

14# from pygeodesy.geodesicx import GeodesicAreaExact # _MODS

15 from pygeodesy.interns import NN, _a12_, _azi1_, _azi2_, \

16 _lat1_, _lat2_, _lon1_, _lon2_, _m12_, \

17 _M12_, _M21_, _s12_, _S12_, _UNDER_

18 from pygeodesy.interns import _UNUSED_, _not_ # PYCHOK used!

19 from pygeodesy.karney import _Azi, Caps, _Deg, GeodesicError, _GTuple, \

20 _Pass, _Lat, _Lon, _M, _M2, _sincos2d, \

21 _0_0, NAN

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

23 _getenv, _PYGEODESY_GEODSOLVE_

24 from pygeodesy.named import _name1__

25 from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple

26 from pygeodesy.props import Property, Property_RO, property_RO

27 from pygeodesy.solveBase import _SolveBase, _SolveLineBase

28 from pygeodesy.utily import _unrollon, _Wrap, wrap360

29

30__all__ = _ALL_LAZY.geodsolve

31__version__ = '24.06.26'

32

33

34 class GeodSolve12Tuple(_GTuple):

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

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

37 C{degrees}, initial C{azi1} and final C{azi2} forward azimuths, distance C{s12} and

38 reduced length C{m12} in C{meter}, area C{S12} in C{meter} I{squared} and geodesic

39 scale factors C{M12} and C{M21}, both C{scalar}, see U{GeodSolve

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

41 '''

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

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

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

45

46

47 class _GeodesicSolveBase(_SolveBase):

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

49 '''

50 _Error = GeodesicError

51 _Names_Direct = \

52 _Names_Inverse = GeodSolve12Tuple._Names_

53 _Solve_name = 'GeodSolve'

54 _Solve_path = _getenv(_PYGEODESY_GEODSOLVE_, _PYGEODESY_GEODSOLVE_)

55

56 @Property_RO

57 def _b_option(self):

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

59

60 @Property_RO

61 def _cmdBasic(self):

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

63 '''

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

65 self._e_option +

66 self._E_option +

67 self._p_option +

68 self._u_option)

69

70 @Property_RO

71 def _E_option(self):

72 return ('-E',) if self.Exact else ()

73

74 @Property

75 def GeodSolve(self):

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

77 executable (C{filename}).

78 '''

79 return self._Solve_path

80

81 @GeodSolve.setter # PYCHOK setter!

82 def GeodSolve(self, path):

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

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

85

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

87 isn't the C{GeodSolve} executable.

88 '''

89 self._setSolve(path)

90

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

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

93

94 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '}

95 for the C{float} C{prec}ision, number of decimal digits

96 (0..9) and the C{sep}arator string to join. Trailing

97 zero decimals are stripped for B{C{prec}} values of

98 1 and above, but kept for negative B{C{prec}} values.

99

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

101 '''

102 return _SolveBase._toStr(self, GeodSolve=self.GeodSolve, **prec_sep)

103

104 @Property_RO

105 def _u_option(self):

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

107

108

109 class GeodesicSolve(_GeodesicSolveBase):

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

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

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

113

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

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

116

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

118 executable for I{every} method call.

119 '''

120

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

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

123 perimeter of a polygon.

124

125 @kwarg polyline: If C{True} perimeter only, otherwise

126 area and perimeter (C{bool}).

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

128

129 @return: A L{GeodesicAreaExact} instance.

130

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

132 to the returned L{GeodesicAreaExact} instance.

133 '''

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

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

136 gaX.verbose = True

137 return gaX

138

139 Polygon = Area # for C{geographiclib} compatibility

140

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

142 '''Return the destination lat, lon and reverse azimuth

143 (final bearing) in C{degrees}.

144

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

146 '''

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

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

149 iteration=r._iteration)

150

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

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

153 '''

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

155

156 def DirectLine(self, lat1, lon1, azi1, **caps_name):

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

158 on a single geodesic.

159

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

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

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

163 @kwarg caps_name: Optional C{B{name}=NN} (C{str}) and keyword

164 argument C{B{caps}=Caps.ALL}, bit-or'ed combination

165 of L{Caps} values specifying the capabilities the

166 L{GeodesicLineSolve} instance should possess.

167

168 @return: A L{GeodesicLineSolve} instance.

169

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

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

172 the limit C{ε → 0+}.

173

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

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

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

177 '''

178 return GeodesicLineSolve(self, lat1, lon1, azi1, **_name1__(caps_name, _or_nameof=self))

179

180 Line = DirectLine

181

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

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

184 '''

185 if wrap:

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

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

188

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

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

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

192

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

194 '''

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

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

197 iteration=r._iteration)

198

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

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

201 '''

202 if wrap:

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

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

205

206 def InverseLine(self, lat1, lon1, lat2, lon2, **caps_name): # PYCHOK no cover

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

208 on a single geodesic.

209

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

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

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

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

214 @kwarg caps_name: Optional C{B{name}=NN} (C{str}) and keyword

215 argument C{B{caps}=Caps.ALL}, bit-or'ed combination

216 of L{Caps} values specifying the capabilities the

217 L{GeodesicLineSolve} instance should possess.

218

219 @return: A L{GeodesicLineSolve} instance.

220

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

222

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

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

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

226 '''

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

228 gl = GeodesicLineSolve(self, lat1, lon1, r.azi1, **_name1__(caps_name, _or_nameof=self))

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

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

231 return gl

232

233

234 class GeodesicLineSolve(_GeodesicSolveBase, _SolveLineBase):

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

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

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

238

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

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

241

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

243 executable for I{every} method call.

244 '''

245 _a13 = \

246 _s13 = NAN # see GeodesicSolve._InverseLine

247

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

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

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

251

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

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

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

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

256 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying the

257 capabilities the L{GeodesicLineSolve} instance should possess,

258 C{B{caps}=Caps.ALL} always. Include C{Caps.LINE_OFF} if

259 updates to the B{C{geodesic}} should I{not} be reflected in

260 this L{GeodesicLineSolve} instance.

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

262

263 @raise GeodesicError: Invalid path for the C{GeodSolve} executable

264 or isn't the C{GeodSolve} executable, see

265 property C{geodesic.GeodSolve}.

266

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

268 '''

269 _xinstanceof(GeodesicSolve, geodesic=geodesic)

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

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

272 _SolveLineBase.__init__(self, geodesic, lat1, lon1, caps, azi1=azi1, **name)

273 try:

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

275 except GeodesicError:

276 pass

277

278 @Property_RO

279 def a13(self):

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

281

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

283 '''

284 return self._a13

285

286 def Arc(self):

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

288

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

290 '''

291 return self.a13

292

293 def ArcPosition(self, a12, outmask=Caps.STANDARD): # PYCHOK unused

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

295

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

297 second point (C{degrees}).

298

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

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

301 '''

302 return self._GDictInvoke(self._cmdArc, True, self._Names_Direct, a12)._unCaps(outmask)

303

304 @Property_RO

305 def azi1(self):

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

307 '''

308 return self._lla1.azi1

309

310 azi12 = azi1 # like RhumbLineSolve

311

312 @Property_RO

313 def azi1_sincos2(self):

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

315 '''

316 return _sincos2d(self.azi1)

317

318 azi12_sincos2 = azi1_sincos2

319

320 @Property_RO

321 def _cmdArc(self):

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

323 '''

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

325

326 def Distance(self):

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

328 '''

329 return self.s13

330

331 @property_RO

332 def geodesic(self):

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

334 '''

335 return self._solve # see .solveBase._SolveLineBase

336

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

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

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

340

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

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

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

344

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

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

347

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

349

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

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

352 '''

353 return self._GDictInvoke(self._cmdDistance, True, self._Names_Direct, s12)._unCaps(outmask)

354

355 @Property_RO

356 def s13(self):

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

358

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

360 '''

361 return self._s13

362

363 def SetArc(self, a13):

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

365

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

367 point (C{degrees}).

368

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

370 the reference point or C{NAN}.

371 '''

372 if self._a13 != a13:

373 self._a13 = a13

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

375# _update_all(self)

376 return self._s13

377

378 def SetDistance(self, s13):

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

380

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

382

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

384 and the reference point or C{NAN}.

385 '''

386 if self._s13 != s13:

387 self._s13 = s13

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

389# _update_all(self)

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

391

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

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

394

395 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '}

396 for the C{float} C{prec}ision, number of decimal digits

397 (0..9) and the C{sep}arator string to join. Trailing

398 zero decimals are stripped for B{C{prec}} values of

399 1 and above, but kept for negative B{C{prec}} values.

400

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

402 '''

403 return _SolveLineBase._toStr(self, azi1=self.azi1, geodesic=self._solve,

404 GeodSolve=self.GeodSolve, **prec_sep)

405

406

407__all__ += _ALL_DOCS(_GeodesicSolveBase)

408

409 if __name__ == '__main__':

410

411 from pygeodesy import printf

412 from sys import argv

413

414 gS = GeodesicSolve(name='Test')

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

416

417 if gS.GeodSolve in (_PYGEODESY_GEODSOLVE_, None): # not set

418 gS.GeodSolve = '/opt/local/bin/GeodSolve' # '/opt/local/Cellar/geographiclib/1.51/bin/GeodSolve' # HomeBrew

419 printf('version: %s', gS.version)

420

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

422 printf('Direct: %r', r, nl=1)

423 printf('Direct3: %r', gS.Direct3(40.6, -73.8, 51, 5.5e6))

424

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

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

427 printf('Inverse3: %r', gS.Inverse3(40.6, -73.8, 51.6, -0.5))

428

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

430 p = glS.Position(5.5e6)

431 printf('Position: %s %r', p == r, p, nl=1)

432 p = glS.ArcPosition(49.475527)

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

434

435

436# % python3 -m pygeodesy.geodsolve

437

438# version: /opt/local/bin/GeodSolve: GeographicLib version 2.2

439

440# 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.09375)

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

442

443# 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)

444# Inverse1: 49.94131021789904

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

446

447# Position: True 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.09375)

448# ArcPosition: False GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, m12=4844148.669561, M12=0.650911, M21=0.651229, s12=5499999.948497, S12=39735074737272.734375)

449

450

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

452

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

454# GeodesicSolve 'Test' 1: /opt/local/bin/GeodSolve: GeographicLib version 2.2 (0)

455# version: /opt/local/bin/GeodSolve: GeographicLib version 2.2

456# GeodesicSolve 'Test' 2: /opt/local/bin/GeodSolve -f -E -p 10 \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct)

457# GeodesicSolve 'Test' 2: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200829, azi2=107.189397162605886, s12=5500000.0, a12=49.475527463251467, m12=4844148.703101486, M12=0.65091056699808603, M21=0.65122865892196558, S12=39735075134877.094 (0)

458

459# 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.09375)

460# GeodesicSolve 'Test' 3: /opt/local/bin/GeodSolve -f -E -p 10 \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct3)

461# GeodesicSolve 'Test' 3: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200829, azi2=107.189397162605886, s12=5500000.0, a12=49.475527463251467, m12=4844148.703101486, M12=0.65091056699808603, M21=0.65122865892196558, S12=39735075134877.094 (0)

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

463# GeodesicSolve 'Test' 4: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse)

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

465

466# 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)

467# GeodesicSolve 'Test' 5: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse1)

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

469# Inverse1: 49.94131021789904

470# GeodesicSolve 'Test' 6: /opt/local/bin/GeodSolve -f -E -p 10 -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse3)

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

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

473

474# Position: True 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.09375)

475# ArcPosition: False GDict(a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, m12=4844148.669561, M12=0.650911, M21=0.651229, s12=5499999.948497, S12=39735074737272.734375)

476

477# **) MIT License

478#

479# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

480#

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

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

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

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

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

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

487#

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

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

490#

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

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

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

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

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

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

497# OTHER DEALINGS IN THE SOFTWARE.