pygeodesy 24.6.24__py2.py3-none-any.whl → 24.7.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygeodesy/geodesicw.py CHANGED
@@ -8,8 +8,8 @@ that package is installed.
8
8
  The I{wrapped} class methods return a L{GDict} instance offering access to the C{dict} items
9
9
  either by C{key} or by C{attribute} name.
10
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
11
+ With env variable C{PYGEODESY_GEOGRAPHICLIB} left undefined or set to C{"2"}, this module and modules
12
+ L{pygeodesy.geodesici}, L{pygeodesy.geodesicx} and L{pygeodesy.karney} will use U{GeographicLib 2.0
13
13
  <https://GeographicLib.SourceForge.io/C++/doc/>} transcoding, otherwise C{1.52} or older.
14
14
  '''
15
15
 
@@ -18,17 +18,19 @@ from pygeodesy.constants import EPS, NAN, _EPSqrt as _TOL, _0_5
18
18
  from pygeodesy.datums import _earth_datum, _WGS84, _EWGS84
19
19
  # from pygeodesy.dms import F_D # from .latlonBase
20
20
  # from pygeodesy.ellipsoids import _EWGS84 # from .datums
21
- from pygeodesy.errors import IntersectionError, GeodesicError
21
+ from pygeodesy.errors import _AssertionError, GeodesicError, \
22
+ IntersectionError
22
23
  from pygeodesy.fsums import Fsum, Fmt, unstr
23
24
  from pygeodesy.internals import _dunder_nameof, _under
24
25
  from pygeodesy.interns import NN, _DOT_, _SPACE_, _to_, _too_
25
26
  from pygeodesy.karney import _atan2d, Caps, Direct9Tuple, GDict, \
26
- _kWrapped, Inverse10Tuple
27
+ Inverse10Tuple, _kWrapped
27
28
  from pygeodesy.latlonBase import LatLonBase as _LLB, F_D, Radius_
28
29
  from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
29
30
  from pygeodesy.named import callername, classname, _name1__, _name2__
30
31
  from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple
31
- from pygeodesy.props import Property, Property_RO, property_RO
32
+ from pygeodesy.props import Property, Property_RO, property_RO, \
33
+ property_ROver
32
34
  # from pygeodesy.streprs import Fmt, unstr # from .fsums
33
35
  # from pygeodesy.units import Radius_ # from .latlonBase
34
36
  from pygeodesy.utily import _unrollon, _Wrap, wrap360, fabs # PYCHOK used!
@@ -37,7 +39,7 @@ from contextlib import contextmanager
37
39
  # from math import fabs # from .utily
38
40
 
39
41
  __all__ = _ALL_LAZY.geodesicw
40
- __version__ = '24.06.24'
42
+ __version__ = '24.07.12'
41
43
 
42
44
  _plumb_ = 'plumb'
43
45
  _TRIPS = 65
@@ -48,14 +50,24 @@ class _gWrapped(_kWrapped):
48
50
  <https://PyPI.org/project/geographiclib>} classes.
49
51
  '''
50
52
 
51
- @Property_RO # MCCABE 24
53
+ @property_ROver # MCCABE 24
52
54
  def Geodesic(self):
53
55
  '''Get the I{wrapped} C{geodesic.Geodesic} class from I{Karney}'s Python
54
56
  U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},
55
57
  provided the latter is installed.
56
58
  '''
57
59
  _Geodesic = self.geographiclib.Geodesic
58
- # assert Caps._STD == _Geodesic.STANDARD
60
+ if not (Caps.LATITUDE == _Geodesic.LATITUDE and
61
+ Caps.LONGITUDE == _Geodesic.LONGITUDE and
62
+ Caps.AZIMUTH == _Geodesic.AZIMUTH and
63
+ Caps.DISTANCE == _Geodesic.DISTANCE and
64
+ Caps.DISTANCE_IN == _Geodesic.DISTANCE_IN and
65
+ Caps.REDUCEDLENGTH == _Geodesic.REDUCEDLENGTH and
66
+ Caps.GEODESICSCALE == _Geodesic.GEODESICSCALE and
67
+ Caps.AREA == _Geodesic.AREA and
68
+ Caps.ALL == _Geodesic.ALL):
69
+ raise _AssertionError(Caps=bin(Caps.ALL),
70
+ Geodesic=bin(_Geodesic.ALL))
59
71
 
60
72
  class Geodesic(_Geodesic):
61
73
  '''I{Wrapper} for I{Karney}'s Python U{geodesic.Geodesic
@@ -245,14 +257,16 @@ class _gWrapped(_kWrapped):
245
257
 
246
258
  # Polygon = _Geodesic.Polygon
247
259
 
260
+ WGS84 = None # _EWGS84.geodesicw recusion
261
+
248
262
  # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__
249
263
  # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__
250
264
  # Geodesic.Inverse.__doc__ = _Geodesic.Inverse.__doc__
251
265
  # Geodesic.InverseLine.__doc__ = _Geodesic.InverseLinr.__doc__
252
266
  # Geodesic.Line.__doc__ = _Geodesic.Line.__doc__
253
- return Geodesic
267
+ return Geodesic # overwrite property_ROver
254
268
 
255
- @Property_RO # MCCABE 16
269
+ @property_ROver # MCCABE 16
256
270
  def GeodesicLine(self):
257
271
  '''Get the I{wrapped} C{geodesicline.GeodesicLine} class from I{Karney}'s
258
272
  Python U{geographiclib<https://GitHub.com/geographiclib/geographiclib-python>},
@@ -416,48 +430,61 @@ class _gWrapped(_kWrapped):
416
430
 
417
431
  # GeodesicLine.ArcPosition.__doc__ = _GeodesicLine.ArcPosition.__doc__
418
432
  # GeodesicLine.Position.__doc__ = _GeodesicLine.Position.__doc__
419
- return GeodesicLine
433
+ return GeodesicLine # overwrite property_ROver
420
434
 
421
- @Property_RO
435
+ @property_ROver
422
436
  def Geodesic_WGS84(self):
423
437
  '''Get the I{wrapped} C{Geodesic(WGS84)} singleton, provided the
424
438
  U{geographiclib<https://PyPI.org/project/geographiclib>} package
425
439
  is installed, otherwise an C{ImportError}.
426
440
  '''
427
- return _EWGS84.geodesic
441
+ return _EWGS84.geodesicw # overwrite property_ROver
428
442
 
429
443
  _wrapped = _gWrapped() # PYCHOK singleton, .ellipsoids, .test/base.py
430
444
 
431
445
 
432
- def Geodesic(a_ellipsoid=_EWGS84, f=None, **name):
433
- '''Return a I{wrapped} C{geodesic.Geodesic} instance from I{Karney}'s
434
- Python U{geographiclib<https://PyPI.org/project/geographiclib>},
435
- provide the latter is installed, otherwise an C{ImportError}.
436
-
437
- @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum})
438
- or the equatorial radius I{a} of the ellipsoid (C{meter}).
439
- @arg f: The flattening of the ellipsoid (C{scalar}), required if
440
- B{C{a_ellipsoid}}) is C{meter}, ignored otherwise.
441
- @kwarg name: Optional C{B{name}=NN} (C{str}), ignored like B{C{f}}.
446
+ class Geodesic(_gWrapped): # overwritten by 1st instance
447
+ '''I{Wrapper} around I{Karney}'s class U{geographiclib.geodesic.Geodesic
448
+ <https://geographiclib.sourceforge.io/Python/doc/code.html>}.
442
449
  '''
443
- return _wrapped.Geodesic(a_ellipsoid, f=f, **name)
444
-
445
-
446
- def GeodesicLine(geodesic, lat1, lon1, azi1, caps=Caps._STD_LINE):
447
- '''Return a I{wrapped} C{geodesicline.GeodesicLine} instance from I{Karney}'s
448
- Python U{geographiclib<https://PyPI.org/project/geographiclib>}, provided
449
- the latter is installed, otherwise an C{ImportError}.
450
-
451
- @arg geodesic: A I{wrapped} L{Geodesic} instance.
452
- @arg lat1: Latitude of the first points (C{degrees}).
453
- @arg lon1: Longitude of the first points (C{degrees}).
454
- @arg azi1: Azimuth at the first points (compass C{degrees360}).
455
- @kwarg caps: Optional, bit-or'ed combination of L{Caps} values specifying
456
- the capabilities the C{GeodesicLine} instance should possess,
457
- i.e., which quantities can be returned by methods
458
- C{GeodesicLine.Position} and C{GeodesicLine.ArcPosition}.
450
+ def __new__(unused, a_ellipsoid=_EWGS84, f=None, **name):
451
+ '''Return a I{wrapped} C{geodesic.Geodesic} instance from I{Karney}'s
452
+ Python U{geographiclib<https://PyPI.org/project/geographiclib>},
453
+ provide the latter is installed, otherwise an C{ImportError}.
454
+
455
+ @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum})
456
+ or the equatorial radius I{a} of the ellipsoid (C{meter}).
457
+ @arg f: The flattening of the ellipsoid (C{scalar}), required if
458
+ B{C{a_ellipsoid}}) is C{meter}, ignored otherwise.
459
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
460
+ '''
461
+ g = _wrapped.Geodesic(a_ellipsoid, f=f, **name)
462
+ _MODS.geodesicw.Geodesic = g.__class__ # overwrite class
463
+ return g
464
+
465
+
466
+ class GeodesicLine(_gWrapped): # overwritten by 1st instance
467
+ '''I{Wrapper} around I{Karney}'s class U{geographiclib.geodesicline.GeodesicLine
468
+ <https://geographiclib.sourceforge.io/Python/doc/code.html>}.
459
469
  '''
460
- return _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps)
470
+ def __new__(unused, geodesic, lat1, lon1, azi1, caps=Caps._STD_LINE, **name):
471
+ '''Return a I{wrapped} C{geodesicline.GeodesicLine} instance from I{Karney}'s
472
+ Python U{geographiclib<https://PyPI.org/project/geographiclib>}, provided
473
+ the latter is installed, otherwise an C{ImportError}.
474
+
475
+ @arg geodesic: A I{wrapped} L{Geodesic} instance.
476
+ @arg lat1: Latitude of the first points (C{degrees}).
477
+ @arg lon1: Longitude of the first points (C{degrees}).
478
+ @arg azi1: Azimuth at the first points (compass C{degrees360}).
479
+ @kwarg caps: Optional, bit-or'ed combination of L{Caps} values specifying
480
+ the capabilities the C{GeodesicLine} instance should possess,
481
+ i.e., which quantities can be returned by methods
482
+ C{GeodesicLine.Position} and C{GeodesicLine.ArcPosition}.
483
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
484
+ '''
485
+ gl = _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps, **name)
486
+ _MODS.geodesicw.GeodesicLine = gl.__class__ # overwrite class
487
+ return gl
461
488
 
462
489
 
463
490
  def Geodesic_WGS84():
@@ -469,7 +496,7 @@ def Geodesic_WGS84():
469
496
 
470
497
 
471
498
  class _wargs(object): # see also .formy._idllmn6, .latlonBase._toCartesian3, .vector2d._numpy
472
- '''(INTERNAL) C{geographiclib} caller and exception mapper.
499
+ '''(INTERNAL) C{geographiclib} arguments and exception handler.
473
500
  '''
474
501
  @contextmanager # <https://www.Python.org/dev/peps/pep-0343/> Examples
475
502
  def __call__(self, inst, *args, **kwds):
@@ -553,7 +580,7 @@ def _Intersecant2(gl, lat0, lon0, radius, tol=_TOL, form=F_D): # MCCABE in LatL
553
580
 
554
581
  def _PlumbTo(gl, lat0, lon0, est=None, tol=_TOL):
555
582
  # (INTERNAL) Return the I{perpendicular} intersection of
556
- # a geodesic from C{(lat0, lon0)} and a geodesic (line).
583
+ # a geodesic line C{gl} and geodesic from C{(lat0, lon0)}.
557
584
  pl = _MODS.rhumb.bases._PseudoRhumbLine(gl)
558
585
  return pl.PlumbTo(lat0, lon0, exact=gl.geodesic,
559
586
  est=est, tol=tol)
@@ -23,7 +23,7 @@ from pygeodesy.karney import Caps, GeodesicError
23
23
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
24
24
 
25
25
  __all__ = _ALL_LAZY.geodesicx + _ALL_DOCS(Caps, GeodesicError)
26
- __version__ = '24.06.19'
26
+ __version__ = '24.07.09'
27
27
 
28
28
  # **) MIT License
29
29
  #
pygeodesy/geodesicx/gx.py CHANGED
@@ -63,7 +63,7 @@ from pygeodesy.utily import atan2d as _atan2d_reverse, _unrollon, _Wrap, wrap360
63
63
  from math import atan2, copysign, cos, degrees, fabs, radians, sqrt
64
64
 
65
65
  __all__ = ()
66
- __version__ = '24.06.24'
66
+ __version__ = '24.07.11'
67
67
 
68
68
  _MAXIT1 = 20
69
69
  _MAXIT2 = 10 + _MAXIT1 + MANT_DIG # MANT_DIG == C++ digits
@@ -212,7 +212,7 @@ class GeodesicExact(_GeodesicBase):
212
212
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
213
213
  Python U{Geodesic.ArcDirectLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
214
214
  '''
215
- return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps, **name)
215
+ return GeodesicLineExact(self, lat1, lon1, azi1, caps=caps, **name)._GenSet(self._debug, a12=a12)
216
216
 
