1

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

3

4 u'''Wrapper around Python classes C{geodesic.Geodesic} and C{geodesicline.GeodesicLine} from

5 I{Karney}'s Python package U{geographiclib<https://PyPI.org/project/geographiclib>}, provided

6 that package is installed.

7

8 The I{wrapped} class methods return a L{GDict} instance offering access to the C{dict} items

9 either by C{key} or by C{attribute} name.

10

11 With env variable C{PYGEODESY_GEOGRAPHICLIB} left undefined or set to C{"2"}, this module,

12 L{pygeodesy.geodesicx} and L{pygeodesy.karney} will use U{GeographicLib 2.0

13<https://GeographicLib.SourceForge.io/C++/doc/>} transcoding, otherwise C{1.52} or older.

14'''

15

16# from pygeodesy.basics import _xinstanceof # from .karney

17# from pygeodesy.constants import NAN # from .karney

18# from pygeodesy.datums import _a_ellipsoid # from .karney

19 from pygeodesy.interns import NN, _DOT_, _UNDER

20 from pygeodesy.karney import _a_ellipsoid, _atan2d, Caps, Direct9Tuple, \

21 _EWGS84, fabs, GDict, GeodesicError, Inverse10Tuple, \

22 _kWrapped, NAN, unroll180, wrap360, _xinstanceof # PYCHOK used!

23 from pygeodesy.lazily import _ALL_LAZY

24 from pygeodesy.named import callername, classname, unstr

25 from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple

26 from pygeodesy.props import Property, Property_RO

27# from pygeodesy.streps import unstr # from .named

28# from pygeodesy.utily import unroll180, wrap360 # from .karney

29

30 from contextlib import contextmanager

31# from math import fabs # from .karney

32

33__all__ = _ALL_LAZY.geodesicw

34__version__ = '23.04.11'

35

36

37 class _gWrapped(_kWrapped):

38 ''''(INTERNAL) Wrapper for some of I{Karney}'s U{geographiclib

39 <https://PyPI.org/project/geographiclib>} classes.

40 '''

41

42 @Property_RO # MCCABE 24

43 def Geodesic(self):

44 '''Get the I{wrapped} C{geodesic.Geodesic} class from I{Karney}'s Python

45 U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},

46 provided the latter is installed.

47 '''

48 _Geodesic = self.geographiclib.Geodesic

49 _DIRECT3 = _Geodesic.AZIMUTH | _Geodesic.LATITUDE | _Geodesic.LONGITUDE

50 _INVERSE3 = _Geodesic.AZIMUTH | _Geodesic.DISTANCE

51

52 class Geodesic(_Geodesic):

53 '''I{Wrapper} for I{Karney}'s Python U{geodesic.Geodesic

54 <https://PyPI.org/project/geographiclib>} class.

55 '''

56 _debug = 0 # like .geodesicx.bases._GeodesicBase

57 _E = _EWGS84

58 LINE_OFF = 0 # in .azimuthal._GnomonicBase and .css.CassiniSoldner

59

60 def __init__(self, a_ellipsoid=_EWGS84, f=None, name=NN): # PYCHOK signature

61 '''New I{wrapped} C{geodesic.Geodesic} instance.

62

63 @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum})

64 or the equatorial radius I{a} of the ellipsoid (C{meter}).

65 @arg f: The flattening of the ellipsoid (C{scalar}), ignored if

66 B{C{a_ellipsoid}) is not specified as C{meter}.

67 @kwarg name: Optional ellipsoid name (C{str}), ignored like B{C{f}}.

68 '''

69 if a_ellipsoid not in (Geodesic._E, None): # spherical OK

70 self._E = _a_ellipsoid(a_ellipsoid, f, name=name) # raiser=NN

71 with _wargs(self, *self.ellipsoid.a_f, name=name) as args:

72 _Geodesic.__init__(self, *args)

73

74 def ArcDirect(self, lat1, lon1, azi1, a12, *outmask):

75 '''Return the C{_Geodesic.ArcDirect} result as L{GDict}.

76 '''

77 with _wargs(self, lat1, lon1, azi1, a12, *outmask) as args:

