Coverage for pygeodesy/geodesicw.py: 94%

132 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-06-07 08:37 -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 

19from pygeodesy.interns import NN, _DOT_, _UNDER 

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

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

22 _kWrapped, NAN, _xinstanceof # PYCHOK used! 

23from pygeodesy.lazily import _ALL_LAZY 

24from pygeodesy.named import callername, classname, unstr 

25from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple 

26from pygeodesy.props import Property, Property_RO 

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

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

29 

30from contextlib import contextmanager 

31# from math import fabs # from .karney 

32 

33__all__ = _ALL_LAZY.geodesicw 

34__version__ = '23.05.12' 

35 

36 

37class _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 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll 

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

161 ''' 

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

163 # and .HeightIDWkarney._distances 

164 if wrap: 

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

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

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

168 return fabs(r.a12) 

169 

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

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

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

173 ''' 

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

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

176 

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

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

179 ''' 

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

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

182 return self._Line13(t) 

183 

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

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

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

187 ''' 

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

189 

190 def _Line13(self, t): 

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

192 to reference point 3. 

193 ''' 

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

195 r.a13, r.s13 = t.a13, t.s13 

196 return r 

197 

198# Polygon = _Geodesic.Polygon 

199 

200 # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__ 

201 # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__ 

202 # Geodesic.Inverse.__doc__ = _Geodesic.Inverse.__doc__ 

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

204 # Geodesic.Line.__doc__ = _Geodesic.Line.__doc__ 

205 return Geodesic 

206 

207 @Property_RO # MCCABE 16 

208 def GeodesicLine(self): 

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

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

211 provided the latter is installed. 

212 ''' 

213 _GeodesicLine = self.geographiclib.GeodesicLine 

214 

215 class GeodesicLine(_GeodesicLine): 

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

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

218 ''' 

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

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

221 

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

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

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

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

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

227 specifying the capabilities the C{GeodesicLine} 

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

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

230 and C{GeodesicLine.ArcPosition}. 

231 ''' 

232 _xinstanceof(geodesic, _wrapped.Geodesic) 

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

234 _GeodesicLine.__init__(self, *args) 

235 

236 @Property_RO 

237 def a1(self): 

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

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

240 

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

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

243 ''' 

244 try: 

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

246 except AttributeError: 

247 return NAN # see .geodesicx.gxline._GeodesicLineExact 

248 

249 equatorarc = a1 

250 

251 def Arc(self): 

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

253 ''' 

254 return self.a13 

255 

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

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

258 ''' 

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

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

261 return GDict(d) 

262 

263 @Property_RO 

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

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

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

267 

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

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

270 ''' 

271 try: 

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

273 except AttributeError: 

274 return NAN # see .geodesicx.gxline._GeodesicLineExact 

275 

276 equatorazimuth = azi0 

277 

278 def Distance(self): 

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

280 ''' 

281 return self.s13 

282 

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

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

285 ''' 

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

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

288 return GDict(d) 

289 

290 # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__ 

291 # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__ 

292 return GeodesicLine 

293 

294 @Property_RO 

295 def Geodesic_WGS84(self): 

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

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

298 is installed, otherwise an C{ImportError}. 

299 ''' 

300 return _EWGS84.geodesic 

301 

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

303 

304 

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

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

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

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

309 

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

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

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

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

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

315 ''' 

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

317 

318 

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

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

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

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

323 

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

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

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

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

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

329 specifying the capabilities the C{GeodesicLine} 

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

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

332 and C{GeodesicLine.ArcPosition}. 

333 ''' 

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

335 

336 

337def Geodesic_WGS84(): 

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

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

340 installed, otherwise an C{ImportError}. 

341 ''' 

342 return _wrapped.Geodesic_WGS84 

343 

344 

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

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

347 ''' 

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

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

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

351 ''' 

352 try: 

353 yield args 

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

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

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

357 

358_wargs = _wargs() # PYCHOK singleton 

359 

360 

361# **) MIT License 

362# 

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

364# 

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

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

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

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

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

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

371# 

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

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

374# 

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

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

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

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

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

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

381# OTHER DEALINGS IN THE SOFTWARE.