217
217
  def Area(self, polyline=False, **name):
218
218
  '''Set up a L{GeodesicAreaExact} to compute area and
@@ -390,7 +390,7 @@ class GeodesicExact(_GeodesicBase):
390
390
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
391
391
  Python U{Geodesic.DirectLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
392
392
  '''
393
- return self._GenDirectLine(lat1, lon1, azi1, False, s12, caps, **name)
393
+ return GeodesicLineExact(self, lat1, lon1, azi1, caps=caps, **name)._GenSet(self._debug, s12=s12)
394
394
 
395
395
  def _dn(self, sbet, cbet): # in gxline._GeodesicLineExact.__init__
396
396
  '''(INTERNAL) Helper.
@@ -497,7 +497,7 @@ class GeodesicExact(_GeodesicBase):
497
497
  Cs = Caps
498
498
  if self._debug: # PYCHOK no cover
499
499
  outmask |= Cs._DEBUG_INVERSE & self._debug
500
- outmask &= Cs._OUT_MASK # incl. _SALPs_CALPs and _DEBUG_
500
+ outmask &= Cs._OUT_MASK # incl. _SALP_CALPs_ and _DEBUG_
501
501
  # compute longitude difference carefully (with _diff182):
502
502
  # result is in [-180, +180] but -180 is only for west-going
503
503
  # geodesics, +180 is for east-going and meridional geodesics
@@ -505,8 +505,10 @@ class GeodesicExact(_GeodesicBase):
505
505
  # see C{result} from geographiclib.geodesic.Inverse
506
506
  if (outmask & Cs.LONG_UNROLL): # == (lon1 + lon12) + lon12s
507
507
  r = GDict(lon1=lon1, lon2=fsumf_(lon1, lon12, lon12s))
508
- else:
508
+ elif (outmask & Cs.LONGITUDE):
509
509
  r = GDict(lon1=_norm180(lon1), lon2=_norm180(lon2))
510
+ else:
511
+ r = GDict()
510
512
  if _K_2_0: # GeographicLib 2.0
511
513
  # make longitude difference positive
512
514
  lon12, lon_ = _unsigned2(lon12)
@@ -535,7 +537,8 @@ class GeodesicExact(_GeodesicBase):
535
537
  # If really close to the equator, treat as on equator.
536
538
  lat1 = _around(_fix90(lat1))
537
539
  lat2 = _around(_fix90(lat2))
538
- r.set_(lat1=lat1, lat2=lat2)
540
+ if (outmask & Cs.LATITUDE):
541
+ r.set_(lat1=lat1, lat2=lat2)
539
542
  # Swap points so that point with higher (abs) latitude is
540
543
  # point 1. If one latitude is a NAN, then it becomes lat1.
541
544
  swap_ = fabs(lat1) < fabs(lat2) or isnan(lat2)
@@ -671,7 +674,7 @@ class GeodesicExact(_GeodesicBase):
671
674
  S12 = -S12
672
675
  r.set_(S12=unsigned0(S12))
673
676
 
674
- if (outmask & (Cs.AZIMUTH | Cs._SALPs_CALPs)):
677
+ if (outmask & (Cs.AZIMUTH | Cs._SALP_CALPs_)):
675
678
  if swap_:
676
679
  salp1, salp2 = salp2, salp1
677
680
  calp1, calp2 = calp2, calp1
@@ -683,7 +686,7 @@ class GeodesicExact(_GeodesicBase):
683
686
  if (outmask & Cs.AZIMUTH):
684
687
  r.set_(azi1=_atan2d(salp1, calp1),
685
688
  azi2=_atan2d_reverse(salp2, calp2, reverse=outmask & Cs.REVERSE2))
686
- if (outmask & Cs._SALPs_CALPs):
689
+ if (outmask & Cs._SALP_CALPs_):
687
690
  r.set_(salp1=salp1, calp1=calp1,
688
691
  salp2=salp2, calp2=calp2)
689
692
 
@@ -707,25 +710,13 @@ class GeodesicExact(_GeodesicBase):
707
710
  r = self._GDictDirect(lat1, lon1, azi1, arcmode, s12_a12, outmask)
708
711
  return r.toDirect9Tuple()
709
712
 
710
- def _GenDirectLine(self, lat1, lon1, azi1, arcmode, s12_a12, caps, **name):
711
- '''(INTERNAL) Helper for C{ArcDirectLine} and C{DirectLine}.
712
-
713
- @return: A L{GeodesicLineExact} instance.
714
- '''
715
- azi1 = _norm180(azi1)
716
- # guard against underflow in salp0. Also -0 is converted to +0.
717
- s, c = _sincos2d(_around(azi1))
718
- C = caps if arcmode else (caps | Caps.DISTANCE_IN)
719
- return _GeodesicLineExact(self, lat1, lon1, azi1, C,
720
- self._debug, s, c, **name)._GenSet(arcmode, s12_a12)
721
-
722
713
  def _GenInverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD):
723
714
  '''(INTERNAL) The general I{Inverse} geodesic calculation.
