Coverage for pygeodesy/geodesicw.py: 93%
134 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-08-12 12:31 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-08-12 12:31 -0400
2# -*- coding: utf-8 -*-
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.
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.
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'''
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.errors import _xkwds # from .karney
20from pygeodesy.interns import NN, _DOT_, _under
21from pygeodesy.karney import _a_ellipsoid, _atan2d, Caps, Direct9Tuple, \
22 _EWGS84, fabs, GDict, GeodesicError, Inverse10Tuple, \
23 _kWrapped, NAN, _xinstanceof, _xkwds # PYCHOK used!
24from pygeodesy.lazily import _ALL_LAZY
25from pygeodesy.named import callername, classname, unstr
26from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple
27from pygeodesy.props import Property, Property_RO
28# from pygeodesy.streps import unstr # from .named
29from pygeodesy.utily import _Wrap, wrap360 # PYCHOK used!
31from contextlib import contextmanager
32# from math import fabs # from .karney
34__all__ = _ALL_LAZY.geodesicw
35__version__ = '23.08.05'
38class _gWrapped(_kWrapped):
39 ''''(INTERNAL) Wrapper for some of I{Karney}'s U{geographiclib
40 <https://PyPI.org/project/geographiclib>} classes.
41 '''
43 @Property_RO # MCCABE 24
44 def Geodesic(self):
45 '''Get the I{wrapped} C{geodesic.Geodesic} class from I{Karney}'s Python
46 U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},
47 provided the latter is installed.
48 '''
49 _Geodesic = self.geographiclib.Geodesic
50 # assert Caps._STD == _Geodesic.STANDARD
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
60 def __init__(self, a_ellipsoid=_EWGS84, f=None, name=NN): # PYCHOK signature
61 '''New I{wrapped} C{geodesic.Geodesic} instance.
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)
74 def ArcDirect(self, lat1, lon1, azi1, a12, outmask=Caps._STD):
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)
81 def ArcDirectLine(self, lat1, lon1, azi1, a12, caps=Caps._STD_LINE):
82 '''Return the C{_Geodesic.ArcDirectLine} as I{wrapped} C{GeodesicLine}.
83 '''
84 return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps)
86 Area = _Geodesic.Polygon # like GeodesicExact.Area
88 @Property
89 def debug(self):
90 '''Get the C{debug} option (C{bool}).
91 '''
92 return bool(self._debug)
94 @debug.setter # PYCHOK setter!
95 def debug(self, debug):
96 '''Set the C{debug} option (C{bool}) to include more
97 details in L{GDict} results.
98 '''
99 self._debug = Caps._DEBUG_ALL if debug else 0
101 def Direct(self, lat1, lon1, azi1, s12, outmask=Caps._STD):
102 '''Return the C{_Geodesic.Direct} result as L{GDict}.
103 '''
104 with _wargs(self, lat1, lon1, azi1, s12, outmask) as args:
105 d = _Geodesic.Direct(self, *args)
106 return GDict(d)
108 def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask
109 '''Return the destination lat, lon and reverse azimuth
110 in C{degrees} as L{Destination3Tuple}.
111 '''
112 d = self.Direct(lat1, lon1, azi1, s12, outmask=Caps._DIRECT3)
113 return Destination3Tuple(d.lat2, d.lon2, d.azi2)
115 def DirectLine(self, lat1, lon1, azi1, s12, caps=Caps._STD_LINE):
116 '''Return the C{_Geodesic.DirectLine} as I{wrapped} C{GeodesicLine}.
117 '''
118 return self._GenDirectLine(lat1, lon1, azi1, False, s12, caps)
120 @Property_RO
121 def ellipsoid(self):
122 '''Get this geodesic's ellipsoid (C{Ellipsoid[2]}).
123 '''
124 return self._E
126 @Property_RO
127 def f1(self): # in .css.CassiniSoldner.reset
128 '''Get the geodesic's ellipsoid's I{1 - flattening} (C{float}).
129 '''
130 return getattr(self, _under(Geodesic.f1.name), self.ellipsoid.f1)
132 def _GDictDirect(self, lat, lon, azi, arcmode, s12_a12, outmask=Caps._STD):
133 '''(INTERNAL) Get C{_Geodesic._GenDirect} result as C{GDict}.
134 '''
135 with _wargs(self, lat, lon, azi, arcmode, s12_a12, outmask) as args:
136 t = _Geodesic._GenDirect(self, *args)
137 return Direct9Tuple(t).toGDict() # *t
139 def _GDictInverse(self, lat1, lon1, lat2, lon2, outmask=Caps._STD):
140 '''(INTERNAL) Get C{_Geodesic._GenInverse} result as L{Inverse10Tuple}.
141 '''
142 with _wargs(self, lat1, lon1, lat2, lon2, outmask) as args:
143 t = _Geodesic._GenInverse(self, *args)
144 return Inverse10Tuple(t).toGDict(lon1=lon1, lon2=lon2) # *t
146 def _GenDirectLine(self, lat1, lon1, azi1, arcmode, s12_a12, *caps):
147 '''(INTERNAL) Invoked by C{_Geodesic.DirectLine} and C{-.ArcDirectLine},
148 returning the result as a I{wrapped} C{GeodesicLine}.
149 '''
150 with _wargs(self, lat1, lon1, azi1, arcmode, s12_a12, *caps) as args:
151 t = _Geodesic._GenDirectLine(self, *args)
152 return self._Line13(t)
154 def Inverse(self, lat1, lon1, lat2, lon2, outmask=Caps._STD):
155 '''Return the C{_Geodesic.Inverse} result as L{GDict}.
156 '''
157 with _wargs(self, lat1, lon1, lat2, lon2, outmask) as args:
158 d = _Geodesic.Inverse(self, *args)
159 return GDict(d)
161 def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False):
162 '''Return the non-negative, I{angular} distance in C{degrees}.
164 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
165 B{C{lat2}} and BC{lon2}} (C{bool}).
166 '''
167 # see .FrechetKarney.distance, .HausdorffKarney._distance
168 # and .HeightIDWkarney._distances
169 if wrap:
170 _, lat2, lon2 = _Wrap.latlon3(lat1, lat2, lon2, True) # _Geodesic.LONG_UNROLL
171 r = self.Inverse(lat1, lon1, lat2, lon2)
172 # XXX _Geodesic.DISTANCE needed for 'a12'?
173 return fabs(r.a12)
175 def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask
176 '''Return the distance in C{meter} and the forward and reverse
177 azimuths in C{degrees} as L{Distance3Tuple}.
178 '''
179 r = self.Inverse(lat1, lon1, lat2, lon2, outmask=Caps._INVERSE3)
180 return Distance3Tuple(r.s12, wrap360(r.azi1), wrap360(r.azi2))
182 def InverseLine(self, lat1, lon1, lat2, lon2, caps=Caps._STD_LINE):
183 '''Return the C{_Geodesic.InverseLine} as I{wrapped} C{GeodesicLine}.
184 '''
185 with _wargs(self, lat1, lon1, lat2, lon2, caps) as args:
186 t = _Geodesic.InverseLine(self, *args)
187 return self._Line13(t)
189 def Line(self, lat1, lon1, azi1, caps=Caps._STD_LINE):
190 '''Set up a I{wrapped} C{GeodesicLine} to compute several points
191 along a single, I{wrapped} (this) geodesic.
192 '''
193 return _wrapped.GeodesicLine(self, lat1, lon1, azi1, caps=caps)
195 def _Line13(self, t):
196 '''(INTERNAL) Wrap C{_GeodesicLine}, add distance and arc length
197 to reference point 3.
198 '''
199 rl = _wrapped.GeodesicLine(self, t.lat1, t.lon1, t.azi1, caps=t.caps,
200 salp1=t.salp1, calp1=t.calp1)
201 rl.a13, rl.s13 = t.a13, t.s13
202 return rl
204# Polygon = _Geodesic.Polygon
206 # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__
207 # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__
208 # Geodesic.Inverse.__doc__ = _Geodesic.Inverse.__doc__
209 # Geodesic.InverseLine.__doc__ = _Geodesic.InverseLinr.__doc__
210 # Geodesic.Line.__doc__ = _Geodesic.Line.__doc__
211 return Geodesic
213 @Property_RO # MCCABE 16
214 def GeodesicLine(self):
215 '''Get the I{wrapped} C{geodesicline.GeodesicLine} class from I{Karney}'s
216 Python U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},
217 provided the latter is installed.
218 '''
219 _GeodesicLine = self.geographiclib.GeodesicLine
221 class GeodesicLine(_GeodesicLine):
222 '''I{Wrapper} for I{Karney}'s Python U{geodesicline.GeodesicLine
223 <https://PyPI.org/project/geographiclib>} class.
224 '''
225 def __init__(self, geodesic, lat1, lon1, azi1, **caps): # caps, salp1=NAN, calp1=NAN
226 '''New I{wrapped} C{geodesicline.GeodesicLine} instance.
228 @arg geodesic: A I{wrapped} C{Geodesic} instance.
229 @arg lat1: Latitude of the first points (C{degrees}).
230 @arg lon1: Longitude of the first points (C{degrees}).
231 @arg azi1: Azimuth at the first points (compass C{degrees360}).
232 @kwarg caps: Optional, bit-or'ed combination of L{Caps} values
233 specifying the capabilities the C{GeodesicLine}
234 instance should possess, i.e., which quantities can
235 be returned by calls to C{GeodesicLine.Position}
236 and C{GeodesicLine.ArcPosition}.
237 '''
238 _xinstanceof(_wrapped.Geodesic, geodesic=geodesic)
239 with _wargs(self, geodesic, lat1, lon1, azi1, **caps) as args:
240 _GeodesicLine.__init__(self, *args, **caps)
242 @Property_RO
243 def a1(self):
244 '''Get the I{equatorial arc} (C{degrees}), the arc length between
245 the northward equatorial crossing and point C{(lat1, lon1)}.
247 @see: U{EquatorialArc<https://GeographicLib.SourceForge.io/
248 C++/doc/classGeographicLib_1_1GeodesicLine.html>}
249 '''
250 try:
251 return _atan2d(self._ssig1, self._csig1)
252 except AttributeError:
253 return NAN # see .geodesicx.gxline._GeodesicLineExact
255 equatorarc = a1
257 def Arc(self):
258 '''Return the arc length to reference point 3 (C{degrees} or C{NAN}).
259 '''
260 return self.a13
262 def ArcPosition(self, a12, outmask=Caps._STD):
263 '''Return the position at arc length C{B{a12} degrees} on this line.
264 '''
265 with _wargs(self, a12, outmask) as args:
266 d = _GeodesicLine.ArcPosition(self, *args)
267 return GDict(d)
269 @Property_RO
270 def azi0(self): # see .css.CassiniSoldner.forward4
271 '''Get the I{equatorial azimuth} (C{degrees}), the azimuth of the
272 geodesic line as it crosses the equator in a northward direction.
274 @see: U{EquatorialAzimuth<https://GeographicLib.SourceForge.io/
275 C++/doc/classGeographicLib_1_1GeodesicLine.html>}
276 '''
277 try:
278 return _atan2d(self._salp0, self._calp0)
279 except AttributeError:
280 return NAN # see .geodesicx.gxline._GeodesicLineExact
282 equatorazimuth = azi0
284 def Distance(self):
285 '''Return the distance to reference point 3 (C{meter} or C{NAN}).
286 '''
287 return self.s13
289 def Position(self, s12, outmask=Caps._STD):
290 '''Return the position at distance C{B{s12} meter} on this line.
291 '''
292 with _wargs(self, s12, outmask) as args:
293 d = _GeodesicLine.Position(self, *args)
294 return GDict(d)
296 # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__
297 # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__
298 return GeodesicLine
300 @Property_RO
301 def Geodesic_WGS84(self):
302 '''Get the I{wrapped} C{Geodesic(WGS84)} singleton, provided the
303 U{geographiclib<https://PyPI.org/project/geographiclib>} package
304 is installed, otherwise an C{ImportError}.
305 '''
306 return _EWGS84.geodesic
308_wrapped = _gWrapped() # PYCHOK singleton, .ellipsoids, .test/base.py
311def Geodesic(a_ellipsoid, f=None, name=NN):
312 '''Return a I{wrapped} C{geodesic.Geodesic} instance from I{Karney}'s
313 Python U{geographiclib<https://PyPI.org/project/geographiclib>},
314 provide the latter is installed, otherwise an C{ImportError}.
316 @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum})
317 or the equatorial radius I{a} of the ellipsoid (C{meter}).
318 @arg f: The flattening of the ellipsoid (C{scalar}), ignored if
319 B{C{a_ellipsoid}}) is not specified as C{meter}.
320 @kwarg name: Optional ellipsoid name (C{str}), ignored like B{C{f}}.
321 '''
322 return _wrapped.Geodesic(a_ellipsoid, f=f, name=name)
325def GeodesicLine(geodesic, lat1, lon1, azi1, caps=Caps._STD_LINE):
326 '''Return a I{wrapped} C{geodesicline.GeodesicLine} instance from I{Karney}'s
327 Python U{geographiclib<https://PyPI.org/project/geographiclib>}, provided
328 the latter is installed, otherwise an C{ImportError}.
330 @arg geodesic: A I{wrapped} L{Geodesic} instance.
331 @arg lat1: Latitude of the first points (C{degrees}).
332 @arg lon1: Longitude of the first points (C{degrees}).
333 @arg azi1: Azimuth at the first points (compass C{degrees360}).
334 @kwarg caps: Optional, bit-or'ed combination of L{Caps} values
335 specifying the capabilities the C{GeodesicLine}
336 instance should possess, i.e., which quantities can
337 be returned by calls to C{GeodesicLine.Position}
338 and C{GeodesicLine.ArcPosition}.
339 '''
340 return _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps)
343def Geodesic_WGS84():
344 '''Get the I{wrapped} L{Geodesic}C{(WGS84)} singleton, provided
345 U{geographiclib<https://PyPI.org/project/geographiclib>} is
346 installed, otherwise an C{ImportError}.
347 '''
348 return _wrapped.Geodesic_WGS84
351class _wargs(object): # see also .vector2d._numpy
352 '''(INTERNAL) C{geographiclib} caller, catching exceptions.
353 '''
354 @contextmanager # <https://www.python.org/dev/peps/pep-0343/> Examples
355 def __call__(self, inst, *args, **kwds):
356 '''(INTERNAL) Yield C{tuple(B{args})} with any errors raised as L{NumPyError}.
357 '''
358 try:
359 yield args
360 except (AttributeError, TypeError, ValueError) as x:
361 n = _DOT_(classname(inst), callername(up=3, underOK=True))
362 raise GeodesicError(unstr(n, *args, **kwds), cause=x)
364_wargs = _wargs() # PYCHOK singleton
367# **) MIT License
368#
369# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
370#
371# Permission is hereby granted, free of charge, to any person obtaining a
372# copy of this software and associated documentation files (the "Software"),
373# to deal in the Software without restriction, including without limitation
374# the rights to use, copy, modify, merge, publish, distribute, sublicense,
375# and/or sell copies of the Software, and to permit persons to whom the
376# Software is furnished to do so, subject to the following conditions:
377#
378# The above copyright notice and this permission notice shall be included
379# in all copies or substantial portions of the Software.
380#
381# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
382# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
383# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
384# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
385# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
386# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
387# OTHER DEALINGS IN THE SOFTWARE.