Coverage for pygeodesy/geodesicw.py: 92%
133 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-10-04 12:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-10-04 12:08 -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.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!
33from contextlib import contextmanager
34# from math import fabs # from .utily
36__all__ = _ALL_LAZY.geodesicw
37__version__ = '23.08.20'
40class _gWrapped(_kWrapped):
41 ''''(INTERNAL) Wrapper for some of I{Karney}'s U{geographiclib
42 <https://PyPI.org/project/geographiclib>} classes.
43 '''
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
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
62 def __init__(self, a_ellipsoid=_EWGS84, f=None, name=NN): # PYCHOK signature
63 '''New I{wrapped} C{geodesic.Geodesic} instance.
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)
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)
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)
88 Area = _Geodesic.Polygon # like GeodesicExact.Area
90 @Property
91 def debug(self):
92 '''Get the C{debug} option (C{bool}).
93 '''
94 return bool(self._debug)
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
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)
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)
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)
122 @Property_RO
123 def ellipsoid(self):
124 '''Get this geodesic's ellipsoid (C{Ellipsoid[2]}).
125 '''
126 return self._E
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)
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
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
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)
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)
163 def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False):
164 '''Return the non-negative, I{angular} distance in C{degrees}.
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)
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))
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)
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)
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
206# Polygon = _Geodesic.Polygon
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
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
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.
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)
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)}.
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
257 equatorarc = a1
259 def Arc(self):
260 '''Return the arc length to reference point 3 (C{degrees} or C{NAN}).
261 '''
262 return self.a13
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)
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.
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
284 equatorazimuth = azi0
286 def Distance(self):
287 '''Return the distance to reference point 3 (C{meter} or C{NAN}).
288 '''
289 return self.s13
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)
298 # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__
299 # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__
300 return GeodesicLine
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
310_wrapped = _gWrapped() # PYCHOK singleton, .ellipsoids, .test/base.py
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}.
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)
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}.
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)
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
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)
366_wargs = _wargs() # PYCHOK singleton
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.