724
715
 
725
716
  @return: L{Inverse10Tuple}C{(a12, s12, salp1, calp1, salp2, calp2,
726
717
  m12, M12, M21, S12)}.
727
718
  '''
728
- r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask | Caps._SALPs_CALPs)
719
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask | Caps._SALP_CALPs_)
729
720
  return r.toInverse10Tuple()
730
721
 
731
722
  def _Inverse(self, ll1, ll2, wrap, **outmask):
@@ -770,7 +761,7 @@ class GeodesicExact(_GeodesicBase):
770
761
  # and .HeightIDWkarney._distances
771
762
  if wrap:
772
763
  _, lat2, lon2 = _Wrap.latlon3(lat1, lat2, lon2, True) # _Geodesic.LONG_UNROLL
773
- return fabs(self._GDictInverse(lat1, lon1, lat2, lon2, Caps._ANGLE_ONLY).a12)
764
+ return fabs(self._GDictInverse(lat1, lon1, lat2, lon2, Caps.EMPTY).a12) # a12 always
774
765
 
775
766
  def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask
776
767
  '''Return the distance in C{meter} and the forward and
@@ -815,11 +806,9 @@ class GeodesicExact(_GeodesicBase):
815
806
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
816
807
  '''
817
808
  Cs = Caps
818
- r = self._GDictInverse(lat1, lon1, lat2, lon2, Cs._SALPs_CALPs) # No need for AZIMUTH
819
- C = (caps | Cs.DISTANCE) if (caps & (Cs.DISTANCE_IN & Cs._OUT_MASK)) else caps
820
- azi1 = _atan2d(r.salp1, r.calp1)
821
- return _GeodesicLineExact(self, lat1, lon1, azi1, C, # ensure a12 is distance
822
- self._debug, r.salp1, r.calp1, **name)._GenSet(True, r.a12)
809
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Cs._SALP_CALPs_)
810
+ return GeodesicLineExact(self, lat1, lon1, None, caps=caps, _s_calp1=(r.salp1, r.calp1),
811
+ **name)._GenSet(self._debug, **r)
823
812
 
824
813
  def _InverseArea(self, _meridian, salp1, calp1, # PYCHOK 9 args
825
814
  salp2, calp2,
@@ -1130,7 +1119,7 @@ class GeodesicExact(_GeodesicBase):
1130
1119
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
1131
1120
  and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
1132
1121
  '''
