pygeodesy 25.5.25__py2.py3-none-any.whl → 25.5.28__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygeodesy/geodesicw.py CHANGED
@@ -39,7 +39,7 @@ from contextlib import contextmanager
39
39
  # from math import fabs # from .utily
40
40
 
41
41
  __all__ = _ALL_LAZY.geodesicw
42
- __version__ = '25.04.14'
42
+ __version__ = '25.05.28'
43
43
 
44
44
  _plumb_ = 'plumb'
45
45
  _TRIPS = 65
@@ -96,6 +96,28 @@ class _gWrapped(_kWrapped):
96
96
  if name:
97
97
  self._name, _ = _name2__(name, _or_nameof=E)
98
98
 
99
+ def Area(self, polyline=False, **name): # like GeodesicExact.Area
100
+ '''Return a C{PolygonArea} instance with method C{Compute} extended.
101
+ '''
102
+ _AreaBase = _wrapped._PolygonArea # in .karney._kwrapped
103
+
104
+ class _PolygonArea(_AreaBase):
105
+ # def __init__(self, *earth_polyline):
106
+ # _PolygonArea.__init__(self, *earth_polyline)
107
+
108
+ def Compute(self, reverse=False, sign=True, polar=False):
109
+ '''Use C{B{polar}=True} to adjust the area, see function
110
+ L{areaOf<pygeodesy.geodesicx.gxarea.areaOf>}.
111
+ '''
112
+ n, p, a = _AreaBase.Compute(self, reverse=reverse, sign=sign)
113
+ if polar: # see .geodesicx.gxarea.GeodesicAreaExact._reduced
114
+ a += _copysign(self.area0 * _0_5 * n, a)
115
+ return n, p, a
116
+
117
+ A = _PolygonArea(self, polyline)
118
+ A.name = _name2__(name, _or_nameof=self)
119
+ return A
120
+
99
121
  def ArcDirect(self, lat1, lon1, azi1, a12, outmask=Caps.STANDARD): # PYCHOK no cover
100
122
  '''Return the C{_Geodesic.ArcDirect} result as L{GDict}.
101
123
  '''
@@ -108,8 +130,6 @@ class _gWrapped(_kWrapped):
108
130
  '''
109
131
  return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps, **name)
110
132
 
111
- Area = _Geodesic.Polygon # like GeodesicExact.Area
112
-
113
133
  @property_RO
114
134
  def datum(self):
115
135
  '''Get this geodesic's datum (C{Datum}).
@@ -257,9 +277,8 @@ class _gWrapped(_kWrapped):
257
277
  '''
258
278
  return self._name
259
279
 
260
- # Polygon = _Geodesic.Polygon
261
-
262
- WGS84 = None # _EWGS84.geodesicw recusion
280
+ Polygon = Area
281
+ WGS84 = None # _EWGS84.geodesicw recusion
263
282
 
264
283
  # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__
265
284
  # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__
@@ -461,7 +480,7 @@ class Geodesic(_gWrapped): # overwritten by 1st instance
461
480
  @kwarg name: Optional C{B{name}=NN} (C{str}).
462
481
  '''
463
482
  g = _wrapped.Geodesic(a_ellipsoid, f=f, **name)
464
- _MODS.geodesicw.Geodesic = g.__class__ # overwrite class
483
+ _MODS.geodesicw.Geodesic = type(g) # overwrite class
465
484
  return g
466
485
 
467
486
 
@@ -485,7 +504,7 @@ class GeodesicLine(_gWrapped): # overwritten by 1st instance
485
504
  @kwarg name: Optional C{B{name}=NN} (C{str}).
