pygeodesy 25.7.25__py2.py3-none-any.whl → 25.9.9__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 (44) hide show
  1. pygeodesy/__init__.py +10 -9
  2. pygeodesy/auxilats/__init__.py +1 -1
  3. pygeodesy/auxilats/auxAngle.py +4 -3
  4. pygeodesy/auxilats/auxily.py +1 -1
  5. pygeodesy/basics.py +4 -4
  6. pygeodesy/booleans.py +25 -25
  7. pygeodesy/cartesianBase.py +21 -20
  8. pygeodesy/constants.py +37 -7
  9. pygeodesy/deprecated/functions.py +1 -0
  10. pygeodesy/dms.py +2 -2
  11. pygeodesy/ecef.py +324 -260
  12. pygeodesy/ellipsoidalExact.py +4 -4
  13. pygeodesy/ellipsoidalGeodSolve.py +3 -3
  14. pygeodesy/ellipsoids.py +79 -52
  15. pygeodesy/elliptic.py +8 -11
  16. pygeodesy/errors.py +18 -5
  17. pygeodesy/etm.py +8 -8
  18. pygeodesy/fmath.py +1 -1
  19. pygeodesy/geodesicx/__init__.py +1 -1
  20. pygeodesy/geodesicx/__main__.py +1 -0
  21. pygeodesy/geodesicx/gx.py +30 -37
  22. pygeodesy/geodesicx/gxbases.py +1 -5
  23. pygeodesy/geodesicx/gxline.py +43 -34
  24. pygeodesy/geodsolve.py +10 -17
  25. pygeodesy/internals.py +39 -15
  26. pygeodesy/karney.py +19 -18
  27. pygeodesy/ktm.py +3 -3
  28. pygeodesy/latlonBase.py +4 -4
  29. pygeodesy/lazily.py +14 -13
  30. pygeodesy/lcc.py +5 -5
  31. pygeodesy/named.py +10 -13
  32. pygeodesy/nvectorBase.py +4 -4
  33. pygeodesy/rhumb/__init__.py +1 -1
  34. pygeodesy/rhumb/aux_.py +1 -1
  35. pygeodesy/rhumb/bases.py +7 -8
  36. pygeodesy/rhumb/ekx.py +9 -9
  37. pygeodesy/solveBase.py +14 -3
  38. pygeodesy/sphericalTrigonometry.py +8 -8
  39. pygeodesy/utily.py +200 -159
  40. pygeodesy/vector3dBase.py +10 -8
  41. {pygeodesy-25.7.25.dist-info → pygeodesy-25.9.9.dist-info}/METADATA +12 -11
  42. {pygeodesy-25.7.25.dist-info → pygeodesy-25.9.9.dist-info}/RECORD +44 -44
  43. {pygeodesy-25.7.25.dist-info → pygeodesy-25.9.9.dist-info}/WHEEL +0 -0
  44. {pygeodesy-25.7.25.dist-info → pygeodesy-25.9.9.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.02'
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
 
@@ -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.
@@ -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.
@@ -791,10 +790,7 @@ class GeodesicExact(_GeodesicBase):
791
790
  @arg lon1: Longitude of the first point (C{degrees}).
792
791
  @arg lat2: Latitude of the second point (C{degrees}).
793
792
  @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}.
793
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
798
794
  @kwarg name: Optional C{B{name}=NN} (C{str}).
799
795
 
800
796
  @return: A L{GeodesicLineExact} instance.
@@ -1106,10 +1102,7 @@ class GeodesicExact(_GeodesicBase):
1106
1102
  @arg lat1: Latitude of the first point (C{degrees}).
1107
1103
  @arg lon1: Longitude of the first point (C{degrees}).
1108
1104
  @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}.
1105
+ @kwarg caps: Desired capabilities for the L{GeodesicLineExact} instance.
1113
1106
  @kwarg name: Optional C{B{name}=NN} (C{str}).
1114
1107
 