1133
- return _GeodesicLineExact(self, lat1, lon1, azi1, caps, self._debug, **name)
1122
+ return GeodesicLineExact(self, lat1, lon1, azi1, caps=caps, **name)._GenSet(self._debug)
1134
1123
 
1135
1124
  @Property_RO
1136
1125
  def n(self):
@@ -1250,8 +1239,8 @@ class GeodesicExact(_GeodesicBase):
1250
1239
 
1251
1240
  @return: C{GeodesicExact} (C{str}).
1252
1241
  '''
1253
- return self._instr(props=(GeodesicExact.ellipsoid,),
1254
- C4order=self.C4order, **prec_sep_name)
1242
+ t = GeodesicExact.caps, GeodesicExact.ellipsoid
1243
+ return self._instr(props=t, C4order=self.C4order, **prec_sep_name)
1255
1244
 
1256
1245
 
1257
1246
  class GeodesicLineExact(_GeodesicLineExact):
@@ -1282,7 +1271,7 @@ class GeodesicLineExact(_GeodesicLineExact):
1282
1271
  if (caps & Caps.LINE_OFF): # copy to avoid updates
1283
1272
  geodesic = geodesic.copy(deep=False, name=_UNDER_(NN, geodesic.name))
1284
1273
  # _update_all(geodesic)
1285
- _GeodesicLineExact.__init__(self, geodesic, lat1, lon1, azi1, caps, 0, **name)
1274
+ _GeodesicLineExact.__init__(self, geodesic, lat1, lon1, azi1, caps, **name)
1286
1275
 
1287
1276
 
1288
1277
  def _Astroid(x, y):
@@ -38,8 +38,8 @@ from __future__ import division as _; del _ # PYCHOK semicolon
38
38
 
39
39
  # from pygeodesy.basics import _xinstanceof # _MODS
40
40
  from pygeodesy.constants import NAN, _EPSqrt as _TOL, _0_0, _1_0, \
41
- _180_0, _2__PI, _copysign_1_0
42
- # from pygeodesy.errors import _xError # _MODS
41
+ _180_0, _2__PI, _copysign_1_0, isfinite
42
+ from pygeodesy.errors import _xError, _xkwds_pop2
43
43
  from pygeodesy.fsums import fsumf_, fsum1f_
44
44
  from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
45
45
  _sincos12, _sin1cos2, \
@@ -47,7 +47,8 @@ from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
47
47
  # from pygeodesy.geodesicw import _Intersecant2 # _MODS
48
48
  from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS
49
49
  from pygeodesy.karney import _around, _atan2d, Caps, GDict, _fix90, \
50
- _K_2_0, _norm2, _norm180, _sincos2, _sincos2d
50
+ _K_2_0, _llz2gl, _norm2, _norm180, \
51
+ _sincos2, _sincos2d
51
52
  from pygeodesy.named import Property_RO, _update_all
52
53
  # from pygeodesy.props import Property_RO, _update_all # from .named
53
54
  from pygeodesy.utily import atan2d as _atan2d_reverse, sincos2
@@ -55,7 +56,7 @@ from pygeodesy.utily import atan2d as _atan2d_reverse, sincos2
55
56
  from math import atan2, cos, degrees, fabs, floor, radians, sin
56
57
 
57
58
  __all__ = ()
58
- __version__ = '24.06.24'
59
+ __version__ = '24.07.12'
59
60
 
60
61
  _glXs = [] # instances of C{[_]GeodesicLineExact} to be updated
61
62
 
@@ -92,23 +93,18 @@ class _GeodesicLineExact(_GeodesicBase):
92
93
  # _somg1 = _comg1 = NAN
93
94
  # _ssig1 = _csig1 = NAN
94
95
 
95
- def __init__(self, gX, lat1, lon1, azi1, caps, _debug, *salp1_calp1, **name):
96
+ def __init__(self, gX, lat1, lon1, azi1, caps, **name_):
96
97
  '''(INTERNAL) New C{[_]GeodesicLineExact} instance.
