pygeodesy 25.8.25__py2.py3-none-any.whl → 25.10.10__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.
Files changed (48) hide show
  1. pygeodesy/__init__.py +21 -20
  2. pygeodesy/auxilats/__init__.py +1 -1
  3. pygeodesy/auxilats/auxAngle.py +4 -3
  4. pygeodesy/auxilats/auxily.py +1 -1
  5. pygeodesy/azimuthal.py +10 -12
  6. pygeodesy/basics.py +4 -4
  7. pygeodesy/booleans.py +25 -25
  8. pygeodesy/constants.py +59 -33
  9. pygeodesy/deprecated/functions.py +1 -0
  10. pygeodesy/dms.py +2 -2
  11. pygeodesy/ecef.py +3 -3
  12. pygeodesy/ellipsoidalExact.py +4 -4
  13. pygeodesy/ellipsoidalGeodSolve.py +3 -3
  14. pygeodesy/ellipsoids.py +52 -41
  15. pygeodesy/elliptic.py +9 -12
  16. pygeodesy/errors.py +18 -5
  17. pygeodesy/etm.py +10 -10
  18. pygeodesy/fmath.py +5 -3
  19. pygeodesy/geodesicx/__init__.py +1 -1
  20. pygeodesy/geodesicx/__main__.py +1 -0
  21. pygeodesy/geodesicx/gx.py +40 -46
  22. pygeodesy/geodesicx/gxarea.py +4 -4
  23. pygeodesy/geodesicx/gxbases.py +1 -5
  24. pygeodesy/geodesicx/gxline.py +43 -34
  25. pygeodesy/geodsolve.py +10 -17
  26. pygeodesy/geohash.py +6 -6
  27. pygeodesy/geoids.py +2 -2
  28. pygeodesy/heights.py +2 -2
  29. pygeodesy/internals.py +42 -19
  30. pygeodesy/karney.py +27 -26
  31. pygeodesy/ktm.py +1 -1
  32. pygeodesy/lazily.py +12 -11
  33. pygeodesy/lcc.py +5 -5
  34. pygeodesy/named.py +11 -14
  35. pygeodesy/rhumb/__init__.py +1 -1
  36. pygeodesy/rhumb/aux_.py +1 -1
  37. pygeodesy/rhumb/bases.py +7 -8
  38. pygeodesy/rhumb/ekx.py +9 -9
  39. pygeodesy/solveBase.py +14 -3
  40. pygeodesy/sphericalTrigonometry.py +4 -4
  41. pygeodesy/streprs.py +9 -9
  42. pygeodesy/trf.py +4 -4
  43. pygeodesy/utily.py +200 -159
  44. pygeodesy/vector3dBase.py +6 -6
  45. {pygeodesy-25.8.25.dist-info → pygeodesy-25.10.10.dist-info}/METADATA +21 -20
  46. {pygeodesy-25.8.25.dist-info → pygeodesy-25.10.10.dist-info}/RECORD +48 -48
  47. {pygeodesy-25.8.25.dist-info → pygeodesy-25.10.10.dist-info}/WHEEL +0 -0
  48. {pygeodesy-25.8.25.dist-info → pygeodesy-25.10.10.dist-info}/top_level.txt +0 -0
pygeodesy/geodesicx/gx.py CHANGED
@@ -65,7 +65,7 @@ from pygeodesy.utily import atan2, atan2d as _atan2d_reverse, _unrollon, \
65
65
  from math import copysign, cos, degrees, fabs, radians, sqrt
66
66
 
67
67
  __all__ = ()
68
- __version__ = '25.06.01'
68
+ __version__ = '25.09.16'
69
69
 
70
70
  _MAXIT1 = 20
71
71
  _MAXIT2 = 10 + _MAXIT1 + MANT_DIG # MANT_DIG == C++ digits
@@ -134,7 +134,7 @@ class GeodesicExact(_GeodesicBase):
134
134
  _datum = _WGS84
135
135
  _nC4 = 30 # default C4order
136
136
 
137
- def __init__(self, a_ellipsoid=_EWGS84, f=None, C4order=None, **name_C4Order): # for backward compatibility
137
+ def __init__(self, a_ellipsoid=_EWGS84, f=None, caps=None, C4order=None, **name_C4Order): # for backward compatibility
138
138
  '''New L{GeodesicExact} instance.
139
139
 
140
140
  @arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum}) or
@@ -142,6 +142,8 @@ class GeodesicExact(_GeodesicBase):
142
142
  conventionally in C{meter}), see B{C{f}}.
143
143
  @arg f: The flattening of the ellipsoid (C{scalar}) if B{C{a_ellipsoid}}
144
144
  is specified as C{scalar}.
145
+ @kwarg caps: Optional default capabilities for L{GeodesicLineExact} instances,
146
+ use C{B{caps}=Caps.NONFINITONAN} to handle nonfinites silently.
145
147
  @kwarg C4order: Optional series expansion order (C{int}), see property
146
148
  L{C4order}, default C{30}.
147
149
  @kwarg name_C4Order: Optional C{B{name}=NN} (C{str}) and the DEPRECATED
@@ -154,9 +156,11 @@ class GeodesicExact(_GeodesicBase):
154
156
  if name:
155
157
  self.name = name
156
158
  else:
157
- name = {} # name_C4Order
159
+ name = name_C4Order # no name
158
160
 
159
161
  _earth_datum(self, a_ellipsoid, f=f, **name)
162
+ if caps:
163
+ self._caps |= caps & Caps._OUT_MASK
160
164
  if C4order: # XXX private copy, always?
161
165
  self.C4order = C4order
162
166
 
@@ -184,7 +188,7 @@ class GeodesicExact(_GeodesicBase):
184
188
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
185
189
  and Python U{Geodesic.ArcDirect<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
186
190
  '''