1115
1108
  @return: A L{GeodesicLineExact} instance.
@@ -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/internals.py CHANGED
@@ -4,10 +4,10 @@
4
4
  u'''Mostly INTERNAL functions, except L{machine}, L{print_} and L{printf}.
5
5
  '''
6
6
  # from pygeodesy.basics import isiterablen, ubstr # _MODS
7
- # from pygeodesy.errors import _AttributeError, _error_init, _UnexpectedError, _xError2 # _MODS
8
- from pygeodesy.interns import _BAR_, _COLON_, _DASH_, _DMAIN_, _DOT_, _ELLIPSIS_, _EQUALSPACED_, \
9
- _immutable_, _NL_, NN, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, \
10
- _QUOTE2_, _s_, _SPACE_, _sys, _UNDER_
7
+ # from pygeodesy.errors import _AttributeError, _error_init, _ImmutableError, _UnexpectedError, _xError2 # _MODS
8
+ from pygeodesy.interns import _BAR_, _COLON_, _DASH_, _DMAIN_, _DOT_, _ELLIPSIS_, _NL_, NN, \
9
+ _pygeodesy_, _PyPy__, _python_, _QUOTE1_, _QUOTE2_, _s_, _sys, \
10
+ _SPACE_, _UNDER_
11
11
  from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used!
12
12
  # from pygeodesy.streprs import anstr, pairs, unstr # _MODS
13
13
 
@@ -56,6 +56,27 @@ def _Property_RO(method):
56
56
  return property(_get, _set, _del)
57
57
 
58
58
 
59
+ class _Enum(object): # in .elliptic, .utily
60
+ '''(INTERNAL) Enum-like, immutable items.
61
+ '''
62
+ # _ImmutableError = None
63
+
64
+ def __init__(self, **enums):
65
+ self.__dict__.update(enums)
66
+ # for item in enums.items():
67
+ # setattr(self, *item) # object.__setattr__
68
+
69
+ def __str__(self):
70
+ _unstr = _MODS.streprs.unstr
71
+ return _unstr(_Enum, **self.__dict__)
72
+
73
+ # def __delattr__(self, attr): # PYCHOK no cover
74
+ # raise _ImmutableError(self, attr) # _del_
75
+
76
+ # def __setattr__(self, attr, value): # PYCHOK no cover
77
+ # raise _ImmutableError(self, attr, value)
78
+
79
+
59
80
  class _MODS_Base(object):
60
81
  '''(INTERNAL) Base-class for C{lazily._ALL_MODS}.
61
82
  '''
@@ -63,10 +84,7 @@ class _MODS_Base(object):
63
84
  self.__dict__.pop(attr, None)
64
85
 
65
86
  def __setattr__(self, attr, value): # PYCHOK no cover
66
- e = _MODS.errors
67
- n = _DOT_(self.name, attr)
68
- t = _EQUALSPACED_(n, repr(value))
69
- raise e._AttributeError(_immutable_, txt=t)
87
+ raise _ImmutableError(self, attr, value)
70
88
 
71
89
  @_Property_RO
72
90
  def basics(self):
@@ -76,7 +94,7 @@ class _MODS_Base(object):
76
94
  return b
77
95
 
78
96
  @_Property_RO
79
- def bits_machine2(self):
97
+ def bits_machine2(self): # in test/bases.py
80
98
  '''Get platform 2-list C{[bits, machine]}, I{once}.
81
99
  '''
82
100
  import platform as p
@@ -302,6 +320,12 @@ def _headof(name):
302
320
  return name if i < 0 else name[:i]
303
321
 
304
322
 
323
+ def _ImmutableError(*inst_attr_value):
324
+ '''(INTERNAL) Format an C{_ImmutableError}.
325
+ '''
326
+ return _MODS.errors._ImmutableError(*inst_attr_value)
327
+
328
+
305
329
  # def _is(a, b): # PYCHOK no cover
306
330
  # '''(INTERNAL) C{a is b}? in C{PyPy}