97
98
  '''
98
- _xGeodesicExact(gX=gX)
99
- Cs = Caps
100
- if _debug: # PYCHOK no cover
101
- self._debug |= _debug & Cs._DEBUG_ALL
102
- # _CapsBase.debug._update(self)
103
- if salp1_calp1:
104
- salp1, calp1 = salp1_calp1
105
- else:
99
+ # _xGeodesicExact(gX=gX)
100
+ if azi1 is None: # see GeodesicExact.InverseLine
101
+ (salp1, calp1), name_ = _xkwds_pop2(name_, _s_calp1=(_0_0, _1_0))
102
+ azi1 = _atan2d(salp1, calp1)
103
+ else: # guard against salp0 underflow, convert -0 to +0
106
104
  azi1 = _norm180(azi1)
107
- # guard against salp0 underflow,
108
- # also -0 is converted to +0
109
105
  salp1, calp1 = _sincos2d(_around(azi1))
110
- if name:
111
- self.name = name
106
+ if name_:
107
+ self.name = name_
112
108
 
113
109
  self._gX = gX # GeodesicExact only
114
110
  self._lat1 = lat1 = _fix90(lat1)
@@ -117,7 +113,7 @@ class _GeodesicLineExact(_GeodesicBase):
117
113
  self._salp1 = salp1
118
114
  self._calp1 = calp1
119
115
  # allow lat, azimuth and unrolling of lon
120
- self._caps = caps | Cs._LINE
116
+ self._caps = caps | Caps._AZIMUTH_LATITUDE_LONG_UNROLL
121
117
 
122
118
  sbet1, cbet1 = _sinf1cos2d(_around(lat1), gX.f1)
123
119
  self._dn1 = gX._dn(sbet1, cbet1)
@@ -142,11 +138,11 @@ class _GeodesicLineExact(_GeodesicBase):
142
138
  self._ssig1, self._csig1 = _norm2(sbet1, c) # sig1 in (-pi, pi]
143
139
  # _norm2(somg1, comg1) # no need to normalize!
144
140
  # _norm2(schi1?, cchi1) # no need to normalize!
145
- if not (caps & Cs.LINE_OFF):
141
+ if not (caps & Caps.LINE_OFF):
146
142
  _glXs.append(self)
147
- # no need to pre-compute other attrs based on _Caps.X. All are
148
- # Property_RO's, computed once and cached/memoized until reset
149
- # when C4order is changed or Elliptic function reset is invoked.
143
+ # no need to pre-compute other attrs for (caps & Caps.X). All are
144
+ # Property_RO's, computed once and cached/memoized until reset when
145
+ # arc, distance, C4order is changed or Elliptic function is reset.
150
146
 
151
147
  def __del__(self): # XXX use weakref?
152
148
  if _glXs: # may be empty or None
@@ -295,22 +291,18 @@ class _GeodesicLineExact(_GeodesicBase):
295
291
  C{lon1}, C{azi1} and arc length C{a12} always included,
296
292
  except when C{a12=NAN}.
297
293
  '''