187
- return self._GDictDirect(lat1, lon1, azi1, True, a12, outmask)
191
+ return self._GDictDirect(lat1, lon1, azi1, True, a12, outmask=outmask)
188
192
 
189
193
  def ArcDirectLine(self, lat1, lon1, azi1, a12, caps=Caps.ALL, **name):
190
194
  '''Define a L{GeodesicLineExact} in terms of the I{direct} geodesic problem and as arc length.
@@ -193,10 +197,7 @@ class GeodesicExact(_GeodesicBase):
193
197
  @arg lon1: Longitude of the first point (C{degrees}).
194
198
  @arg azi1: Azimuth at the first point (compass C{degrees}).
195
199
  @arg a12: Arc length between the points (C{degrees}), can be negative.
196
- @kwarg caps: Bit-or'ed combination of L{Caps<pygeodesy.karney.Caps>} values
197
- specifying the capabilities the L{GeodesicLineExact} instance
198
- should possess, i.e., which quantities can be returned by methods
199
- L{GeodesicLineExact.Position} and L{GeodesicLineExact.ArcPosition}.
200
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
200
201
  @kwarg name: Optional C{B{name}=NN} (C{str}).
201
202
 
202
203
  @return: A L{GeodesicLineExact} instance.
@@ -347,7 +348,7 @@ class GeodesicExact(_GeodesicBase):
347
348
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
348
349
  and Python U{Geodesic.Direct<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
349
350
  '''
350
- return self._GDictDirect(lat1, lon1, azi1, False, s12, outmask)
351
+ return self._GDictDirect(lat1, lon1, azi1, False, s12, outmask=outmask)
351
352
 
352
353
  def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask
353
354
  '''Return the destination lat, lon and reverse azimuth
@@ -355,7 +356,7 @@ class GeodesicExact(_GeodesicBase):
355
356
 
356
357
  @return: L{Destination3Tuple}C{(lat, lon, final)}.
357
358
  '''
358
- r = self._GDictDirect(lat1, lon1, azi1, False, s12, Caps._AZIMUTH_LATITUDE_LONGITUDE)
359
+ r = self._GDictDirect(lat1, lon1, azi1, False, s12, outmask=Caps._AZIMUTH_LATITUDE_LONGITUDE)
359
360
  return Destination3Tuple(r.lat2, r.lon2, r.azi2) # no iteration
360
361
 
361
362
  def _DirectLine(self, ll1, azi12, s12=0, **caps_name):
@@ -370,10 +371,7 @@ class GeodesicExact(_GeodesicBase):
370
371
  @arg lon1: Longitude of the first point (C{degrees}).
371
372
  @arg azi1: Azimuth at the first point (compass C{degrees}).
372
373
  @arg s12: Distance between the points (C{meter}), can be negative.
373
- @kwarg caps: Bit-or'ed combination of L{Caps<pygeodesy.karney.Caps>} values
374
- specifying the capabilities the L{GeodesicLineExact} instance
375
- should possess, i.e., which quantities can be returned by methods
376
- L{GeodesicLineExact.Position}.
374
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
377
375
  @kwarg name: Optional C{B{name}=NN} (C{str}).
378
376
 
379
377
  @return: A L{GeodesicLineExact} instance.
@@ -487,7 +485,7 @@ class GeodesicExact(_GeodesicBase):
487
485
  @return: A L{GDict} ...
488
486
  '''
489
487
  C = outmask if arcmode else (outmask | Caps.DISTANCE_IN)
490
- glX = self.Line(lat1, lon1, azi1, C | Caps.LINE_OFF)
488
+ glX = self.Line(lat1, lon1, azi1, caps=C | Caps.LINE_OFF)
491
489
  return glX._GDictPosition(arcmode, s12_a12, outmask)
492
490
 
493
491
  def _GDictInverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD): # MCCABE 33, 41 vars
@@ -495,22 +493,23 @@ class GeodesicExact(_GeodesicBase):
495
493
 
496
494
  @return: A L{GDict} ...
497
495
  '''
498
- Cs = Caps
496
+ r, Cs = GDict(), Caps
499
497
  if self._debug: # PYCHOK no cover
500
498
  outmask |= Cs._DEBUG_INVERSE & self._debug
499
+ outmask |= self.caps
501
500
  outmask &= Cs._OUT_MASK # incl. _SALP_CALPs_ and _DEBUG_
502
- toNAN = _toNAN(outmask, lat1, lon1, lat2, lon2)
501
+ if _toNAN(outmask, lat1, lon1, lat2, lon2):
502
+ return r._toNAN(outmask, lat1=lat1, lon1=lon1, lat2=lat2, lon2=lon2)
503
+
503
504
  # compute longitude difference carefully (with _diff182):
504
505
  # result is in [-180, +180] but -180 is only for west-going
505
506
  # geodesics, +180 is for east-going and meridional geodesics
506
507
  lon12, lon12s = _diff182(lon1, lon2)
507
508
  # see C{result} from geographiclib.geodesic.Inverse
