pygeodesy 25.8.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.
pygeodesy/elliptic.py CHANGED
@@ -75,7 +75,7 @@ U{22<https://DLMF.NIST.gov/22>}.
75
75
  # make sure int/int division yields float quotient, see .basics
76
76
  from __future__ import division as _; del _ # noqa: E702 ;
77
77
 
78
- from pygeodesy.basics import copysign0, map2, neg, neg_, typename
78
+ from pygeodesy.basics import copysign0, map2, neg, neg_
79
79
  from pygeodesy.constants import EPS, INF, NAN, PI, PI_2, PI_4, \
80
80
  _EPStol as _TolJAC, _0_0, _0_25, \
81
81
  _0_5, _1_0, _2_0, _N_2_0, _3_0, \
@@ -85,7 +85,7 @@ from pygeodesy.constants import _EPSjam as _TolJAM # PYCHOK used!
85
85
  # from pygeodesy.errors import _ValueError # from .fsums
86
86
  from pygeodesy.fmath import favg, Fdot_, fma, hypot1, zqrt
87
87
  from pygeodesy.fsums import Fsum, _fsum, _ValueError
88
- # from pygeodesy.internals import typename # from .basics
88
+ from pygeodesy.internals import _Enum, typename
89
89
  from pygeodesy.interns import NN, _delta_, _DOT_, _f_, _invalid_, \
90
90
  _invokation_, _negative_, _SPACE_
91
91
  from pygeodesy.karney import _K_2_0, _norm180, _signBit, _sincos2
@@ -100,7 +100,7 @@ from math import asin, asinh, atan, ceil, cosh, fabs, floor, radians, \
100
100
  sin, sinh, sqrt, tan, tanh # tan as _tan
101
101
 
102
102
  __all__ = _ALL_LAZY.elliptic
103
- __version__ = '25.06.02'
103
+ __version__ = '25.09.04'
104
104
 
105
105
  _TolRD = zqrt(EPS * 0.002)
106
106
  _TolRF = zqrt(EPS * 0.030)
@@ -108,13 +108,10 @@ _TolRG0 = _TolJAC * 2.7
108
108
  _TRIPS = 28 # Max depth, 6-18 might be sufficient
109
109
 
110
110
 
111
- class _Cs(object):
112
- '''(INTERAL) Complete integrals cache.
111
+ class _Cs(_Enum):
112
+ '''(INTERAL) Complete Integrals cache.
113
113
  '''
114
- def __init__(self, **kwds):
115
- # for n,v in kwds.items():
116
- # setattr(self, n, v)
117
- self.__dict__ = kwds
114
+ pass
118
115
 
119
116
 
120
117
  class Elliptic(_Named):
@@ -773,8 +770,8 @@ class Elliptic(_Named):
773
770
  def _S(**kwds):
774
771
  return Scalar_(Error=EllipticError, **kwds)
775
772
 
776
- self._k2 = _S(k2 = k2, low=None, high=_1_0)
777
- self._kp2 = _S(kp2=_1p2(kp2, k2)) # low=_0_0
773
+ self._k2 = _S(k2 = k2, low=None, high=_1_0)
774
+ self._kp2 = _S(kp2=_1p2(kp2, k2)) # low=_0_0
778
775
 
779
776
  self._alpha2 = _S(alpha2 = alpha2, low=None, high=_1_0)
780
777
  self._alphap2 = _S(alphap2=_1p2(alphap2, alpha2)) # low=_0_0
pygeodesy/errors.py CHANGED
@@ -16,9 +16,10 @@ C{PYGEODESY_EXCEPTION_CHAINING=std} or to any non-empty string.
16
16
  # from pygeodesy import errors # _MODS, _MODS.getattr
17
17
  from pygeodesy.internals import _envPYGEODESY, _plural, _tailof, typename
18
18
  from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, _COLON_, _COLONSPACE_, \
19
- _COMMASPACE_, _datum_, _ELLIPSIS_, _ellipsoidal_, _incompatible_, \
20
- _invalid_, _keyword_, _LatLon_, _len_, _not_, _or_, _SPACE_, \
21
- _specified_, _UNDER_, _vs_, _with_
19
+ _COMMASPACE_, _datum_, _DOT_, _ELLIPSIS_, _ellipsoidal_, \
20
+ _EQUALSPACED_, _immutable_, _incompatible_, _invalid_, _keyword_, \
21
+ _LatLon_, _len_, _not_, _or_, _SPACE_, _specified_, _UNDER_, \
22
+ _vs_, _with_
22
23
  from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _PYTHON_X_DEV
23
24
  # from pygeodesy import streprs as _streprs # _MODS.into
24
25
  # from pygeodesy.unitsBase import Str # _MODS
@@ -27,11 +28,12 @@ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _PYTHON_X_DEV
27
28
  from copy import copy as _copy
28
29
 
29
30
  __all__ = _ALL_LAZY.errors # _ALL_DOCS('_InvalidError', '_IsnotError') _under
30
- __version__ = '25.05.19'
31
+ __version__ = '25.09.04'
31
32
 
32
33
  _argument_ = 'argument'
33
34
  _basics = _MODS.into(basics=__name__)
34
35
  _box_ = 'box'
36
+ _del_ = 'del'
35
37
  _expected_ = 'expected'
36
38
  _limiterrors = True # in .formy
37
39
  _name_value_ = repr('name=value')
@@ -224,7 +226,8 @@ class IntersectionError(_ValueError): # in .ellipsoidalBaseDI, .formy, ...
224
226
  '''New L{IntersectionError}.
225
227
  '''
226
228
  if args:
227
- _ValueError.__init__(self, _SPACE_(*args), **kwds)
229
+ t = _COMMASPACE_(*map(repr, args))
230
+ _ValueError.__init__(self, t, **kwds)
228
231
  else:
229
232
  _ValueError.__init__(self, **kwds)
230
233
 
@@ -474,6 +477,16 @@ def exception_chaining(exc=None):
474
477
  getattr(exc, '__cause__', None) # _DCAUSE_
475
478
 
476
479
 
480
+ def _ImmutableError(inst, attr, value=_del_, Error=_TypeError): # PYCHOK self
481
+ '''(INTERNAL) Format an C{immutable _TypeError}.
482
+ '''
483
+ n = typename(inst)
484
+ n = _DOT_(_xattr(inst, name=n), attr)
485
+ t = _SPACE_(_del_, n) if value is _del_ else \
486
+ _EQUALSPACED_(n, repr(value))
487
+ return Error(_immutable_, txt=t)
488
+
489
+
477
490
  def _incompatible(this):
478
491
  '''(INTERNAL) Format an C{"incompatible with ..."} text.
479
492
  '''
pygeodesy/etm.py CHANGED
@@ -75,8 +75,8 @@ from pygeodesy.datums import _ellipsoidal_datum, _WGS84, _EWGS84
75
75
  # from pygeodesy.fsums import Fsum # from .fmath
76
76
  from pygeodesy.fmath import cbrt, hypot, hypot1, hypot2, Fsum
77
77
  from pygeodesy.interns import _COMMASPACE_, _DMAIN_, _near_, _SPACE_, _spherical_
78
- from pygeodesy.karney import _K_2_4, _copyBit, _diff182, _fix90, _norm2, \
79
- _norm180, _tand, _unsigned2
78
+ from pygeodesy.karney import _K_2_4, _diff182, _fix90, _norm2, _norm180, \
79
+ _signBit, _tand, _unsigned2
80
80
  # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .named
81
81
  from pygeodesy.named import callername, _incompatible, _NamedBase, \
82
82
  _ALL_LAZY, _MODS
@@ -93,7 +93,7 @@ from pygeodesy.utm import _cmlon, _LLEB, _parseUTM5, _toBand, _toXtm8, \
93
93
  from math import asinh, degrees, radians, sinh, sqrt
94
94
 
95
95
  __all__ = _ALL_LAZY.etm
96
- __version__ = '25.05.12'
96
+ __version__ = '25.08.31'
97
97
 
98
98
  _OVERFLOW = _1_EPS**2 # ~2e+31
99
99
  _TAYTOL = pow(EPS, 0.6)
@@ -790,7 +790,7 @@ class ExactTransverseMercator(_NamedBase):
790
790
  mu = mu / d2
791
791
  mv = mv / d2
792
792
  else:
793
- mu, mv = map1(_overflow, mu, mv)
793
+ mu, mv = map1(_copysignOVERFLOW, mu, mv)
794
794
  xi = self._Eu.fE(snu, cnu, dnu) - mu
795
795
  v -= self._Ev.fE(snv, cnv, dnv) - mv
796
796
  return xi, v, d2
@@ -910,12 +910,12 @@ class ExactTransverseMercator(_NamedBase):
910
910
  if d1 > EPS02: # _EPSmin
911
911
  t1 = snu * dnv / sqrt(d1)
912
912
  else: # like atan(overflow) = pi/2
913
- t1, d1 = _overflow(snu), 0
913
+ t1, d1 = _copysignOVERFLOW(snu), 0
914
914
  d2 = cnu2 * self._mu + cnv**2 * mv
915
915
  if d2 > EPS02: # _EPSmin
916
916
  t2 = sinh(e * asinh(e * snu / sqrt(d2)))
917
917
  else:
918
- t2, d2 = _overflow(snu), 0
918
+ t2, d2 = _copysignOVERFLOW(snu), 0
919
919
  # psi = asinh(t1) - asinh(t2)
920
920
  # taup = sinh(psi)
921
921
  taup = t1 * hypot1(t2) - t2 * hypot1(t1)
@@ -1026,10 +1026,10 @@ _allPropertiesOf_n(22, ExactTransverseMercator, Property_RO) # PYCHOK assert _R
1026
1026
  del _0_1, _allPropertiesOf_n, EPS, _1_EPS, _EWGS84
1027
1027
 
1028
1028
 
1029
- def _overflow(x):
1029
+ def _copysignOVERFLOW(x):
1030
1030
  '''(INTERNAL) Like C{copysign0(OVERFLOW, B{x})}.
1031
1031
  '''
1032
- return _copyBit(_OVERFLOW, x)
1032
+ return (-_OVERFLOW) if _signBit(x) else _OVERFLOW
1033
1033
 
1034
1034
 
1035
1035
  def parseETM5(strUTM, datum=_WGS84, Etm=Etm, falsed=True, **name):
pygeodesy/fmath.py CHANGED
@@ -25,7 +25,7 @@ from math import fabs, sqrt # pow
25
25
  import operator as _operator # in .datums, .trf, .utm
26
26
 
27
27
  __all__ = _ALL_LAZY.fmath
28
- __version__ = '25.06.03'
28
+ __version__ = '25.08.31'
29
29
 
30
30
  # sqrt(2) - 1 <https://WikiPedia.org/wiki/Square_root_of_2>
31
31
  _0_4142 = 0.41421356237309504880 # ~ 3_730_904_090_310_553 / 9_007_199_254_740_992
@@ -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__ = '25.06.04'
26
+ __version__ = '25.09.02'
27
27
 
28
28
  # **) MIT License
29
29
  #
@@ -46,6 +46,7 @@ def _main(**C4order): # PYCHOK no cover
46
46
  from sys import argv # .internals._isPyChOK
47
47
  _main(C4order=int(argv[1])) if len(argv) == 2 and argv[1].isdigit() else _main()
48
48
 
49
+
49
50
  # % python3.13 -m pygeodesy.geodesicx
50
51
  # pygeodesy.geodesicx 25.06.01: C4order=30, C4n=5425, C4u=5107, C4u_n=94.1%, C4x=465, C4t=tuple, C4z=166008, geographiclib 2.0 (pygeodesy 25.5.28 Python 3.13.3 64bit arm64 macOS 15.5)
51
52
 
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