298
-
299
- r = GDict(a12=NAN, s12=NAN) # note both a12 and s12, always
300
- if not (arcmode or self._caps_DISTANCE_IN): # PYCHOK no cover
301
- return r # Uninitialized or impossible distance requested
302
-
303
294
  Cs = Caps
304
- if self._debug: # PYCHOK no cover
305
- outmask |= self._debug & Cs._DEBUG_DIRECT_LINE
306
- outmask &= self._caps & Cs._OUT_MASK
307
-
295
+ if outmask:
296
+ outmask &= self._caps & Cs._OUT_MASK
308
297
  eF = self._eF
309
298
  gX = self.geodesic # ._gX
299
+ r = GDict(a12=NAN, s12=NAN) # both a12 and s12, always
310
300
 
311
- if arcmode:
312
- # s12_a12 is spherical arc length
313
- E2 = _0_0
301
+ if not isfinite(s12_a12):
302
+ # E2 = sig12 = ssig12 = csig12 = NAN
303
+ return r._toNAN(outmask)
304
+ elif arcmode: # s12_a12 is (spherical) arc length
305
+ r.set_(a12=s12_a12)
314
306
  sig12 = radians(s12_a12)
315
307
  if _K_2_0:
316
308
  ssig12, csig12 = sincos2(sig12) # utily, no NEG0
@@ -319,31 +311,25 @@ class _GeodesicLineExact(_GeodesicBase):
319
311
  a -= floor(a / _180_0) * _180_0 # 0 <= 0 < 180
320
312
  ssig12 = _0_0 if a == 0 else sin(sig12)
321
313
  csig12 = _0_0 if a == 90 else cos(sig12)
322
- else: # s12_a12 is distance
314
+ E2 = _0_0
315
+ elif self._caps_DISTANCE_IN: # s12_a12 is distance
323
316
  t = s12_a12 / self._E0b
324
317
  s, c = _sincos2(t) # tau12
325
318
  # tau2 = tau1 + tau12
326
319
  E2 = -eF.deltaEinv(*_sincos12(-s, c, *self._stau1_ctau1))