508
509
  if (outmask & Cs.LONG_UNROLL): # == (lon1 + lon12) + lon12s
509
- r = GDict(lon1=lon1, lon2=fsumf_(lon1, lon12, lon12s))
510
+ r.set_(lon1=lon1, lon2=fsumf_(lon1, lon12, lon12s))
510
511
  elif (outmask & Cs.LONGITUDE):
511
- r = GDict(lon1=_norm180(lon1), lon2=_norm180(lon2))
512
- else:
513
- r = GDict()
512
+ r.set_(lon1=_norm180(lon1), lon2=_norm180(lon2))
514
513
  if _K_2_0: # GeographicLib 2.0
515
514
  # make longitude difference positive
516
515
  lon12, lon_ = _unsigned2(lon12)
@@ -543,7 +542,7 @@ class GeodesicExact(_GeodesicBase):
543
542
  r.set_(lat1=lat1, lat2=lat2)
544
543
  # Swap points so that point with higher (abs) latitude is
545
544
  # point 1. If one latitude is a NAN, then it becomes lat1.
546
- swap_ = fabs(lat1) < fabs(lat2) or isnan(lat2)
545
+ swap_ = isnan(lat2) or fabs(lat1) < fabs(lat2)
547
546
  if swap_:
548
547
  lat1, lat2 = lat2, lat1
549
548
  lon_ = not lon_
@@ -580,7 +579,7 @@ class GeodesicExact(_GeodesicBase):
580
579
  p = _PDict(sbet1=sbet1, cbet1=cbet1, dn1=self._dn(sbet1, cbet1),
581
580
  sbet2=sbet2, cbet2=cbet2, dn2=self._dn(sbet2, cbet2))
582
581
 
583
- _meridian = _b = True # i.e. not meridian, not b
582
+ _meridian = _b = True # i.e. meridian = b = False
584
583
  if lat1 == -90 or slam12 == 0:
585
584
  # Endpoints are on a single full meridian,
586
585
  # so the geodesic might lie on a meridian.
@@ -597,17 +596,18 @@ class GeodesicExact(_GeodesicBase):
597
596
  # echo 20.001 0 20.001 0 | GeodSolve -i
598
597
  # In fact, we will have sig12 > PI/2 for meridional
599
598
  # geodesic which is not a shortest path.
600
- if m12x >= 0 or sig12 < _1_0:
599
+ if sig12 < _TOL2 or m12x >= 0: # GeographicLib 2.5.1
601
600
  # Need at least 2 to handle 90 0 90 180
602
601
  # Prevent negative s12 or m12 from geographiclib 1.52
603
602
  if sig12 < _TINY3 or (sig12 < _TOL0 and (s12x < 0 or m12x < 0)):
604
603
  sig12 = m12x = s12x = _0_0
605
- else:
606
- _b = False # apply .b to s12x, m12x
607
- _meridian = False
604
+ # else:
605
+ # m12x *= self.b
606
+ # s12x *= self.b
607
+ _meridian = _b = False # i.e. meridian = b = True
608
608
  C = 1
609
- # else: # m12 < 0, prolate and too close to anti-podal
610
- # _meridian = True
609
+ # else: # m12 < 0, prolate and too close to anti-podal
610
+ # _meridian = True # i.e. meridian = False
611
611
  a12 = _0_0 # if _b else degrees(sig12)
612
612
 
613
613
  if _meridian:
@@ -651,7 +651,7 @@ class GeodesicExact(_GeodesicBase):
651
651
  if (outmask & Cs.AREA):
652
652
  somg12, comg12 = _sincos2(lam12 / (self.f1 * dnm))
653
653
 
654
- else: # _meridian is False
654
+ else: # _meridian is False, i.e. meridian is True
655
655
  somg12 = comg12 = NAN
656
656
 
657
657
  r.set_(a12=a12 if _b else degrees(sig12)) # in [0, 180]
@@ -702,8 +702,7 @@ class GeodesicExact(_GeodesicBase):
702
702
  p.update(r) # r overrides p
703
703
  r = p.toGDict()
704
704
 
705
- r = self._iter2tion(r, **p)
706
- return r._toNAN(outmask) if toNAN else r
705
+ return self._iter2tion(r, **p)
707
706
 
708
707
  def _GenDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask=Caps.STANDARD):
709
708
  '''(INTERNAL) The general I{Inverse} geodesic calculation.
@@ -711,7 +710,7 @@ class GeodesicExact(_GeodesicBase):
711
710
  @return: L{Direct9Tuple}C{(a12, lat2, lon2, azi2,
712
711
  s12, m12, M12, M21, S12)}.
713
712
  '''
714
- r = self._GDictDirect(lat1, lon1, azi1, arcmode, s12_a12, outmask)
713
+ r = self._GDictDirect(lat1, lon1, azi1, arcmode, s12_a12, outmask=outmask)
715
714
  return r.toDirect9Tuple()
716
715
 
717
716
  def _GenInverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD):
@@ -720,7 +719,7 @@ class GeodesicExact(_GeodesicBase):
720
719
  @return: L{Inverse10Tuple}C{(a12, s12, salp1, calp1, salp2, calp2,
721
720
  m12, M12, M21, S12)}.
722
721
  '''
723
- r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask | Caps._SALP_CALPs_)
722
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask=outmask | Caps._SALP_CALPs_)
724
723
  return r.toInverse10Tuple()
725
724
 
726
725
  def _Inverse(self, ll1, ll2, wrap, **outmask):
@@ -753,7 +752,7 @@ class GeodesicExact(_GeodesicBase):
753
752
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
754
753
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
755
754
  '''
