Coverage for pygeodesy/geodesicw.py: 92%

133 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-08-20 14:00 -0400

1 

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

3 

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

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

6that package is installed. 

7 

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

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

10 

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

12L{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.ellipsoids import _EWGS84 # from .karney 

20# from pygeodesy.errors import _xkwds # from .karney 

21from pygeodesy.interns import NN, _DOT_, _under 

22from pygeodesy.karney import _atan2d, Caps, Direct9Tuple, GDict, \ 

23 _EWGS84, GeodesicError, Inverse10Tuple, \ 

24 _kWrapped, _a_ellipsoid, _ALL_LAZY, NAN, \ 

25 _xinstanceof, _xkwds # PYCHOK used! 

26# from pygeodesy.lazily import _ALL_LAZY # from .karney 

27from pygeodesy.named import callername, classname, unstr 

28from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple 

29from pygeodesy.props import Property, Property_RO 

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

31from pygeodesy.utily import _Wrap, wrap360, fabs # PYCHOK used! 

32 

33from contextlib import contextmanager 

34# from math import fabs # from .utily 

35 

36__all__ = _ALL_LAZY.geodesicw 

37__version__ = '23.08.20' 

38 

39 

40class _gWrapped(_kWrapped): 

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

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

43 ''' 

44 

45 @Property_RO # MCCABE 24 

46 def Geodesic(self): 

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

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

49 provided the latter is installed. 

50 ''' 

51 _Geodesic = self.geographiclib.Geodesic 

52 # assert Caps._STD == _Geodesic.STANDARD 

53 

54 class Geodesic(_Geodesic): 

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

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

57 ''' 

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

59 _E = _EWGS84 

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

61 

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

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

64 

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

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

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

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

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

70 ''' 

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

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

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

74 _Geodesic.__init__(self, *args) 

75 

76 def ArcDirect(self, lat1, lon1, azi1, a12, outmask=Caps._STD): 

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

78 ''' 

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

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

81 return GDict(d) 

82 

83 def ArcDirectLine(self, lat1, lon1, azi1, a12, caps=Caps._STD_LINE): 

84 '''Return the C{_Geodesic.ArcDirectLine} as I{wrapped} C{GeodesicLine}. 

85 ''' 

86 return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps) 

87 

88 Area = _Geodesic.Polygon # like GeodesicExact.Area 

89 

90 @Property 

91 def debug(self): 

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

93 ''' 

94 return bool(self._debug) 

95 

96 @debug.setter # PYCHOK setter! 

97 def debug(self, debug): 

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

99 details in L{GDict} results. 

100 ''' 

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

102 

103 def Direct(self, lat1, lon1, azi1, s12, outmask=Caps._STD): 

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

105 ''' 

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

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

108 return GDict(d) 

109 

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

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

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

113 ''' 

114 d = self.Direct(lat1, lon1, azi1, s12, outmask=Caps._DIRECT3) 

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

116 

117 def DirectLine(self, lat1, lon1, azi1, s12, caps=Caps._STD_LINE): 

118 '''Return the C{_Geodesic.DirectLine} as I{wrapped} C{GeodesicLine}. 

119 ''' 

120 return self._GenDirectLine(lat1, lon1, azi1, False, s12, caps) 

121 

122 @Property_RO 

123 def ellipsoid(self): 

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

125 ''' 

126 return self._E 

127 

128 @Property_RO 

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

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

131 ''' 

132 return getattr(self, _under(Geodesic.f1.name), self.ellipsoid.f1) 

133 

134 def _GDictDirect(self, lat, lon, azi, arcmode, s12_a12, outmask=Caps._STD): 

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

136 ''' 

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

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

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

140 

141 def _GDictInverse(self, lat1, lon1, lat2, lon2, outmask=Caps._STD): 

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

143 ''' 

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

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

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

147 

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

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

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

151 ''' 

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

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

154 return self._Line13(t) 

155 

156 def Inverse(self, lat1, lon1, lat2, lon2, outmask=Caps._STD): 

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

158 ''' 

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

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

161 return GDict(d) 

162 

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

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

165 

166 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll 

167 B{C{lat2}} and BC{lon2}} (C{bool}). 

168 ''' 

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

170 # and .HeightIDWkarney._distances 

171 if wrap: 

172 _, lat2, lon2 = _Wrap.latlon3(lat1, lat2, lon2, True) # _Geodesic.LONG_UNROLL 

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

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

175 return fabs(r.a12) 

176 

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

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

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

180 ''' 

181 r = self.Inverse(lat1, lon1, lat2, lon2, outmask=Caps._INVERSE3) 

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

183 

184 def InverseLine(self, lat1, lon1, lat2, lon2, caps=Caps._STD_LINE): 

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

186 ''' 

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

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

189 return self._Line13(t) 

190 

191 def Line(self, lat1, lon1, azi1, caps=Caps._STD_LINE): 

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

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

194 ''' 

195 return _wrapped.GeodesicLine(self, lat1, lon1, azi1, caps=caps) 

196 

197 def _Line13(self, t): 

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

199 to reference point 3. 

200 ''' 

201 rl = _wrapped.GeodesicLine(self, t.lat1, t.lon1, t.azi1, caps=t.caps, 

202 salp1=t.salp1, calp1=t.calp1) 

203 rl.a13, rl.s13 = t.a13, t.s13 

204 return rl 

205 

206# Polygon = _Geodesic.Polygon 

207 

208 # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__ 

209 # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__ 

210 # Geodesic.Inverse.__doc__ = _Geodesic.Inverse.__doc__ 

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

212 # Geodesic.Line.__doc__ = _Geodesic.Line.__doc__ 

213 return Geodesic 

214 

215 @Property_RO # MCCABE 16 

216 def GeodesicLine(self): 

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

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

219 provided the latter is installed. 

220 ''' 

221 _GeodesicLine = self.geographiclib.GeodesicLine 

222 

223 class GeodesicLine(_GeodesicLine): 

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

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

226 ''' 

227 def __init__(self, geodesic, lat1, lon1, azi1, **caps): # caps, salp1=NAN, calp1=NAN 

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

229 

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

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

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

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

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

235 specifying the capabilities the C{GeodesicLine} 

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

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

238 and C{GeodesicLine.ArcPosition}. 

239 ''' 

240 _xinstanceof(_wrapped.Geodesic, geodesic=geodesic) 

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

242 _GeodesicLine.__init__(self, *args, **caps) 

243 

244 @Property_RO 

245 def a1(self): 

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

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

248 

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

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

251 ''' 

252 try: 

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

254 except AttributeError: 

255 return NAN # see .geodesicx.gxline._GeodesicLineExact 

256 

257 equatorarc = a1 

258 

259 def Arc(self): 

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

261 ''' 

262 return self.a13 

263 

264 def ArcPosition(self, a12, outmask=Caps._STD): 

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

266 ''' 

267 with _wargs(self, a12, outmask) as args: 

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

269 return GDict(d) 

270 

271 @Property_RO 

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

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

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

275 

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

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

278 ''' 

279 try: 

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

281 except AttributeError: 

282 return NAN # see .geodesicx.gxline._GeodesicLineExact 

283 

284 equatorazimuth = azi0 

285 

286 def Distance(self): 

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

288 ''' 

289 return self.s13 

290 

291 def Position(self, s12, outmask=Caps._STD): 

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

293 ''' 

294 with _wargs(self, s12, outmask) as args: 

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

296 return GDict(d) 

297 

298 # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__ 

299 # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__ 

300 return GeodesicLine 

301 

302 @Property_RO 

303 def Geodesic_WGS84(self): 

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

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

306 is installed, otherwise an C{ImportError}. 

307 ''' 

308 return _EWGS84.geodesic 

309 

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

311 

312 

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

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

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

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

317 

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

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

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

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

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

323 ''' 

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

325 

326 

327def GeodesicLine(geodesic, lat1, lon1, azi1, caps=Caps._STD_LINE): 

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

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

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

331 

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

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

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

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

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

337 specifying the capabilities the C{GeodesicLine} 

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

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

340 and C{GeodesicLine.ArcPosition}. 

341 ''' 

342 return _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps) 

343 

344 

345def Geodesic_WGS84(): 

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

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

348 installed, otherwise an C{ImportError}. 

349 ''' 

350 return _wrapped.Geodesic_WGS84 

351 

352 

353class _wargs(object): # see also .vector2d._numpy 

354 '''(INTERNAL) C{geographiclib} caller, catching exceptions. 

355 ''' 

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

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

358 '''(INTERNAL) Yield C{tuple(B{args})} with any errors raised as L{NumPyError}. 

359 ''' 

360 try: 

361 yield args 

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

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

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

365 

366_wargs = _wargs() # PYCHOK singleton 

367 

368 

369# **) MIT License 

370# 

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

372# 

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

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

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

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

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

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

379# 

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

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

382# 

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

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

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

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

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

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

389# OTHER DEALINGS IN THE SOFTWARE.