486
505
  '''
487
506
  gl = _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps, **name)
488
- _MODS.geodesicw.GeodesicLine = gl.__class__ # overwrite class
507
+ _MODS.geodesicw.GeodesicLine = type(gl) # overwrite class
489
508
  return gl
490
509
 
491
510
 
pygeodesy/geodesicx/gx.py CHANGED
@@ -44,11 +44,11 @@ from pygeodesy.constants import EPS, EPS0, EPS02, MANT_DIG, NAN, PI, _EPSqrt, \
44
44
  from pygeodesy.datums import _earth_datum, _WGS84, _EWGS84
45
45
  # from pygeodesy.ellipsoids import _EWGS84 # from .datums
46
46
  from pygeodesy.errors import GeodesicError, _xkwds_pop2
47
- from pygeodesy.fmath import hypot as _hypot, Fmt
47
+ from pygeodesy.fmath import fdot_, hypot, hypot2, Fmt
48
48
  from pygeodesy.fsums import fsumf_, fsum1f_
49
49
  from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
50
50
  _sincos12, _sin1cos2, _sinf1cos2d, \
51
- _TINY, _xnC4
51
+ _TINY, _toNAN, _xnC4
52
52
  from pygeodesy.geodesicx.gxline import _GeodesicLineExact, _update_glXs
53
53
  # from pygeodesy.internals import typename # from .basics
54
54
  from pygeodesy.interns import NN, _DOT_, _UNDER_
@@ -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.05.12'
68
+ __version__ = '25.05.28'
69
69
 
70
70
  _MAXIT1 = 20
71
71
  _MAXIT2 = 10 + _MAXIT1 + MANT_DIG # MANT_DIG == C++ digits
@@ -96,15 +96,12 @@ def _eTOL2(f):
96
96
  # (bet1 + bet2) / 2, the relative error in the azimuth
97
97
  # consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2.
98
98
  # (Error measured for 1/100 < b/a < 100 and abs(f) >= 1/1000.
99
-
100
- # For a given f and sig12, the max error occurs for lines
101
- # near the pole. If the old rule for computing dnm = (dn1
102
- # + dn2)/2 is used, then the error increases by a factor of
103
- # 2.) Setting this equal to epsilon gives sig12 = etol2.
104
-
105
- # Here 0.1 is a safety factor (error decreased by 100) and
106
- # max(0.001, abs(f)) stops etol2 getting too large in the
107
- # nearly spherical case.
99
+ # For a given f and sig12, the max error occurs for lines
100
+ # near the pole. If the old rule for computing dnm = (dn1 +
101
+ # dn2)/2 is used, then the error increases by a factor of 2.)
102
+ # Setting this equal to epsilon gives sig12 = eTOL2. Here,
103
+ # safety factor 0.1 (error decreased by 100) and max(0.001, abs(f))
104
+ # stops eTOL2 getting too large in the nearly spherical case.
108
105
  t = min(_1_0, _1_0 - f * _0_5) * max(_0_001, fabs(f)) * _0_5
109
106
  return _TOL3 / (sqrt(t) if t > EPS02 else EPS0)
110
107
 
@@ -216,11 +213,10 @@ class GeodesicExact(_GeodesicBase):
216
213
  return GeodesicLineExact(self, lat1, lon1, azi1, caps=caps, **name)._GenSet(self._debug, a12=a12)
217
214
 
218
215
  def Area(self, polyline=False, **name):
219
- '''Set up a L{GeodesicAreaExact} to compute area and
220
- perimeter of a polygon.
216
+ '''Set up a L{GeodesicAreaExact} to compute area and perimeter of a polygon.
221
217
 
222
- @kwarg polyline: If C{True}, compute the perimeter only, otherwise
223
- the perimeter and area (C{bool}).
218
+ @kwarg polyline: If C{True}, compute the perimeter only, otherwise the
219
+ perimeter and area (C{bool}).
224
220
  @kwarg name: Optional C{B{name}=NN} (C{str}).
225
221
 
226
222
  @return: A L{GeodesicAreaExact} instance.
@@ -429,17 +425,21 @@ class GeodesicExact(_GeodesicBase):
429
425
  def _eF_reset_cHe2_f1(self, x, y):
430
426
  '''(INTERNAL) Reset elliptic function and return M{cH * e2 / f1 * ...}.
431
427
  '''
432
- self._eF_reset_k2(x)
433
- return y * self._eF.cH * self._e2_f1
428
+ if isnan(x):
429
+ y = NAN
430
+ else:
431
+ self._eF_reset_k2(x)
432
+ y *= self._eF.cH * self._e2_f1
433
+ return y
434
434
 
435
435
  def _eF_reset_k2(self, x):
436
436
  '''(INTERNAL) Reset elliptic function and return C{k2}.