756
- return self._GDictInverse(lat1, lon1, lat2, lon2, outmask)
755
+ return self._GDictInverse(lat1, lon1, lat2, lon2, outmask=outmask)
757
756
 
758
757
  def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False):
759
758
  '''Return the non-negative, I{angular} distance in C{degrees}.
@@ -765,7 +764,8 @@ class GeodesicExact(_GeodesicBase):
765
764
  # and .HeightIDWkarney._distances
766
765
  if wrap:
767
766
  _, lat2, lon2 = _Wrap.latlon3(lat1, lat2, lon2, True) # _Geodesic.LONG_UNROLL
768
- return fabs(self._GDictInverse(lat1, lon1, lat2, lon2, Caps.EMPTY).a12) # a12 always
767
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask=Caps.EMPTY)
768
+ return fabs(r.a12) # a12 always
769
769
 
770
770
  def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask
771
771
  '''Return the distance in C{meter} and the forward and
@@ -773,7 +773,7 @@ class GeodesicExact(_GeodesicBase):
773
773
 
774
774
  @return: L{Distance3Tuple}C{(distance, initial, final)}.
775
775
  '''
776
- r = self._GDictInverse(lat1, lon1, lat2, lon2, Caps.AZIMUTH_DISTANCE)
776
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask=Caps.AZIMUTH_DISTANCE)
777
777
  return Distance3Tuple(r.s12, wrap360(r.azi1), wrap360(r.azi2),
778
778
  iteration=r.iteration)
779
779
 
@@ -791,10 +791,7 @@ class GeodesicExact(_GeodesicBase):
791
791
  @arg lon1: Longitude of the first point (C{degrees}).
792
792
  @arg lat2: Latitude of the second point (C{degrees}).
793
793
  @arg lon2: Longitude of the second point (C{degrees}).
794
- @kwarg caps: Bit-or'ed combination of L{Caps<pygeodesy.karney.Caps>} values
795
- specifying the capabilities the L{GeodesicLineExact} instance
796
- should possess, i.e., which quantities can be returned by methods
797
- L{GeodesicLineExact.Position} and L{GeodesicLineExact.ArcPosition}.
794
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
798
795
  @kwarg name: Optional C{B{name}=NN} (C{str}).
799
796
 
800
797
  @return: A L{GeodesicLineExact} instance.
@@ -808,7 +805,7 @@ class GeodesicExact(_GeodesicBase):
808
805
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
809
806
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
810
807
  '''
811
- r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Caps._SALP_CALPs_)
808
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask=caps | Caps._SALP_CALPs_)
812
809
  return GeodesicLineExact(self, lat1, lon1, None, caps=caps, _s_calp1=(r.salp1, r.calp1),
813
810
  **name)._GenSet(self._debug, **r)
814
811
 
@@ -1106,10 +1103,7 @@ class GeodesicExact(_GeodesicBase):
1106
1103
  @arg lat1: Latitude of the first point (C{degrees}).
1107
1104
  @arg lon1: Longitude of the first point (C{degrees}).
1108
1105
  @arg azi1: Azimuth at the first point (compass C{degrees}).
1109
- @kwarg caps: Bit-or'ed combination of L{Caps<pygeodesy.karney.Caps>} values
1110
- specifying the capabilities the L{GeodesicLineExact} instance
1111
- should possess, i.e., which quantities can be returned by methods
1112
- L{GeodesicLineExact.Position} and L{GeodesicLineExact.ArcPosition}.
1106
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
1113
1107
  @kwarg name: Optional C{B{name}=NN} (C{str}).
1114
1108
 
1115
1109
  @return: A L{GeodesicLineExact} instance.
@@ -31,7 +31,7 @@ from pygeodesy.props import Property, Property_RO, property_RO
31
31
  from math import fmod as _fmod
32
32
 
33
33
  __all__ = ()
34
- __version__ = '25.06.04'
34
+ __version__ = '25.09.16'
35
35
 
36
36
 
37
37
  class GeodesicAreaExact(_NamedBase):
@@ -74,7 +74,7 @@ class GeodesicAreaExact(_NamedBase):
74
74
  self._g_gX = g = geodesic
75
75
  # use the class-level Caps since the values
76
76
  # differ between GeodesicExact and Geodesic
77
- self._mask = g.DISTANCE | g.LATITUDE | g.LONGITUDE
77
+ self._mask = g.DISTANCE | g.LATITUDE | g.LONGITUDE
78
78
  self._Peri = _Accumulator(name='_Peri')
79
79
  if not polyline: # perimeter and area
80
80
  self._mask |= g.AREA | g.LONG_UNROLL
@@ -188,7 +188,7 @@ class GeodesicAreaExact(_NamedBase):
188
188
  '''(INTERNAL) Edge helper.
189
189
  '''
190
190
  lon1 = self.lon1
191
- r = self._g_gX._GDictDirect(self.lat1, lon1, azi, False, s, self._mask)
191
+ r = self._g_gX._GDictDirect(self.lat1, lon1, azi, False, s, outmask=self._mask)
192
192
  if self._Area: # aka transitDirect
193
193
  # Count crossings of prime meridian exactly as
194
194
  # int(ceil(lon2 / 360)) - int(ceil(lon1 / 360))