78 d = _Geodesic.ArcDirect(self, *args)

79 return GDict(d)

80

81# ArcDirectLine = _Geodesic.ArcDirectLine # via ._GenDirectLine

82

83 Area = _Geodesic.Polygon # like GeodesicExact.Area

84

85 @Property

86 def debug(self):

87 '''Get the C{debug} option (C{bool}).

88 '''

89 return bool(self._debug)

90

91 @debug.setter # PYCHOK setter!

92 def debug(self, debug):

93 '''Set the C{debug} option (C{bool}) to include more

94 details in L{GDict} results.

95 '''

96 self._debug = Caps._DEBUG_ALL if debug else 0

97

98 def Direct(self, lat1, lon1, azi1, s12, *outmask):

99 '''Return the C{_Geodesic.Direct} result as L{GDict}.

100 '''

101 with _wargs(self, lat1, lon1, azi1, s12, *outmask) as args:

102 d = _Geodesic.Direct(self, *args)

103 return GDict(d)

104

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

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

107 in C{degrees} as L{Destination3Tuple}.

108 '''

109 d = self.Direct(lat1, lon1, azi1, s12, _DIRECT3)

110 return Destination3Tuple(d.lat2, d.lon2, d.azi2)

111

112# DirectLine = _Geodesic.DirectLine # via ._GenDirectLine

113

114 @Property_RO

115 def ellipsoid(self):

116 '''Get this geodesic's ellipsoid (C{Ellipsoid[2]}).

117 '''

118 return self._E

119

120 @Property_RO

121 def f1(self): # in .css.CassiniSoldner.reset

122 '''Get the geodesic's ellipsoid's I{1 - flattening} (C{float}).

123 '''

124 return getattr(self, _UNDER(Geodesic.f1.name), self.ellipsoid.f1)

125

126 def _GDictDirect(self, lat, lon, azi, arcmode, s12_a12,

127 outmask=_Geodesic.STANDARD):

128 '''(INTERNAL) Get C{_Geodesic._GenDirect} result as C{GDict}.

129 '''

130 with _wargs(self, lat, lon, azi, arcmode, s12_a12, outmask) as args:

131 t = _Geodesic._GenDirect(self, *args)

132 return Direct9Tuple(t).toGDict() # *t

133

134 def _GDictInverse(self, lat1, lon1, lat2, lon2, outmask=_Geodesic.STANDARD):

135 '''(INTERNAL) Get C{_Geodesic._GenInverse} result as L{Inverse10Tuple}.

136 '''

137 with _wargs(self, lat1, lon1, lat2, lon2, outmask) as args:

138 t = _Geodesic._GenInverse(self, *args)

139 return Inverse10Tuple(t).toGDict(lon1=lon1, lon2=lon2) # *t

140

141 def _GenDirectLine(self, lat1, lon1, azi1, arcmode, s12_a12, *caps):

142 '''(INTERNAL) Invoked by C{_Geodesic.DirectLine} and C{-.ArcDirectLine},

143 returning the result as a I{wrapped} C{GeodesicLine}.

144 '''

145 with _wargs(self, lat1, lon1, azi1, arcmode, s12_a12, *caps) as args:

146 t = _Geodesic._GenDirectLine(self, *args)

147 return self._Line13(t)

148

149 def Inverse(self, lat1, lon1, lat2, lon2, *outmask):

150 '''Return the C{_Geodesic.Inverse} result as L{GDict}.

151 '''

152 with _wargs(self, lat1, lon1, lat2, lon2, *outmask) as args:

153 d = _Geodesic.Inverse(self, *args)

154 return GDict(d)

155

156 def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False):

157 '''Return the non-negative, I{angular} distance in C{degrees}.

158 '''

159 # see .FrechetKarney.distance, .HausdorffKarney._distance

160 # and .HeightIDWkarney._distances

161 _, lon2 = unroll180(lon1, lon2, wrap=wrap) # _Geodesic.LONG_UNROLL

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

163 # XXX _Geodesic.DISTANCE needed for 'a12'?

164 return fabs(r.a12)

165

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

167 '''Return the distance in C{meter} and the forward and reverse