307
331
  # '''
@@ -473,10 +497,10 @@ def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds):
473
497
  else:
474
498
  t = fmt
475
499
  except Exception as x:
476
- _E, s = _MODS.errors._xError2(x)
477
- unstr = _MODS.streprs.unstr
478
- t = unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
479
- raise _E(s, txt=t, cause=x)
500
+ _Error, s = _MODS.errors._xError2(x)
501
+ _unstr = _MODS.strepr.unstr
502
+ t = _unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
503
+ raise _Error(s, txt=t, cause=x)
480
504
  try:
481
505
  n = f.write(NN(b, t, e))
482
506
  except UnicodeEncodeError: # XXX only Windows
@@ -686,7 +710,7 @@ def _versions(sep=_SPACE_):
686
710
 
687
711
 
688
712
  __all__ = tuple(map(typename, (machine, print_, printf, typename)))
689
- __version__ = '25.05.26'
713
+ __version__ = '25.09.05'
690
714
 
691
715
  if __name__ == _DMAIN_:
692
716
 
@@ -699,7 +723,7 @@ if __name__ == _DMAIN_:
699
723
  _main()
700
724
 
701
725
  # % python3 -m pygeodesy.internals
702
- # pygeodesy 25.4.14 Python 3.13.2 64bit arm64 macOS 15.4 _isfrozen False isLazy 1
726
+ # pygeodesy 25.8.18 Python 3.13.5 64bit arm64 macOS 15.6 _isfrozen False isLazy 1
703
727
 
704
728
  # **) MIT License
705
729
  #
pygeodesy/karney.py CHANGED
@@ -157,8 +157,7 @@ from pygeodesy.interns import NN, _a12_, _area_, _azi1_, _azi2_, _azi12_, \
157
157
  _SPACE_, _UNDER_, _X_, _1_, _2_, _BAR_ # PYCHOK used!
158
158
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS
159
159
  from pygeodesy.named import ADict, _NamedBase, _NamedTuple, notImplemented, _Pass
160
- from pygeodesy.props import deprecated_method, Property_RO, property_RO, \
161
- property_ROnce
160
+ from pygeodesy.props import deprecated_method, property_RO, property_ROnce
162
161
  from pygeodesy.units import Azimuth as _Azi, Degrees as _Deg, Lat, Lon, \
163
162
  Meter as _M, Meter2 as _M2, Number_
164
163
  from pygeodesy.utily import atan2d, sincos2d, tand, _unrollon, fabs
@@ -166,7 +165,7 @@ from pygeodesy.utily import atan2d, sincos2d, tand, _unrollon, fabs
166
165
  # from math import fabs # from .utily
167
166
 
168
167
  __all__ = _ALL_LAZY.karney
169
- __version__ = '25.05.28'
168
+ __version__ = '25.08.31'
170
169
 
171
170
  _2_4_ = '2.4'
172
171
  _K_2_0 = _getenv(_PYGEODESY_ENV(typename(_xgeographiclib)[2:]), _2_)
@@ -270,16 +269,16 @@ class Caps(object):
270
269
  REDUCEDLENGTH = 1 << 12 | _CAP_1 | _CAP_2 # compute reduced length C{m12}
271
270
  GEODESICSCALE = 1 << 13 | _CAP_1 | _CAP_2 # compute geodesic scales C{M12} and C{M21}
272
271
  AREA = 1 << 14 | _CAP_4 # compute area C{S12}
272
+ ALL = 0x7F80 | _CAP_ALL # without LONG_UNROLL, LINE_OFF, NONFINITONAN, REVERSE2 and _DEBUG_*
273
273
 
274
274
  STANDARD = AZIMUTH | DISTANCE | LATITUDE | LONGITUDE
275
275
  STANDARD_LINE = STANDARD | DISTANCE_IN # for goedesici/-w
276
276
 