@@ -219,7 +219,7 @@ class GeodesicAreaExact(_NamedBase):
219
219
  def _Inverse(self, lat1, lon1, lat2, lon2):
220
220
  '''(INTERNAL) Point helper.
221
221
  '''
222
- r = self._g_gX._GDictInverse(lat1, lon1, lat2, lon2, self._mask)
222
+ r = self._g_gX._GDictInverse(lat1, lon1, lat2, lon2, outmask=self._mask)
223
223
  if self._Area: # aka transit
224
224
  # count crossings of prime meridian as +1 or -1
225
225
  # if in east or west direction, otherwise 0
@@ -150,11 +150,7 @@ def _sinf1cos2d(lat, f1):
150
150
  def _toNAN(outmask, *args):
151
151
  '''(INTERNAL) Is any C{arg} not finite?
152
152
  '''
153
- if (outmask & _CapsBase.NONFINITONAN): # Caps.NONFINITONAN
154
- for arg in args:
155
- if not isfinite(arg):
156
- return True
157
- return False
153
+ return bool(outmask & _CapsBase.NONFINITONAN) and not all(map(isfinite, args))
158
154
 
159
155
 
160
156
  def _xnC4(**name_nC4):
@@ -38,9 +38,11 @@ from __future__ import division as _; del _ # noqa: E702 ;
38
38
 
39
39
  # from pygeodesy.basics import _xinstanceof # _MODS
40
40
  from pygeodesy.constants import NAN, _EPSqrt as _TOL, \
41
- _0_0, _1_0, _180_0, _2__PI, \
42
- _copysign_1_0, isfinite
41
+ _copysign_1_0, isfinite, \
42
+ _0_0, _1_0, _180_0, _360_0, \
43
+ _2__PI # PYCHOK used!
43
44
  from pygeodesy.errors import _xError, _xkwds_pop2
45
+ # from pygeodesy.fmath import fremainder # from .karney
44
46
  from pygeodesy.fsums import fsumf_, fsum1f_
45
47
  from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
46
48
  _sincos12, _sin1cos2, \
@@ -49,14 +51,14 @@ from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
49
51
  from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS
50
52
  from pygeodesy.karney import _around, _atan2d, Caps, GDict, _fix90, \
51
53
  _K_2_0, _llz2gl, _norm2, _norm180, \
52
- _sincos2, _sincos2d
54
+ _sincos2, _sincos2d, fremainder
53
55
  from pygeodesy.props import Property_RO, property_ROver, _update_all
54
56
  from pygeodesy.utily import atan2, atan2d as _atan2d_reverse, sincos2
55
57
 
56
- from math import cos, degrees, fabs, floor, radians, sin
58
+ from math import degrees, fabs, radians
57
59
 
58
60
  __all__ = ()
59
- __version__ = '25.05.28'
61
+ __version__ = '25.09.09'
60
62
 
61
63
  _glXs = [] # instances of C{[_]GeodesicLineExact} to be updated
62
64
 
@@ -83,6 +85,7 @@ class _GeodesicLineExact(_GeodesicBase):
83
85
  '''
84
86
  _a13 = _s13 = NAN
85
87
  # _azi1 = _0_0
88
+ _caps = Caps._AZIMUTH_LATITUDE_LONG_UNROLL
86
89
  # _cchi1 = NAN
87
90
  # _dn1 = NAN
88
91
  _gX = None # Exact only
@@ -106,7 +109,6 @@ class _GeodesicLineExact(_GeodesicBase):
106
109
  salp1, calp1 = _sincos2d(_around(azi1))
107
110
  if name_:
108
111
  self.name = name_
109
- self._toNAN = _toNAN(caps, lat1, lon1, azi1, salp1, calp1)
110
112
 
111
113
  self._gX = gX # GeodesicExact only
112
114
  self._lat1 = lat1 = _fix90(lat1)
@@ -115,7 +117,9 @@ class _GeodesicLineExact(_GeodesicBase):
115
117
  self._salp1 = salp1
116
118
  self._calp1 = calp1
117
119
  # allow lat, azimuth and unrolling of lon
118
- self._caps = caps | Caps._AZIMUTH_LATITUDE_LONG_UNROLL
120
+ self._caps |= caps | gX.caps # | Caps._AZIMUTH_LATITUDE_LONG_UNROLL
121
+
122
+ self._toNAN = _toNAN(self._caps, lat1, lon1, azi1, salp1, calp1)
119
123
 
120
124
  sbet1, cbet1 = _sinf1cos2d(_around(lat1), gX.f1)
121
125
  self._dn1 = gX._dn(sbet1, cbet1)
@@ -282,8 +286,11 @@ class _GeodesicLineExact(_GeodesicBase):
282
286
  def _eF(self):
283
287
  '''(INTERNAL) Cached/memoized C{Elliptic} function.
284
288
  '''
285
- # see .gx.GeodesicExact._ef_reset_k2
286
- return _MODS.elliptic.Elliptic(k2=-self._k2, alpha2=-self.geodesic.ep2)
289
+ e = _MODS.elliptic
290
+ try: # see .gx.GeodesicExact._ef_reset_k2
291
+ return e.Elliptic(k2=-self._k2, alpha2=-self.geodesic.ep2)
292
+ except e.EllipticError: # nonfinite
293
+ return None
287
294
 
288
295
  def _GDictPosition(self, arcmode, s12_a12, outmask=Caps.STANDARD): # MCCABE 17
289
296
  '''(INTERNAL) Generate a new position along the geodesic.