168 azimuths in C{degrees} as L{Distance3Tuple}.

169 '''

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

171 return Distance3Tuple(r.s12, wrap360(r.azi1), wrap360(r.azi2))

172

173 def InverseLine(self, lat1, lon1, lat2, lon2, *caps):

174 '''Return the C{_Geodesic.InverseLine} as I{wrapped} C{GeodesicLine}.

175 '''

176 with _wargs(self, lat1, lon1, lat2, lon2, *caps) as args:

177 t = _Geodesic.InverseLine(self, *args)

178 return self._Line13(t)

179

180 def Line(self, lat1, lon1, azi1, *caps): # == *caps_salp1_calp1

181 '''Set up a I{wrapped} C{GeodesicLine} to compute several points

182 along a single, I{wrapped} (this) geodesic.

183 '''

184 return _wrapped.GeodesicLine(self, lat1, lon1, azi1, *caps)

185

186 def _Line13(self, t):

187 '''(INTERNAL) Wrap C{_GeodesicLine}, add distance and arc length

188 to reference point 3.

189 '''

190 r = self.Line(t.lat1, t.lon1, t.azi1, t.caps, t.salp1, t.calp1)

191 r.a13, r.s13 = t.a13, t.s13

192 return r

193

194# Polygon = _Geodesic.Polygon

195

196 # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__

197 # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__

198 # Geodesic.Inverse.__doc__ = _Geodesic.Inverse.__doc__

199 # Geodesic.InverseLine.__doc__ = _Geodesic.InverseLinr.__doc__

200 # Geodesic.Line.__doc__ = _Geodesic.Line.__doc__

201 return Geodesic

202

203 @Property_RO # MCCABE 16

204 def GeodesicLine(self):

205 '''Get the I{wrapped} C{geodesicline.GeodesicLine} class from I{Karney}'s

206 Python U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},

207 provided the latter is installed.

208 '''

209 _GeodesicLine = self.geographiclib.GeodesicLine

210

211 class GeodesicLine(_GeodesicLine):

212 '''I{Wrapper} for I{Karney}'s Python U{geodesicline.GeodesicLine

213 <https://PyPI.org/project/geographiclib>} class.

214 '''

215 def __init__(self, geodesic, lat1, lon1, azi1, *caps):

216 '''New I{wrapped} C{geodesicline.GeodesicLine} instance.

217

218 @arg geodesic: A I{wrapped} C{Geodesic} instance.

219 @arg lat1: Latitude of the first points (C{degrees}).

220 @arg lon1: Longitude of the first points (C{degrees}).

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

222 @arg caps: Optional, bit-or'ed combination of L{Caps} values

223 specifying the capabilities the C{GeodesicLine}

224 instance should possess, i.e., which quantities can

225 be returned by calls to C{GeodesicLine.Position}

226 and C{GeodesicLine.ArcPosition}.

227 '''

228 _xinstanceof(geodesic, _wrapped.Geodesic)

229 with _wargs(self, geodesic, lat1, lon1, azi1, *caps) as args:

230 _GeodesicLine.__init__(self, *args)

231

232 @Property_RO

233 def a1(self):

234 '''Get the I{equatorial arc} (C{degrees}), the arc length between

235 the northward equatorial crossing and point C{(lat1, lon1)}.

236

237 @see: U{EquatorialArc<https://GeographicLib.SourceForge.io/

238 C++/doc/classGeographicLib_1_1GeodesicLine.html>}

239 '''

240 try:

241 return _atan2d(self._ssig1, self._csig1)

242 except AttributeError:

243 return NAN # see .geodesicx.gxline._GeodesicLineExact

244

245 equatorarc = a1

246

247 def Arc(self):

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

249 '''

250 return self.a13

251

252 def ArcPosition(self, a12, *outmask):

253 '''Return the position at arc length C{B{a12} degrees} on this line.

254 '''

255 with _wargs(self, a12, *outmask) as args:

256 d = _GeodesicLine.ArcPosition(self, *args)

257 return GDict(d)

258

259 @Property_RO

260 def azi0(self): # see .css.CassiniSoldner.forward4