277
277
  LINE_CAPS = STANDARD_LINE | REDUCEDLENGTH | GEODESICSCALE # .geodesici only
278
278
  LONG_UNROLL = 1 << 15 # unroll C{lon2} in .Direct and .Position
279
- NONFINITONAN = 1 << 16 # see method GDict._toNAN
280
- LINE_OFF = 1 << 17 # Line without updates from parent geodesic or rhumb
279
+ LINE_OFF = 1 << 16 # Line without updates from parent geodesic or rhumb
280
+ NONFINITONAN = 1 << 17 # see method GDict._toNAN
281
281
  REVERSE2 = 1 << 18 # reverse C{azi2}
282
- ALL = 0x7F80 | _CAP_ALL # without LONG_UNROLL, LINE_OFF, REVERSE2 and _DEBUG_*
283
282
 
284
283
  AZIMUTH_DISTANCE = AZIMUTH | DISTANCE
285
284
  AZIMUTH_DISTANCE_AREA = AZIMUTH | DISTANCE | AREA
@@ -300,7 +299,7 @@ class Caps(object):
300
299
  _INVERSE3 = AZIMUTH | DISTANCE # for goedesicw only
301
300
 
302
301
  _OUT_ALL = ALL # see geographiclib.geodesiccapabilities.py
303
- _OUT_MASK = ALL | LONG_UNROLL | REVERSE2 | _DEBUG_ALL
302
+ _OUT_MASK = ALL | LONG_UNROLL | NONFINITONAN | REVERSE2 | _DEBUG_ALL
304
303
 
305
304
  _AZIMUTH_LATITUDE_LONGITUDE = AZIMUTH | LATITUDE | LONGITUDE
306
305
  _AZIMUTH_LATITUDE_LONG_UNROLL = AZIMUTH | LATITUDE | LONG_UNROLL
@@ -366,7 +365,7 @@ class _CapsBase(_NamedBase): # in .auxilats, .geodesicx.gxbases
366
365
  _caps = 0 # None
367
366
  _debug = 0 # or Caps._DEBUG_...
368
367
 
369
- @Property_RO
368
+ @property_RO
370
369
  def caps(self):
371
370
  '''Get the capabilities (bit-or'ed C{Caps}).
372
371
  '''
@@ -455,13 +454,16 @@ class GDict(ADict): # XXX _NamedDict
455
454
  '''
456
455
  return self._toTuple(Inverse10Tuple, dflt)
457
456
 
458
- def _toNAN(self, outmask): # .GeodesicExact._GDistInverse, .GeodesicLineExact._GenPosition
457
+ def _toNAN(self, outmask, **specs): # .GeodesicExact._GDistInverse, .GeodesicLineExact._GenPosition
459
458
  '''(INTERNAL) Convert this C{GDict} to all C{NAN}s.
460
459
  '''
461
460
  if (outmask & Caps.NONFINITONAN):
462
- d = dict((k, NAN) for k, C in _key2Caps.items()
463
- if (outmask & C) == C)
464
- self.set_(**d)
461
+ def _t2(k):
462
+ return k, specs.get(k, NAN)
463
+
464
+ d = dict(_t2(k) for k, C in _key2Caps.items()
465
+ if (outmask & C) == C)
466
+ self.set_(**d) # self.update(d)
465
467
  return self
466
468
 
467
469
  @deprecated_method
@@ -721,12 +723,11 @@ def _around(x): # in .utily.sincos2d
721
723
  try:
722
724
  return _wrapped.Math.AngRound(x)
723
725
  except AttributeError:
724
- if x:
725
- y = _1_16th - fabs(x)
726
- if y > 0: # fabs(x) < _1_16th
727
- x = _copysign(_1_16th - y, x)
728
- else:
729
- x = _0_0 # -0 to 0
726
+ b, a = _1_16th, fabs(x)
727
+ if a < b:
728
+ a -= b
729
+ a += b
730
+ x = _copysign(a, x)
730
731
  return x
731
732
 
732
733