@@ -293,26 +300,27 @@ class _GeodesicLineExact(_GeodesicBase):
293
300
  C{lon1}, C{azi1} and arc length C{a12} always included,
294
301
  except when C{a12=NAN}.
295
302
  '''
296
- Cs = Caps
303
+ r, Cs = GDict(), Caps
297
304
  if outmask:
298
305
  outmask &= self._caps & Cs._OUT_MASK
299
306
  eF = self._eF
307
+ if eF is None or self._toNAN or not isfinite(s12_a12): # _toNAN(outmask, s12_a12)?
308
+ # E2 = sig12 = ssig12 = csig12 = NAN
309
+ d = dict(a12=s12_a12) if arcmode else dict(s12=s12_a12)
310
+ return r._toNAN(outmask | Cs.NONFINITONAN, # for backward compatibility
311
+ lat1=self.lat1, lon1=self.lon1, azi1=self.azi1, **d)
300
312
  gX = self.geodesic # ._gX
301
- r = GDict(a12=NAN, s12=NAN) # both a12 and s12, always
302
313
 
303
- if self._toNAN or not isfinite(s12_a12): # _toNAN(outmask, s12_a12)?
304
- # E2 = sig12 = ssig12 = csig12 = NAN
305
- return r._toNAN(outmask | Cs.NONFINITONAN) # for backward compatibility
306
- elif arcmode: # s12_a12 is (spherical) arc length
307
- r.set_(a12=s12_a12)
314
+ if arcmode: # s12_a12 is (spherical) arc length
315
+ r.set_(a12=s12_a12, s12=NAN)
308
316
  sig12 = radians(s12_a12)
309
- if _K_2_0:
310
- ssig12, csig12 = sincos2(sig12) # utily, no NEG0
311
- else: # PYCHOK no cover
312
- a = fabs(s12_a12) # 0 <= fabs(_remainder(s12_a12, _180_0)) <= 90
313
- a -= floor(a / _180_0) * _180_0 # 0 <= 0 < 180
314
- ssig12 = _0_0 if a == 0 else sin(sig12)
315
- csig12 = _0_0 if a == 90 else cos(sig12)
317
+ ssig12, csig12 = sincos2(sig12) # utily, no NEG0
318
+ if not _K_2_0: # PYCHOK no cover
319
+ d = fremainder(fabs(s12_a12), _180_0)
320
+ if d == 90:
321
+ csig12 = _0_0
322
+ elif d == 0:
323
+ ssig12 = _0_0
316
324
  E2 = _0_0
317
325
  elif self._caps_DISTANCE_IN: # s12_a12 is distance
318
326
  t = s12_a12 / self._E0b
@@ -321,9 +329,9 @@ class _GeodesicLineExact(_GeodesicBase):
321
329
  E2 = -eF.deltaEinv(*_sincos12(-s, c, *self._stau1_ctau1))
322
330
  sig12 = fsum1f_(self._E1, -E2, t) # == t - (E2 - E1)
323
331
  ssig12, csig12 = _sincos2(sig12)
324
- r.set_(a12=degrees(sig12))
332
+ r.set_(a12=degrees(sig12), s12=s12_a12)
325
333
  else: # uninitialized or impossible distance requested
326
- return r
334
+ return r.set_(a12=NAN, s12=NAN)
327
335
 
328
336
  # sig2 = sig1 + sig12
329
337
  ssig1, csig1 = self._ssig1, self._csig1
@@ -331,7 +339,6 @@ class _GeodesicLineExact(_GeodesicBase):
331
339
  dn2 = eF.fDelta(*t)
332
340
 
333
341
  if (outmask & Cs.DISTANCE):
334
- outmask ^= Cs.DISTANCE
335
342
  if arcmode: # or f_0_01
336
343
  E2 = eF.deltaE(ssig2, csig2, dn2)
337
344
  # AB1 = _E0 * (E2 - _E1)
@@ -343,7 +350,7 @@ class _GeodesicLineExact(_GeodesicBase):
343
350
  s12 = s12_a12
344
351
  r.set_(s12=s12)
345
352
 
346
- if not outmask: # all done, see ._GenSet
353
+ if not (outmask ^ Cs.DISTANCE): # all done, see ._GenSet
347
354
  return r
348
355
 
349
356
  if self._debug: # PYCHOK no cover
@@ -374,17 +381,19 @@ class _GeodesicLineExact(_GeodesicBase):
374
381
  if (outmask & Cs.LONGITUDE):
375
382
  schi1 = self._somg1
376
383
  cchi1 = self._cchi1
377
- schi2 = ssig2 * salp0
378
- cchi2 = gX.f1 * dn2 * csig2 # schi2 = somg2 without normalization
384
+ schi2 = ssig2 * salp0 # schi2 = somg2 without normalization
385
+ cchi2 = gX.f1 * dn2 * csig2
379
386
  lam12 = salp0 * self._H0e2_f1 * fsum1f_(eF.deltaH(ssig2, csig2, dn2),
380
387
  -self._H1, sig12)
381
388
  if (outmask & Cs.LONG_UNROLL):
382
- t = _copysign_1_0(salp0) # east-going?
383
- tchi1 = t * schi1
384
- tchi2 = t * schi2
385
- chi12 = t * fsum1f_(atan2(ssig1, csig1), -atan2(ssig2, csig2),
389
+ e = _copysign_1_0(salp0) # east-going?
390
+ tchi1 = e * schi1
391
+ tchi2 = e * schi2
392
+ chi12 = e * fsum1f_(atan2(ssig1, csig1), -atan2(ssig2, csig2),
386
393
  atan2(tchi2, cchi2), -atan2(tchi1, cchi1), sig12)
387
394
  lon2 = self.lon1 + degrees(chi12 - lam12)
395
+ if fabs(lon2) > _360_0: # XXX kludge
396
+ lon2 = _norm180(lon2)
388
397
  else:
389
398
  chi12 = atan2(*_sincos12(schi1, cchi1, schi2, cchi2))
390
399
  lon2 = _norm180(self._lon1_norm180 + _norm180(degrees(chi12 - lam12)))
@@ -398,7 +407,7 @@ class _GeodesicLineExact(_GeodesicBase):
398
407
  J12 = self._D0k2 * fsumf_(eF.deltaD(ssig2, csig2, dn2), -self._D1, sig12)
399
408
  if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
400
409
  r.set_(ssig1=ssig1, dn1=dn1, D0k2=self._D0k2,
401
- csig1=csig1, J12=J12, D1=self._D1)
410
+ csig1=csig1, dn2=dn2, D1=self._D1, J12=J12)
402
411
  if (outmask & Cs.REDUCEDLENGTH):
403
412
  # Add parens around (csig1 * ssig2) and (ssig1 * csig2) to
404
413
  # ensure accurate cancellation in the case of coincident points.
pygeodesy/geodsolve.py CHANGED
@@ -16,14 +16,13 @@ from pygeodesy.interns import _DMAIN_, NN, _UNDER_
16
16
  from pygeodesy.karney import Caps, GeodesicError, GeodSolve12Tuple, \
17
17
  _sincos2d, _Xables, _0_0, NAN
18
18
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
19
- from pygeodesy.named import _name1__
20
19
  from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple
21
20
  from pygeodesy.props import Property, Property_RO, property_RO
22
21
  from pygeodesy.solveBase import _SolveGDictBase, _SolveGDictLineBase
23
22
  from pygeodesy.utily import _unrollon, _Wrap, wrap360
24
23
 
25
24
  __all__ = _ALL_LAZY.geodsolve
26
- __version__ = '25.04.14'
25
+ __version__ = '25.09.03'
27
26
 
28
27
 
29
28
  class _GeodesicSolveBase(_SolveGDictBase):
@@ -131,18 +130,15 @@ class GeodesicSolve(_GeodesicSolveBase):
131
130
  '''