261 '''Get the I{equatorial azimuth} (C{degrees}), the azimuth of the

262 geodesic line as it crosses the equator in a northward direction.

263

264 @see: U{EquatorialAzimuth<https://GeographicLib.SourceForge.io/

265 C++/doc/classGeographicLib_1_1GeodesicLine.html>}

266 '''

267 try:

268 return _atan2d(self._salp0, self._calp0)

269 except AttributeError:

270 return NAN # see .geodesicx.gxline._GeodesicLineExact

271

272 equatorazimuth = azi0

273

274 def Distance(self):

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

276 '''

277 return self.s13

278

279 def Position(self, s12, *outmask):

280 '''Return the position at distance C{B{s12} meter} on this line.

281 '''

282 with _wargs(self, s12, *outmask) as args:

283 d = _GeodesicLine.Position(self, *args)

284 return GDict(d)

285

286 # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__

287 # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__

288 return GeodesicLine

289

290 @Property_RO

291 def Geodesic_WGS84(self):

292 '''Get the I{wrapped} C{Geodesic(WGS84)} singleton, provided the

293 U{geographiclib<https://PyPI.org/project/geographiclib>} package

294 is installed, otherwise an C{ImportError}.

295 '''

296 return _EWGS84.geodesic

297

298_wrapped = _gWrapped() # PYCHOK singleton, .ellipsoids, .test/base.py

299

300

301 def Geodesic(a_ellipsoid, f=None, name=NN):

302 '''Return a I{wrapped} C{geodesic.Geodesic} instance from I{Karney}'s

303 Python U{geographiclib<https://PyPI.org/project/geographiclib>},

304 provide the latter is installed, otherwise an C{ImportError}.

305

306 @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum})

307 or the equatorial radius I{a} of the ellipsoid (C{meter}).

308 @arg f: The flattening of the ellipsoid (C{scalar}), ignored if

309 B{C{a_ellipsoid}}) is not specified as C{meter}.

310 @kwarg name: Optional ellipsoid name (C{str}), ignored like B{C{f}}.

311 '''

312 return _wrapped.Geodesic(a_ellipsoid, f=f, name=name)

313

314

315 def GeodesicLine(geodesic, lat1, lon1, azi1, *caps):

316 '''Return a I{wrapped} C{geodesicline.GeodesicLine} instance from I{Karney}'s

317 Python U{geographiclib<https://PyPI.org/project/geographiclib>}, provided

318 the latter is installed, otherwise an C{ImportError}.

319

320 @arg geodesic: A I{wrapped} L{Geodesic} instance.

321 @arg lat1: Latitude of the first points (C{degrees}).

322 @arg lon1: Longitude of the first points (C{degrees}).

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

324 @arg caps: Optional, bit-or'ed combination of L{Caps} values

325 specifying the capabilities the C{GeodesicLine}

326 instance should possess, i.e., which quantities can

327 be returned by calls to C{GeodesicLine.Position}

328 and C{GeodesicLine.ArcPosition}.

329 '''

330 return _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, *caps)

331

332

333 def Geodesic_WGS84():

334 '''Get the I{wrapped} L{Geodesic}C{(WGS84)} singleton, provided

335 U{geographiclib<https://PyPI.org/project/geographiclib>} is

336 installed, otherwise an C{ImportError}.

337 '''

338 return _wrapped.Geodesic_WGS84

339

340

341 class _wargs(object):

342 '''(INTERNAL) C{geographiclib} caller.

343 '''

344 @contextmanager # <https://www.python.org/dev/peps/pep-0343/> Examples

345 def __call__(self, inst, *args, **kwds):

346 '''(INTERNAL) Yield self with any errors raised as L{NumPyError}.

347 '''

348 try:

349 yield args

350 except (AttributeError, TypeError, ValueError) as x:

351 n = _DOT_(classname(inst), callername(up=3, underOK=True))

352 raise GeodesicError(unstr(n, *args, **kwds), cause=x)

353

354_wargs = _wargs() # PYCHOK singleton

355

356

357# **) MIT License

358#

359# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

360#

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

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

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

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

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

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

367#

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

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

370#

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

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

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

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

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

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

377# OTHER DEALINGS IN THE SOFTWARE.