437
437
  '''
438
438
  ep2 = self.ep2
439
- k2 = x**2 * ep2 # see .gxline._GeodesicLineExact._eF
440
- self._eF.reset(k2=-k2, alpha2=-ep2) # kp2, alphap2 defaults
439
+ x *= x * ep2 # see .gxline._GeodesicLineExact._eF
440
+ self._eF.reset(k2=-x, alpha2=-ep2) # kp2, alphap2 defaults
441
441
  _update_glXs(self) # zap cached/memoized _GeodesicLineExact attrs
442
- return k2
442
+ return x
443
443
 
444
444
  @Property_RO
445
445
  def ellipsoid(self):
@@ -499,6 +499,7 @@ class GeodesicExact(_GeodesicBase):
499
499
  if self._debug: # PYCHOK no cover
500
500
  outmask |= Cs._DEBUG_INVERSE & self._debug
501
501
  outmask &= Cs._OUT_MASK # incl. _SALP_CALPs_ and _DEBUG_
502
+ toNAN = _toNAN(outmask, lat1, lon1, lat2, lon2)
502
503
  # compute longitude difference carefully (with _diff182):
503
504
  # result is in [-180, +180] but -180 is only for west-going
504
505
  # geodesics, +180 is for east-going and meridional geodesics
@@ -700,7 +701,9 @@ class GeodesicExact(_GeodesicBase):
700
701
  eFk2=eF.k2, eFa2=eF.alpha2)
701
702
  p.update(r) # r overrides p
702
703
  r = p.toGDict()
703
- return self._iter2tion(r, **p)
704
+
705
+ r = self._iter2tion(r, **p)
706
+ return r._toNAN(outmask) if toNAN else r
704
707
 
705
708
  def _GenDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask=Caps.STANDARD):
706
709
  '''(INTERNAL) The general I{Inverse} geodesic calculation.
@@ -805,8 +808,7 @@ class GeodesicExact(_GeodesicBase):
805
808
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
806
809
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
807
810
  '''
808
- Cs = Caps
809
- r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Cs._SALP_CALPs_)
811
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Caps._SALP_CALPs_)
810
812
  return GeodesicLineExact(self, lat1, lon1, None, caps=caps, _s_calp1=(r.salp1, r.calp1),
811
813
  **name)._GenSet(self._debug, **r)
812
814
 
@@ -839,12 +841,12 @@ class GeodesicExact(_GeodesicBase):
839
841
  # (tan(bet1/2) + tan(bet2/2)) /
840
842
  # (tan(bet1/2) * tan(bet2/2) + 1))
841
843
  # with tan(x/2) = sin(x) / (1 + cos(x))
842
- dbet1 = p.cbet1 + _1_0
843
- dbet2 = p.cbet2 + _1_0
844
- domg12 = comg12 + _1_0
845
- salp12 = (p.sbet1 * dbet2 + dbet1 * p.sbet2) * somg12
846
- calp12 = (p.sbet1 * p.sbet2 + dbet1 * dbet2) * domg12
847
- alp12 = atan2(salp12, calp12) * _2_0
844
+ dbet1 = p.cbet1 + _1_0
845
+ dbet2 = p.cbet2 + _1_0
846
+ domg12 = comg12 + _1_0
847
+ salp12 = fdot_(p.sbet1, dbet2, dbet1, p.sbet2) * somg12
848
+ calp12 = fdot_(p.sbet1, p.sbet2, dbet1, dbet2) * domg12
849
+ alp12 = atan2(salp12, calp12) * _2_0
848
850
  else:
849
851
  # alp12 = alp2 - alp1, used in atan2, no need to normalize
850
852
  salp12, calp12 = _sincos12(salp1, calp1, salp2, calp2)
@@ -870,7 +872,7 @@ class GeodesicExact(_GeodesicBase):
870
872
  and C{p.set_sigs} updated for Newton, C{sig12=None}.
871
873
  '''
872
874
  sig12 = None # use Newton
873
- salp1 = calp1 = salp2 = calp2 = dnm = NAN
875
+ salp1 = calp1 = salp2 = calp2 = NAN
874
876
 
875
877
  # bet12 = bet2 - bet1 in [0, PI)
876
878
  sbet12, cbet12 = _sincos12(p.sbet1, p.cbet1, p.sbet2, p.cbet2)
@@ -878,12 +880,15 @@ class GeodesicExact(_GeodesicBase):
878
880
  if shortline:
879
881
  # sin((bet1 + bet2)/2)^2 = (sbet1 + sbet2)^2 / (
880
882
  # (cbet1 + cbet2)^2 + (sbet1 + sbet2)^2)
881
- t = (p.sbet1 + p.sbet2)**2
882
- s = t / ((p.cbet1 + p.cbet2)**2 + t)
883
- dnm = sqrt(_1_0 + self.ep2 * s)
883
+ t = p.sbet1 + p.sbet2
884
+ if t:
885
+ t *= t / hypot2(t, p.cbet1 + p.cbet2)
886
+ dnm = sqrt(self.ep2 * t + _1_0)
887
+ else:
888
+ dnm = _1_0
884
889
  somg12, comg12 = _sincos2(lam12 / (self.f1 * dnm))
885
890
  else:
886
- somg12, comg12 = p.slam12, p.clam12
891
+ somg12, comg12, dnm = p.slam12, p.clam12, NAN
887
892
 
888
893
  # bet12a = bet2 + bet1 in (-PI, 0], note -sbet1