132
131
  return self.DirectLine(ll1.lat, ll1.lon, azi12, **caps_name)
133
132
 
134
- def DirectLine(self, lat1, lon1, azi1, **caps_name):
133
+ def DirectLine(self, lat1, lon1, azi1, caps=Caps.ALL, **name):
135
134
  '''Set up a L{GeodesicLineSolve} to compute several points
136
135
  on a single geodesic.
137
136
 
138
137
  @arg lat1: Latitude of the first point (C{degrees}).
139
138
  @arg lon1: Longitude of the first point (C{degrees}).
140
139
  @arg azi1: Azimuth at the first point (compass C{degrees}).
141
- @kwarg caps_name: Optional C{B{name}=NN} (C{str}) and keyword
142
- argument C{B{caps}=Caps.ALL}, bit-or'ed combination
143
- of L{Caps<pygeodesy.karney.Caps>} values specifying
144
- the capabilities the L{GeodesicLineSolve} instance
145
- should possess.
140
+ @kwarg caps: Desired capabilities for the L{GeodesicLineSolve} instance.
141
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
146
142
 
147
143
  @return: A L{GeodesicLineSolve} instance.
148
144
 
@@ -154,9 +150,9 @@ class GeodesicSolve(_GeodesicSolveBase):
154
150
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
155
151
  and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
156
152
  '''
157
- return GeodesicLineSolve(self, lat1, lon1, azi1, **_name1__(caps_name, _or_nameof=self))
153
+ return GeodesicLineSolve(self, lat1, lon1, azi1, caps=caps, **name)
158
154
 
159
- Line = DirectLine
155
+ Line = ArcDirectLine = DirectLine
160
156
 
161
157
  def _Inverse(self, ll1, ll2, wrap, **outmask): # PYCHOK no cover
162
158
  '''(INTERNAL) Short-cut version, see .ellipsoidalBaseDI.intersecant2.
@@ -182,7 +178,7 @@ class GeodesicSolve(_GeodesicSolveBase):
182
178
  ll2 = _unrollon(ll1, _Wrap.point(ll2))
183
179
  return self.InverseLine(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **caps_name)
184
180
 
185
- def InverseLine(self, lat1, lon1, lat2, lon2, **caps_name): # PYCHOK no cover
181
+ def InverseLine(self, lat1, lon1, lat2, lon2, caps=Caps.ALL, **name): # PYCHOK no cover
186
182
  '''Set up a L{GeodesicLineSolve} to compute several points
187
183
  on a single geodesic.
188
184
 
@@ -190,11 +186,8 @@ class GeodesicSolve(_GeodesicSolveBase):
190
186
  @arg lon1: Longitude of the first point (C{degrees}).
191
187
  @arg lat2: Latitude of the second point (C{degrees}).
192
188
  @arg lon2: Longitude of the second point (C{degrees}).