327
320
  sig12 = fsum1f_(self._E1, -E2, t) # == t - (E2 - E1)
328
321
  ssig12, csig12 = _sincos2(sig12)
329
-
330
- salp0, calp0 = self._salp0, self._calp0
331
- ssig1, csig1 = self._ssig1, self._csig1
322
+ r.set_(a12=degrees(sig12))
323
+ else: # uninitialized or impossible distance requested
324
+ return r
332
325
 
333
326
  # sig2 = sig1 + sig12
334
- ssig2, csig2 = _sincos12(-ssig12, csig12, ssig1, csig1)
335
- dn2 = eF.fDelta(ssig2, csig2)
336
- # sin(bet2) = cos(alp0) * sin(sig2) and
337
- # cbet2 = hypot(salp0, calp0 * csig2). Alt:
338
- # cbet2 = hypot(csig2, salp0 * ssig2)
339
- sbet2, cbet2 = _sin1cos2(calp0, salp0, csig2, ssig2)
340
- if cbet2 == 0: # salp0 = 0, csig2 = 0, break degeneracy
341
- cbet2 = csig2 = _TINY
342
- # tan(alp0) = cos(sig2) * tan(alp2)
343
- salp2 = salp0
344
- calp2 = calp0 * csig2 # no need to normalize
327
+ ssig1, csig1 = self._ssig1, self._csig1
328
+ ssig2, csig2 = t = _sincos12(-ssig12, csig12, ssig1, csig1)
329
+ dn2 = eF.fDelta(*t)
345
330
 
346
- if (outmask & Cs.DISTANCE):
331
+ if (outmask & Cs.DISTANCE):
332
+ outmask ^= Cs.DISTANCE
347
333
  if arcmode: # or f_0_01
348
334
  E2 = eF.deltaE(ssig2, csig2, dn2)
349
335
  # AB1 = _E0 * (E2 - _E1)
@@ -355,10 +341,34 @@ class _GeodesicLineExact(_GeodesicBase):
355
341
  s12 = s12_a12
356
342
  r.set_(s12=s12)
357
343
 
344
+ if not outmask: # all done, see ._GenSet
345
+ return r
346
+
347
+ if self._debug: # PYCHOK no cover
348
+ outmask |= self._debug & Cs._DEBUG_DIRECT_LINE
349
+
358
350
  if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
359
351
  r.set_(sig12=sig12, dn2=dn2, b=gX.b, e2=gX.e2, f1=gX.f1,
360
352
  E0b=self._E0b, E1=self._E1, E2=E2, eFk2=eF.k2, eFa2=eF.alpha2)
361
353
 
354
+ # sin(bet2) = cos(alp0) * sin(sig2) and
355
+ # cbet2 = hypot(salp0, calp0 * csig2). Alt:
356
+ # cbet2 = hypot(csig2, salp0 * ssig2)
357
+ salp0, calp0 = self._salp0, self._calp0
358
+ sbet2, cbet2 = _sin1cos2(calp0, salp0, csig2, ssig2)
359
+ if cbet2 == 0: # salp0 = 0, csig2 = 0, break degeneracy
360
+ cbet2 = csig2 = _TINY
361
+ # tan(alp0) = cos(sig2) * tan(alp2)
362
+ salp2 = salp0
363
+ calp2 = calp0 * csig2 # no need to normalize
364
+
365
+ if (outmask & Cs.AZIMUTH):
366
+ r.set_(azi2=_atan2d_reverse(salp2, calp2,
367
+ reverse=outmask & Cs.REVERSE2))
368
+
369
+ if (outmask & Cs.LATITUDE):
370
+ r.set_(lat2=_atan2d(sbet2, gX.f1 * cbet2))
371
+
362
372
  if (outmask & Cs.LONGITUDE):
363
373
  schi1 = self._somg1
364
374
  cchi1 = self._cchi1
@@ -372,7 +382,7 @@ class _GeodesicLineExact(_GeodesicBase):
372
382
  tchi2 = t * schi2
373
383
  chi12 = t * fsum1f_(_a(ssig1, csig1), -_a(ssig2, csig2),
374
384
  _a(tchi2, cchi2), -_a(tchi1, cchi1), sig12)
375
- lon2 = self.lon1 + degrees(chi12 - lam12)
385
+ lon2 = self.lon1 + degrees(chi12 - lam12)
376
386
  else:
377
387
  chi12 = atan2(*_sincos12(schi1, cchi1, schi2, cchi2))
378
388
  lon2 = _norm180(self._lon1_norm180 + _norm180(degrees(chi12 - lam12)))
@@ -381,12 +391,6 @@ class _GeodesicLineExact(_GeodesicBase):
381
391
  r.set_(ssig2=ssig2, chi12=chi12, H0e2_f1=self._H0e2_f1,
382
392
  csig2=csig2, lam12=lam12, H1=self._H1)
383
393
 