889
894
  sbet12a, cbet12a = _sincos12(-p.sbet1, p.cbet1, p.sbet2, p.cbet2)
@@ -894,8 +899,8 @@ class GeodesicExact(_GeodesicBase):
894
899
  salp1 = p.cbet2 * somg12
895
900
  calp1 = (sbet12a - t) if comg12 < 0 else (sbet12 + t)
896
901
 
897
- ssig12 = _hypot(salp1, calp1)
898
- csig12 = p.sbet1 * p.sbet2 + p.cbet1 * p.cbet2 * comg12
902
+ ssig12 = hypot(salp1, calp1)
903
+ csig12 = fdot_(p.sbet1, p.sbet2, p.cbet1, p.cbet2 * comg12)
899
904
 
900
905
  if shortline and ssig12 < self._eTOL2: # really short lines
901
906
  t = c if comg12 < 0 else s
@@ -970,8 +975,8 @@ class GeodesicExact(_GeodesicBase):
970
975
  #
971
976
  # omg12 is near PI, estimate work with omg12a = PI - omg12
972
977
  k = _Astroid(x, y)
973
- sca *= (y * (k + _1_0) / k) if f < 0 else \
974
- (x * k / (k + _1_0))
978
+ k1 = _1_0 + k
979
+ sca *= (y * k1 / k) if f < 0 else (x * k / k1)
975
980
  s, c = _sincos2(-sca) # omg12a
976
981
  # update spherical estimate of alp1 using omg12 instead of lam12
977
982
  salp1 = p.cbet2 * s
@@ -1082,16 +1087,15 @@ class GeodesicExact(_GeodesicBase):
1082
1087
  # Missing a factor of self.b. Add parens around
1083
1088
  # (csig1 * ssig2) and (ssig1 * csig2) to ensure
1084
1089
  # accurate cancellation for coincident points.
1085
- m12b = fsum1f_(p.dn2 * (p.csig1 * p.ssig2),
1086
- -p.dn1 * (p.ssig1 * p.csig2),
1087
- J12 * (p.csig1 * p.csig2))
1090
+ m12b = fdot_(p.dn2, (p.csig1 * p.ssig2),
1091
+ -p.dn1, (p.ssig1 * p.csig2),
1092
+ J12, (p.csig1 * p.csig2))
1088
1093
  if (outmask & Cs.GEODESICSCALE):
1089
- M12 = M21 = p.ssig1 * p.ssig2 + \
1090
- p.csig1 * p.csig2
1094
+ M12 = M21 = fdot_(p.ssig1, p.ssig2, p.csig1, p.csig2)
1091
1095
  t = (p.cbet1 - p.cbet2) * self.ep2 * \
1092
1096
  (p.cbet1 + p.cbet2) / (p.dn1 + p.dn2)
1093
- M12 += (p.ssig2 * t + p.csig2 * J12) * p.ssig1 / p.dn1
1094
- M21 -= (p.ssig1 * t + p.csig1 * J12) * p.ssig2 / p.dn2
1097
+ M12 += fdot_(p.ssig2, t, p.csig2, J12) * p.ssig1 / p.dn1
1098
+ M21 -= fdot_(p.ssig1, t, p.csig1, J12) * p.ssig2 / p.dn2
1095
1099
 
1096
1100
  return s12b, m12b, m0, M12, M21
1097
1101
 
@@ -1310,7 +1314,7 @@ def _Astroid(x, y):
1310
1314
  u = _cbrt(S * _2_0) # == T3 + _copysign(abs(S), T3)
1311
1315
  else:
1312
1316
  u = _0_0
1313
- v = _hypot(u, y) # sqrt(u**2 + q)
1317
+ v = hypot(u, y) # sqrt(u**2 + q)
1314
1318
  # avoid loss of accuracy when u < 0
1315
1319
  u = (q / (v - u)) if u < 0 else (v + u)
1316
1320
  w = (u - q) / (v + v) # positive?
@@ -17,7 +17,7 @@ U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
17
17
  # make sure int/int division yields float quotient
18
18
  from __future__ import division as _; del _ # noqa: E702 ;
19
19
 
20
- from pygeodesy.basics import isodd, unsigned0
20
+ from pygeodesy.basics import _copysign, isodd, unsigned0
21
21
  from pygeodesy.constants import NAN, _0_0, _0_5, _720_0
22
22
  from pygeodesy.internals import printf, typename
23
23
  # from pygeodesy.interns import _COMMASPACE_ # from .lazily
@@ -31,19 +31,16 @@ 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.05.12'
34
+ __version__ = '25.05.28'
35
35
 
36
36
 