193
- @kwarg caps_name: Optional C{B{name}=NN} (C{str}) and keyword
194
- argument C{B{caps}=Caps.ALL}, bit-or'ed combination
195
- of L{Caps<pygeodesy.karney.Caps>} values specifying
196
- the capabilities the L{GeodesicLineSolve} instance
197
- should possess.
189
+ @kwarg caps: Desired capabilities for the L{GeodesicLineSolve} instance.
190
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
198
191
 
199
192
  @return: A L{GeodesicLineSolve} instance.
200
193
 
@@ -205,7 +198,7 @@ class GeodesicSolve(_GeodesicSolveBase):
205
198
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
206
199
  '''
207
200
  r = self.Inverse(lat1, lon1, lat2, lon2)
208
- gl = GeodesicLineSolve(self, lat1, lon1, r.azi1, **_name1__(caps_name, _or_nameof=self))
201
+ gl = GeodesicLineSolve(self, lat1, lon1, r.azi1, caps=caps, **name)
209
202
  gl._a13 = r.a12 # gl.SetArc(r.a12)
210
203
  gl._s13 = r.s12 # gl.SetDistance(r.s12)
211
204
  return gl
pygeodesy/geohash.py CHANGED
@@ -37,7 +37,7 @@ from pygeodesy.units import Degrees_, Int, Lat_, Lon_, Meter, Precision_, Str
37
37
  from math import fabs, ldexp, log10, radians
38
38
 
39
39
  __all__ = _ALL_LAZY.geohash
40
- __version__ = '25.04.21'
40
+ __version__ = '25.09.16'
41
41
 
42
42
  _formy = _MODS.into(formy=__name__)
43
43
  _MASK5 = 16, 8, 4, 2, 1 # PYCHOK used!
@@ -150,10 +150,10 @@ class _GH(object):
150
150
  '''
151
151
  def _encodes(lat, lon, prec, eps=0):
152
152
  s, w, n, e = self.SWNE4
153
- E, d, _mid = self.EncodeB32, True, _2mid
153
+ d, _mid = True, _2mid
154
154
  for _ in range(prec):
155
155
  i = 0
156
- for _ in range(5): # len(_MASK5)
156
+ for _ in _MASK5:
157
157
  i += i
158
158
  if d: # bisect longitude
159
159
  a = _mid(e, w)
@@ -170,7 +170,7 @@ class _GH(object):
170
170
  s = a
171
171
  i += 1
172
172
  d = not d
173
- yield E[i]
173
+ yield self.EncodeB32[i]
174
174
  if eps > 0: # infer prec
175
175
  if _2dab(lon, e, w) < eps and \
176
176
  _2dab(lat, n, s) < eps:
@@ -233,10 +233,10 @@ class _GH(object):
233
233
  if not (0 < nc <= _MaxPrec): # or geohash.startswith(_INV_)
234
234
  raise GeohashError(geohash=geohash, len=nc)
235
235
  s, w, n, e = self.SWNE4
236
- D, d, _mid = self.DecodeB32, True, _2mid
236
+ d, _mid = True, _2mid
237
237
  try:
238
238
  for j, c in enumerate(geohash.lower()):
239
- i = D[c]
239
+ i = self.DecodeB32[c]
240
240
  for m in mask5:
241
241
  if d: # longitude
242
242
  a = _mid(e, w)
pygeodesy/geoids.py CHANGED
@@ -28,7 +28,7 @@ or L{GeoidPGM} and download a C{geoid} model file, containing locations with
28
28
  known heights also referred to as the C{grid knots}. See the documentation of
29
29
  the interpolator class for references to available C{grid} models.
30
30
 
31
- C{>>> from pygeodesy import GeoidEGM96 # or -G2012B, -Karney or -PGM as GeoidXyz}
31
+ C{>>> from pygeodesy import GeoidEGM96 as GeoidXyz # or GeoidG2012B, -Karney or -PGM}
32
32
 
33
33
  2. Instantiate an interpolator with the C{geoid} model file and use keyword
34
34
  arguments to select different interpolation options
@@ -126,7 +126,7 @@ except ImportError: # Python 3+
126
126
  from io import BytesIO as _BytesIO # PYCHOK expected
127
127
 
128
128
  __all__ = _ALL_LAZY.geoids
129
- __version__ = '25.05.21'
129
+ __version__ = '25.09.26'
130
130
 
131
131
  _assert_ = 'assert'
132
132
  _bHASH_ = b'#'
pygeodesy/heights.py CHANGED
@@ -21,7 +21,7 @@ C{>>> ...}
21
21
 
22
22
  2. Select one of the C{Height} classes for height interpolation
23
23
 
24
- C{>>> from pygeodesy import HeightCubic # or other Height... as HeightXyz}
24
+ C{>>> from pygeodesy import HeightCubic as HeightXyz # or an other Height... class}
25
25
 
26
26
  3. Instantiate a height interpolator with the C{knots} and use keyword
27
27
  arguments to select different interpolation options
@@ -92,7 +92,7 @@ from pygeodesy.units import _isDegrees, Float_, Int_
92
92
  # from math import radians # from .points
93
93
 
94
94
  __all__ = _ALL_LAZY.heights
95
- __version__ = '25.05.26'
95
+ __version__ = '25.09.29'
96
96
 
97
97
  _error_ = 'error'
98
98
  _formy = _MODS.into(formy=__name__)