384
- if (outmask & Cs.LATITUDE):
385
- r.set_(lat2=_atan2d(sbet2, gX.f1 * cbet2))
386
-
387
- if (outmask & Cs.AZIMUTH):
388
- r.set_(azi2=_atan2d_reverse(salp2, calp2, reverse=outmask & Cs.REVERSE2))
389
-
390
394
  if (outmask & Cs._REDUCEDLENGTH_GEODESICSCALE):
391
395
  dn1 = self._dn1
392
396
  J12 = self._D0k2 * fsumf_(eF.deltaD(ssig2, csig2, dn2), -self._D1, sig12)
@@ -438,10 +442,9 @@ class _GeodesicLineExact(_GeodesicBase):
438
442
  S12 += gX.c2 * atan2(salp12, calp12)
439
443
  r.set_(S12=S12)
440
444
 
441
- r.set_(a12=s12_a12 if arcmode else degrees(sig12),
445
+ r.set_(azi1=_norm180(self.azi1),
442
446
  lat1=self.lat1, # == _fix90(lat1)
443
- lon1=self.lon1 if (outmask & Cs.LONG_UNROLL) else self._lon1_norm180,
444
- azi1=_norm180(self.azi1))
447
+ lon1=self.lon1 if (outmask & Cs.LONG_UNROLL) else self._lon1_norm180)
445
448
  return r
446
449
 
447
450
  def _GenPosition(self, arcmode, s12_a12, outmask):
@@ -453,14 +456,24 @@ class _GeodesicLineExact(_GeodesicBase):
453
456
  r = self._GDictPosition(arcmode, s12_a12, outmask)
454
457
  return r.toDirect9Tuple()
455
458
 
456
- def _GenSet(self, arcmode, s13_a13):
459
+ def _GenSet(self, debug, s12=None, a12=None, **llz2):
457
460
  '''(INTERNAL) Aka C++ C{GenSetDistance}.
458
461
  '''
459
- if arcmode:
460
- self.SetArc(s13_a13)
461
- else:
462
- self.SetDistance(s13_a13)
463
- return self # for gx.GeodesicExact.InverseLine and -._GenDirectLine
462
+ Cs = Caps
463
+ if debug: # PYCHOK no cover
464
+ self._debug |= debug & Cs._DEBUG_ALL
465
+ # _CapsBase.debug._update(self)
466
+ if s12 is None:
467
+ if a12 is None: # see GeodesicExact.Line
468
+ return self
469
+ s12 = self._GDictPosition(True, a12, outmask=Cs.DISTANCE).s12 if a12 else _0_0
470
+ elif a12 is None:
471
+ a12 = self._GDictPosition(False, s12, 0).a12 if s12 else _0_0
472
+ self._s13 = s12
473
+ self._a13 = a12
474
+ self._caps |= Cs.DISTANCE | Cs.DISTANCE_IN
475
+ # _update_all(self) # new, from GeodesicExact.*Line
476
+ return _llz2gl(self, **llz2)
464
477
 
465
478
  @Property_RO
466
479
  def geodesic(self):
@@ -498,7 +511,7 @@ class _GeodesicLineExact(_GeodesicBase):
498
511
  try:
499
512
  return _MODS.geodesicw._Intersecant2(self, lat0, lon0, radius, tol=tol)
500
513
  except (TypeError, ValueError) as x:
501
- raise _MODS.errors._xError(x, lat0, lon0, radius, tol=_TOL)
514
+ raise _xError(x, lat0, lon0, radius, tol=_TOL)
502
515
 
503
516
  @Property_RO
504
517
  def _H0e2_f1(self):
@@ -594,8 +607,7 @@ class _GeodesicLineExact(_GeodesicBase):
594
607
  the reference point or C{NAN}.
595
608
  '''
596
609
  if self._a13 != a13:
597
- self._a13 = a13
598
- self._s13 = self._GDictPosition(True, a13, Caps.DISTANCE).s12 # if a13 else _0_0
610
+ self._GenSet(0, a12=a13)
599
611
  _update_all(self)
600
612
  return self._s13
601
613
 
@@ -608,10 +620,9 @@ class _GeodesicLineExact(_GeodesicBase):
608
620
  and the reference point or C{NAN}.
609
621
  '''
610
622
  if self._s13 != s13:
611
- self._s13 = s13
612
- self._a13 = self._GDictPosition(False, s13, 0).a12 if s13 else _0_0
623
+ self._GenSet(0, s12=s13)
613
624
  _update_all(self)
614
- return self._a13 # NAN for GeodesicLineExact without Cap.DISTANCE_IN
625
+ return self._a13
615
626
 
616
627
  @Property_RO
617
628
  def _stau1_ctau1(self):
@@ -632,7 +643,7 @@ class _GeodesicLineExact(_GeodesicBase):
632
643
  @return: C{GeodesicLineExact} (C{str}).
633
644
  '''
634
645
  C = _GeodesicLineExact
635
- t = C.lat1, C.lon1, C.azi1, C.a13, C.s13, C.geodesic
646
+ t = C.lat1, C.lon1, C.azi1, C.a13, C.s13, C.caps, C.geodesic
636
647
  return self._instr(props=t, **prec_sep_name)
637
648
 
638
649