37
37
  class GeodesicAreaExact(_NamedBase):
38
- '''Area and perimeter of a geodesic polygon, an enhanced
39
- version of I{Karney}'s Python class U{PolygonArea
40
- <https://GeographicLib.SourceForge.io/html/python/
41
- code.html#module-geographiclib.polygonarea>} using
42
- the more accurate surface area.
43
-
44
- @note: The name of this class C{*Exact} is a misnomer, see
45
- I{Karney}'s comments at C++ attribute U{GeodesicExact._c2
46
- <https://GeographicLib.SourceForge.io/C++/doc/
38
+ '''Area and perimeter of a geodesic polygon, an enhanced version of I{Karney}'s
39
+ Python class U{PolygonArea<https://GeographicLib.SourceForge.io/html/python/
40
+ code.html#module-geographiclib.polygonarea>} using the more accurate surface area.
41
+
42
+ @note: The name of this class C{*Exact} is a misnomer, see I{Karney}'s comments at
43
+ C++ attribute U{GeodesicExact._c2<https://GeographicLib.SourceForge.io/C++/doc/
47
44
  GeodesicExact_8cpp_source.html>}.
48
45
  '''
49
46
  _Area = None
@@ -140,36 +137,35 @@ class GeodesicAreaExact(_NamedBase):
140
137
 
141
138
  @Property_RO
142
139
  def area0x(self):
143
- '''Get the ellipsoid's surface area (C{meter} I{squared}),
144
- more accurate for very I{oblate} ellipsoids.
140
+ '''Get the ellipsoid's surface area (C{meter} I{squared}), more accurate
141
+ for very I{oblate} ellipsoids.
145
142
  '''
146
143
  return self.ellipsoid.areax # not .area!
147
144
 
148
145
  area0 = area0x # for C{geographiclib} compatibility
149
146
 
150
- def Compute(self, reverse=False, sign=True):
147
+ def Compute(self, reverse=False, sign=True, polar=False):
151
148
  '''Compute the accumulated perimeter and area.
152
149
 
153
- @kwarg reverse: If C{True}, clockwise traversal counts as a
154
- positive area instead of counter-clockwise
155
- (C{bool}).
156
- @kwarg sign: If C{True}, return a signed result for the area if
157
- the polygon is traversed in the "wrong" direction
158
- instead of returning the area for the rest of the
159
- earth.
160
-
161
- @return: L{Area3Tuple}C{(number, perimeter, area)} with the
162
- number of points, the perimeter in C{meter} and the
163
- area in C{meter**2}. The perimeter includes the
164
- length of a final edge, connecting the current to
165
- the initial point, if this polygon was initialized
166
- with C{polyline=False}. For perimeter only, i.e.
167
- C{polyline=True}, area is C{NAN}.
168
-
169
- @note: Arbitrarily complex polygons are allowed. In the case
170
- of self-intersecting polygons, the area is accumulated
171
- "algebraically". E.g., the areas of the 2 loops in a
172
- I{figure-8} polygon will partially cancel.
150
+ @kwarg reverse: If C{True}, clockwise traversal counts as a positive area instead
151
+ of counter-clockwise (C{bool}).
152
+ @kwarg sign: If C{True}, return a signed result for the area if the polygon is
153
+ traversed in the "wrong" direction instead of returning the area for
154
+ the rest of the earth.
155
+ @kwarg polar: Use C{B{polar}=True} if the polygon encloses a pole (C{bool}), see
156
+ function L{ispolar<pygeodesy.points.ispolar>} and U{area of a polygon
157
+ enclosing a pole<https://GeographicLib.SourceForge.io/C++/doc/
158
+ classGeographicLib_1_1GeodesicExact.html#a3d7a9155e838a09a48dc14d0c3fac525>}.
159
+
160
+ @return: L{Area3Tuple}C{(number, perimeter, area)} with the number of points, the
161
+ perimeter in C{meter} and the (signed) area in C{meter**2}. The perimeter
162
+ includes the length of a final edge, connecting the current to the initial
163
+ point, if this polygon was initialized with C{polyline=False}. For perimeter
164
+ only, i.e. C{polyline=True}, area is C{NAN}.
165
+
166
+ @note: Arbitrarily complex polygons are allowed. In the case of self-intersecting
167
+ polygons, the area is accumulated "algebraically". E.g., the areas of both
168
+ loops in a I{figure-8} polygon will partially cancel.
173
169
 
174
170
  @note: More points and edges can be added after this call.
175
171
  '''
@@ -179,7 +175,7 @@ class GeodesicAreaExact(_NamedBase):
179
175
  a = NAN if self.polyline else p
180
176
  elif self._Area:
181
177
  r = self._Inverse(self.lat1, self.lon1, self.lat0, self.lon0)
182
- a = self._reduced(r.S12, reverse, sign, r.xing)
178
+ a = self._reduced(r.S12, r.xing, n, reverse=reverse, sign=sign, polar=polar)
183
179
  p = self._Peri.Sum(r.s12)
184
180
  else:
185
181
  p = self._Peri.Sum()
@@ -279,7 +275,7 @@ class GeodesicAreaExact(_NamedBase):
279
275
  t = _COMMASPACE_.join(pairs(d, prec=10))
280
276
  printf('%s %s: %s (%s)', self.named2, n, t, callername(up=2))
281
277
 
282
- def _reduced(self, S12, reverse, sign, xing):
278
+ def _reduced(self, S12, xing, n, reverse=False, sign=True, polar=False):
283
279
  '''(INTERNAL) Accumulate and reduce area to allowed range.
284
280
  '''
285
281
  a0 = self.area0x
@@ -296,6 +292,8 @@ class GeodesicAreaExact(_NamedBase):
296
292
  a = A.Add(-a0)
297
293
  elif a <= -a0_:
298
294
  a = A.Add( a0)
295
+ if polar: # see .geodesicw._gwrapped.Geodesic.Area
296
+ a = A.Add(_copysign(a0 * _0_5 * n, a)) # - if reverse or sign?
299
297
  return unsigned0(a)
300
298
 
301
299
  def Reset(self):
@@ -313,63 +311,54 @@ class GeodesicAreaExact(_NamedBase):
313
311
 
314
312
  Clear = Reset
315
313
 
316
- def TestEdge(self, azi, s, reverse=False, sign=True):
314
+ def TestEdge(self, azi, s, **reverse_sign_polar):
317
315
  '''Compute the properties for a tentative, additional edge
318
316
 
319
317
  @arg azi: Azimuth at the current the point (compass C{degrees}).
320
318
  @arg s: Length of the edge (C{meter}).
321
- @kwarg reverse: If C{True}, clockwise traversal counts as a
322
- positive area instead of counter-clockwise
323
- (C{bool}).
324
- @kwarg sign: If C{True}, return a signed result for the area if
325
- the polygon is traversed in the "wrong" direction
326
- instead of returning the area for the rest of the
327
- earth.
319
+ @kwarg reverse_sign_polar: Optional C{B{reverse}=False}, C{B{sign}=True} and
320
+ C{B{polar}=False} keyword arguments, see method L{Compute}.
328
321
 
329
322
  @return: L{Area3Tuple}C{(number, perimeter, area)}.
330
323
 
331
324
  @raise GeodesicError: No points.
332
325
  '''
333
- n = self.num + 1
334
- p = self._Peri.Sum(s)
335
- if self.polyline:
336
- a, r = NAN, None
337
- elif n < 2:
338
- raise GeodesicError(num=self.num)
326
+ r, n = None, self.num + 1
327
+ if n < 2: # raise GeodesicError(num=self.num)
328
+ a = p = NAN # like .test_Planimeter19
339
329
  else:
340
- d = self._Direct(azi, s)
341
- r = self._Inverse(d.lat2, d.lon2, self.lat0, self.lon0)
342
- a = self._reduced(d.S12 + r.S12, reverse, sign, d.xing + r.xing)
343
- p += r.s12
330
+ p = self._Peri.Sum(s)
331
+ if self.polyline:
332
+ a = NAN
333
+ else:
334
+ d = self._Direct(azi, s)
335
+ r = self._Inverse(d.lat2, d.lon2, self.lat0, self.lon0)
336
+ a = self._reduced(d.S12 + r.S12, d.xing + r.xing, n, **reverse_sign_polar)
337
+ p += r.s12
344
338
  if self.verbose: # PYCHOK no cover
345
339
  self._print(n, p, a, r, azi=azi, s=s)
346
340
  return Area3Tuple(n, p, a)
347
341
 
348
- def TestPoint(self, lat, lon, reverse=False, sign=True):
342
+ def TestPoint(self, lat, lon, **reverse_sign_polar):
349
343
  '''Compute the properties for a tentative, additional vertex
350
344
 
351
345
  @arg lat: Latitude of the point (C{degrees}).
352
346
  @arg lon: Longitude of the point (C{degrees}).
353
- @kwarg reverse: If C{True}, clockwise traversal counts as a
354
- positive area instead of counter-clockwise
355
- (C{bool}).
356
- @kwarg sign: If C{True}, return a signed result for the area if
357
- the polygon is traversed in the "wrong" direction
358
- instead of returning the area for the rest of the
359
- earth.
347
+ @kwarg reverse_sign_polar: Optional C{B{reverse}=False}, C{B{sign}=True} and
348
+ C{B{polar}=False} keyword arguments, see method L{Compute}.
360
349
 
361
350
  @return: L{Area3Tuple}C{(number, perimeter, area)}.
362
351
  '''
363
352
  r, n = None, self.num + 1
364
353
  if n < 2:
365
354
  p = _0_0
366
- a = NAN if self.polyline else p
355
+ a = NAN if self.polyline else p
367
356
  else:
368
357
  i = self._Inverse(self.lat1, self.lon1, lat, lon)
369
358
  p = self._Peri.Sum(i.s12)
370
359
  if self._Area:
371
360
  r = self._Inverse(lat, lon, self.lat0, self.lon0)
372
- a = self._reduced(i.S12 + r.S12, reverse, sign, i.xing + r.xing)
361
+ a = self._reduced(i.S12 + r.S12, i.xing + r.xing, n, **reverse_sign_polar)
373
362
  p += r.s12
374
363
  else:
375
364
  a = NAN
@@ -9,7 +9,7 @@ U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
9
9
  '''
10
10
 
11
11
  from pygeodesy.basics import isodd, _MODS
12
- from pygeodesy.constants import _EPSmin as _TINY, _0_0
12
+ from pygeodesy.constants import _EPSmin as _TINY, _0_0, isfinite
13
13
  from pygeodesy.errors import _or, _xkwds_item2
14
14
  from pygeodesy.fmath import hypot as _hypot
15
15
  # from pygeodesy.interns import _numpy_ # _MODS
@@ -20,7 +20,7 @@ from pygeodesy.karney import _CapsBase, GeodesicError, _2cos2x, \
20
20
  from math import fabs, ldexp as _ldexp
21
21
 
22
22
  __all__ = ()
23
- __version__ = '24.09.07'
23
+ __version__ = '25.05.28'
24
24
 
25
25
  # valid C{nC4}s and C{C4order}s, see _xnC4 below
26
26
  _nC4s = {24: 2900, 27: 4032, 30: 5425}
@@ -147,6 +147,16 @@ def _sinf1cos2d(lat, f1):
147
147
  return sbet, (cbet if fabs(cbet) > _TINY else _TINY)
148
148
 
149
149
 
150
+ def _toNAN(outmask, *args):
151
+ '''(INTERNAL) Is any C{arg} not finite?
152
+ '''
153
+ if (outmask & _CapsBase.NONFINITONAN): # Caps.NONFINITONAN
154
+ for arg in args:
155
+ if not isfinite(arg):
156
+ return True
157
+ return False
158
+
159
+
150
160
  def _xnC4(**name_nC4):
151
161
  '''(INTERNAL) Validate C{C4order}.
152
162
  '''
@@ -37,13 +37,14 @@ from __future__ import division as _; del _ # noqa: E702 ;
37
37
  # - s and c prefixes mean sin and cos
38
38
 
39
39
  # from pygeodesy.basics import _xinstanceof # _MODS
40
- from pygeodesy.constants import NAN, _EPSqrt as _TOL, _0_0, _1_0, \
41
- _180_0, _2__PI, _copysign_1_0, isfinite
40
+ from pygeodesy.constants import NAN, _EPSqrt as _TOL, \
41
+ _0_0, _1_0, _180_0, _2__PI, \
42
+ _copysign_1_0, isfinite
42
43
  from pygeodesy.errors import _xError, _xkwds_pop2
43
44
  from pygeodesy.fsums import fsumf_, fsum1f_
44
45
  from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
45
46
  _sincos12, _sin1cos2, \
46
- _sinf1cos2d, _TINY
47
+ _sinf1cos2d, _TINY, _toNAN
47
48
  # from pygeodesy.geodesicw import _Intersecant2 # _MODS
48
49
  from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS
49
50
  from pygeodesy.karney import _around, _atan2d, Caps, GDict, _fix90, \
@@ -55,7 +56,7 @@ from pygeodesy.utily import atan2, atan2d as _atan2d_reverse, sincos2
55
56
  from math import cos, degrees, fabs, floor, radians, sin
56
57
 
57
58
  __all__ = ()
58
- __version__ = '25.05.12'
59
+ __version__ = '25.05.28'
59
60
 
60
61
  _glXs = [] # instances of C{[_]GeodesicLineExact} to be updated
61
62
 
@@ -91,6 +92,7 @@ class _GeodesicLineExact(_GeodesicBase):
91
92
  # _salp1 = _calp1 = NAN
92
93
  # _somg1 = _comg1 = NAN
93
94
  # _ssig1 = _csig1 = NAN
95
+ # _toNAN = False
94
96
 
95
97
  def __init__(self, gX, lat1, lon1, azi1, caps, **name_):
96
98
  '''(INTERNAL) New C{[_]GeodesicLineExact} instance.
@@ -104,6 +106,7 @@ class _GeodesicLineExact(_GeodesicBase):
104
106
  salp1, calp1 = _sincos2d(_around(azi1))
105
107
  if name_:
106
108
  self.name = name_
109
+ self._toNAN = _toNAN(caps, lat1, lon1, azi1, salp1, calp1)
107
110
 
108
111
  self._gX = gX # GeodesicExact only
109
112
  self._lat1 = lat1 = _fix90(lat1)
@@ -297,9 +300,9 @@ class _GeodesicLineExact(_GeodesicBase):
297
300
  gX = self.geodesic # ._gX
298
301
  r = GDict(a12=NAN, s12=NAN) # both a12 and s12, always
299
302
 
300
- if not isfinite(s12_a12):
303
+ if self._toNAN or not isfinite(s12_a12): # _toNAN(outmask, s12_a12)?
301
304
  # E2 = sig12 = ssig12 = csig12 = NAN
302
- return r._toNAN(outmask)
305
+ return r._toNAN(outmask | Cs.NONFINITONAN) # for backward compatibility
303
306
  elif arcmode: # s12_a12 is (spherical) arc length
304
307
  r.set_(a12=s12_a12)
305
308
  sig12 = radians(s12_a12)
pygeodesy/geoids.py CHANGED
@@ -355,7 +355,7 @@ class _GeoidBase(_HeightBase):
355
355
  # build grid axis, hi = lo + (n - 1) * d
356
356
  m, a = len2(frange(lo, n, d))
357
357
  if m != n:
358
- raise LenError(self.__class__, grid=m, **{name: n})
358
+ raise LenError(type(self), grid=m, **{name: n})
359
359
  if d < 0:
360
360
  d, a = -d, list(reversed(a))
361
361
  a = self.numpy.array(a)
pygeodesy/hausdorff.py CHANGED
@@ -85,7 +85,7 @@ from pygeodesy import unitsBase as _unitsBase # _Str_..., _xUnit, _xUnits
85
85
  from random import Random
86
86
 
87
87
  __all__ = _ALL_LAZY.hausdorff
88
- __version__ = '25.05.21'
88
+ __version__ = '25.05.26'
89
89
 
90
90
  _formy = _MODS.into(formy=__name__)
91
91
 
@@ -124,7 +124,7 @@ class Hausdorff(_Named):
124
124
  @raise HausdorffError: Insufficient number of B{C{point1s}} or an invalid
125
125
  B{C{point1}}, B{C{seed}} or B{C{units}}.
126
126
  '''
127
- name, kwds = _name2__(**name__kwds) # name__=self.__class__
127
+ name, kwds = _name2__(**name__kwds) # name__=type(self)
128
128
  if name:
129
129
  self.name = name
130
130
 
pygeodesy/heights.py CHANGED
@@ -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.12'
95
+ __version__ = '25.05.26'
96
96
 
97
97
  _error_ = 'error'
98
98
  _formy = _MODS.into(formy=__name__)
@@ -212,7 +212,7 @@ class _HeightNamed(_Named): # in .geoids
212
212
  n, lats = len2(lats)
213
213
  m, lons = len2(lons)
214
214
  if n != m: # format a LenError, but raise self._Error
215
- e = LenError(self.__class__, lats=n, lons=m, txt=None)
215
+ e = LenError(type(self), lats=n, lons=m, txt=None)
216
216
  raise self._Error(str(e))
217
217
  llis = [LLiC(*t, datum=d) for t in zip(lats, lons)]
218
218
  return llis
@@ -363,13 +363,13 @@ class _HeightBase(_HeightNamed): # in .geoids
363
363
  def numpy(self):
364
364
  '''Get the C{numpy} module or C{None}.
365
365
  '''
366
- return _xnumpy(self.__class__, 1, 9) # overwrite property_ROver
366
+ return _xnumpy(type(self), 1, 9) # overwrite property_ROver
367
367
 
368
368
  @property_ROver
369
369
  def scipy(self):
370
370
  '''Get the C{scipy} module or C{None}.
371
371
  '''
372
- return _xscipy(self.__class__, 1, 2) # overwrite property_ROver
372
+ return _xscipy(type(self), 1, 2) # overwrite property_ROver
373
373
 
374
374
  @property_ROver
375
375
  def scipy_interpolate(self):