pygeodesy 25.12.12__py2.py3-none-any.whl → 25.12.31__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/__init__.py +27 -19
- pygeodesy/constants.py +2 -0
- pygeodesy/ellipsoids.py +24 -12
- pygeodesy/fmath.py +5 -7
- pygeodesy/formy.py +243 -104
- pygeodesy/fsums.py +40 -28
- pygeodesy/geod3solve.py +10 -9
- pygeodesy/geodesici.py +5 -4
- pygeodesy/geodesicx/__init__.py +1 -1
- pygeodesy/geodesicx/gxarea.py +53 -23
- pygeodesy/geodsolve.py +4 -3
- pygeodesy/karney.py +21 -36
- pygeodesy/lazily.py +2 -2
- pygeodesy/named.py +2 -2
- pygeodesy/props.py +5 -3
- pygeodesy/trf.py +1 -3
- pygeodesy/triaxials/bases.py +51 -20
- pygeodesy/triaxials/triaxial3.py +12 -13
- {pygeodesy-25.12.12.dist-info → pygeodesy-25.12.31.dist-info}/METADATA +22 -15
- {pygeodesy-25.12.12.dist-info → pygeodesy-25.12.31.dist-info}/RECORD +22 -22
- {pygeodesy-25.12.12.dist-info → pygeodesy-25.12.31.dist-info}/WHEEL +0 -0
- {pygeodesy-25.12.12.dist-info → pygeodesy-25.12.31.dist-info}/top_level.txt +0 -0
pygeodesy/__init__.py
CHANGED
|
@@ -8,8 +8,8 @@ geodetic (lat-/longitude), geocentric (U{ECEF<https://WikiPedia.org/wiki/ECEF>}
|
|
|
8
8
|
<https://GeographicLib.SourceForge.io/1.44/triaxial.html>} coordinates.
|
|
9
9
|
|
|
10
10
|
Transcoded in part from U{JavaScript originals<https://GitHub.com/ChrisVeness/geodesy>} by I{Chris Veness (C)
|
|
11
|
-
2005-
|
|
12
|
-
F. F. Karney (C) 2008-
|
|
11
|
+
2005-2025} and from several U{C++ classes<https://GeographicLib.SourceForge.io/C++/doc/annotated.html>} by I{Charles
|
|
12
|
+
F. F. Karney (C) 2008-2025} and published under the same U{MIT License<https://OpenSource.org/licenses/MIT>}**.
|
|
13
13
|
|
|
14
14
|
There are four modules for ellipsoidal earth models, C{ellipsoidalExact}, C{-Karney}, C{-Vincenty} and C{-Nvector}
|
|
15
15
|
and two for spherical ones, C{sphericalTrigonometry} and C{-Nvector}. Each module provides a geodetic B{C{LatLon}}
|
|
@@ -101,9 +101,15 @@ and L{GnomonicGeodSolve} depend on I{Karney}'s C++ utility U{GeodSolve
|
|
|
101
101
|
<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} to be executable and set with
|
|
102
102
|
env variable C{PYGEODESY_GEODSOLVE} or with property L{Ellipsoid.geodsolve}.
|
|
103
103
|
|
|
104
|
+
Triaxial geodesic classes L{Geodesic3Solve} and L{GeodesicLine3Solve} in module L{geod3solve} need
|
|
105
|
+
I{Karney}'s C++ utility U{Geod3Solve<https://GeographicLib.SourceForge.io/C++/doc/Geod3Solve.1.html>}
|
|
106
|
+
to be executable and set with env variable C{PYGEODESY_GEOD3SOLVE} or with property
|
|
107
|
+
L{Geodesic3Solve.Geod3Solve<geod3solve._Geodesic3SolveBase.Geod3Solve>}.
|
|
108
|
+
|
|
104
109
|
Class L{Intersectool} in module L{geodesici} needs I{Karney}'s C++ utility U{IntersectTool
|
|
105
110
|
<https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>} to be executable and set with
|
|
106
|
-
env variable C{PYGEODESY_INTERSECTTOOL}.
|
|
111
|
+
env variable C{PYGEODESY_INTERSECTTOOL} or with property L{Intersectool.IntersectTool
|
|
112
|
+
<geodesici.Intersectool.IntersectTool>}.
|
|
107
113
|
|
|
108
114
|
To compare C{MGRS} results from modules L{mgrs} and C{testMgrs} with I{Karney}'s C++ utility
|
|
109
115
|
U{GeoConvert<https://GeographicLib.SourceForge.io/C++/doc/GeoConvert.1.html>}, the latter must
|
|
@@ -128,22 +134,24 @@ Tests
|
|
|
128
134
|
The tests ran with Python 3.14.2 (with U{geographiclib<https://PyPI.org/project/geographiclib>} 2.1),
|
|
129
135
|
Python 3.13.9 (with U{geographiclib<https://PyPI.org/project/geographiclib>} 2.1),
|
|
130
136
|
U{numpy<https://PyPI.org/project/numpy>} 2.3.3, U{scipy<https://PyPI.org/project/scipy>} 1.16.2,
|
|
131
|
-
U{GeoConvert<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.
|
|
132
|
-
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.
|
|
137
|
+
U{GeoConvert<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7 and
|
|
138
|
+
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7),
|
|
133
139
|
Python 3.12.7 (with U{geographiclib<https://PyPI.org/project/geographiclib>} 2.0,
|
|
134
140
|
U{numpy<https://PyPI.org/project/numpy>} 2.1.0, U{scipy<https://PyPI.org/project/scipy>} 1.14.1,
|
|
135
|
-
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.
|
|
136
|
-
U{
|
|
137
|
-
U{
|
|
141
|
+
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7,
|
|
142
|
+
U{GeodS3olve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7,
|
|
143
|
+
U{IntersectTool<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7 and
|
|
144
|
+
U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7),
|
|
138
145
|
Python 3.11.5 (with U{geographiclib<https://PyPI.org/project/geographiclib>} 2.0,
|
|
139
146
|
U{numpy<https://PyPI.org/project/numpy>} 1.24.2 and U{scipy<https://PyPI.org/project/scipy>} 1.10.1),
|
|
140
147
|
and with Python 2.7.18 (with U{geographiclib<https://PyPI.org/project/geographiclib>} 1.50,
|
|
141
148
|
U{numpy<https://PyPI.org/project/numpy>} 1.16.6, U{scipy<https://PyPI.org/project/scipy>} 1.2.2,
|
|
142
|
-
U{GeoConvert<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.
|
|
143
|
-
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.
|
|
144
|
-
U{
|
|
145
|
-
U{
|
|
146
|
-
|
|
149
|
+
U{GeoConvert<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7,
|
|
150
|
+
U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7,
|
|
151
|
+
U{Geod3Solve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7,
|
|
152
|
+
U{IntersectTool<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7 and
|
|
153
|
+
U{RhumbSolve<https://GeographicLib.SourceForge.io/C++/doc/utilities.html>} 2.7), all in 64-bit on
|
|
154
|
+
macOS 26.2 Tahoe.
|
|
147
155
|
|
|
148
156
|
All tests ran with and without C{lazy import} for Python 3 and with command line option C{-W default} and
|
|
149
157
|
env variable C{PYGEODESY_WARNINGS=on} for all Python versions. The results of those tests are included in
|
|
@@ -153,11 +161,11 @@ Test coverage has been measured with U{coverage<https://PyPI.org/project/coverag
|
|
|
153
161
|
3.14.2, 3.13.9 and 3.12.7. The complete coverage report in HTML and a PDF summary are included in the
|
|
154
162
|
distribution files.
|
|
155
163
|
|
|
156
|
-
Python 3.14.2, 3.13.9, 3.12.7 and 3.11.5 run on Apple M4
|
|
164
|
+
Python 3.14.2, 3.13.9, 3.12.7 and 3.11.5 run on Apple Si M4 (C{arm64}), I{natively}. Python 2.7.18 runs
|
|
157
165
|
on Intel (C{x86_64}) or Intel I{emulation} ("C{arm64_x86_64}", see function L{machine<pygeodesy.machine>}).
|
|
158
166
|
|
|
159
|
-
The tests also ran with Python 3.
|
|
160
|
-
U{Debian 12<https://Cirrus-CI.com/github/mrJean1/PyGeodesy/master>} in 64-bit only, with Python 3.
|
|
167
|
+
The tests also ran with Python 3.14.2 (and U{geographiclib<https://PyPI.org/project/geographiclib>} 2.1) on
|
|
168
|
+
U{Debian 12<https://Cirrus-CI.com/github/mrJean1/PyGeodesy/master>} in 64-bit only, with Python 3.13.9 (and
|
|
161
169
|
U{geographiclib<https://PyPI.org/project/geographiclib>} 2.0) on U{Windows 2019Server
|
|
162
170
|
<https://CI.AppVeyor.com/project/mrJean1/pygeodesy>} in 64-bit only and with Python 2.7.18 (and U{geographiclib
|
|
163
171
|
<https://PyPI.org/project/geographiclib>} 1.52) on U{Windows 10<https://CI.AppVeyor.com/project/mrJean1/pygeodesy>}
|
|
@@ -190,7 +198,7 @@ All Python source code has been statically U{checked<https://GitHub.com/ActiveSt
|
|
|
190
198
|
546532_PyChecker_postprocessor>} with U{Ruff<https://GitHub.com/astral-sh/ruff>} using Python 3.13.9 and with
|
|
191
199
|
U{PyChecker<https://PyPI.org/project/pychecker>}, U{PyFlakes<https://PyPI.org/project/pyflakes>}, U{PyCodeStyle
|
|
192
200
|
<https://PyPI.org/project/pycodestyle>} (formerly Pep8) and U{McCabe<https://PyPI.org/project/mccabe>} using Python
|
|
193
|
-
2.7.18, both in 64-bit on macOS 26.
|
|
201
|
+
2.7.18, both in 64-bit on macOS 26.2 Tahoe.
|
|
194
202
|
|
|
195
203
|
For a summary of all I{Karney}-based functionality in C{pygeodesy}, see module U{karney
|
|
196
204
|
<https://mrJean1.GitHub.io/PyGeodesy/docs/pygeodesy.karney-module.html>}.
|
|
@@ -210,7 +218,7 @@ The following environment variables are observed by C{pygeodesy}:
|
|
|
210
218
|
- C{PYGEODESY_FSUM_RESIDUAL} - see module L{fsums<pygeodesy.fsums>} and method L{RESIDUAL<pygeodesy.Fsum.RESIDUAL>}.
|
|
211
219
|
- C{PYGEODESY_GEOCONVERT} - see module L{mgrs<pygeodesy.mgrs>}.
|
|
212
220
|
- C{PYGEODESY_GEODSOLVE} - see module L{geodsolve<pygeodesy.geodsolve>}.
|
|
213
|
-
- C{PYGEODESY_GEOD3SOLVE} - see module L{
|
|
221
|
+
- C{PYGEODESY_GEOD3SOLVE} - see module L{geod3solve<pygeodesy.geod3solve>}.
|
|
214
222
|
- C{PYGEODESY_INTERSECTTOOL} - see module L{geodesici<pygeodesy.geodesici>}.
|
|
215
223
|
- C{PYGEODESY_LAZY_IMPORT} - see module L{lazily<pygeodesy.lazily>} and variable L{isLazy<pygeodesy.isLazy>}.
|
|
216
224
|
- C{PYGEODESY_NOTIMPLEMENTED} - C{__special__} methods return C{NotImplemented} if set to "std".
|
|
@@ -618,7 +626,7 @@ else:
|
|
|
618
626
|
|
|
619
627
|
from pygeodesy.internals import _version2, _DOT_ # noqa: E402
|
|
620
628
|
# from pygeodesy.interns import _DOT_ # from .internals
|
|
621
|
-
__version__ = '25.12.
|
|
629
|
+
__version__ = '25.12.31'
|
|
622
630
|
# see setup.py for similar logic
|
|
623
631
|
version = _DOT_(*_version2(__version__, n=3))
|
|
624
632
|
|
pygeodesy/constants.py
CHANGED
|
@@ -332,6 +332,8 @@ R_VM = _Radius(R_VM=6366707.0194937) # PYCHOK aViation/naVigation earth radius
|
|
|
332
332
|
|
|
333
333
|
_INF_NAN_NINF = {INF, NAN, NINF, _inf, _nan}
|
|
334
334
|
_pos_self = _1_0.__pos__() is _1_0 # PYCHOK in .fsums, .vector3dBase
|
|
335
|
+
_1_3rd = 1 / 3 # in .fmath
|
|
336
|
+
_2_3rd = 2 / 3 # in .fmath, .formy
|
|
335
337
|
|
|
336
338
|
|
|
337
339
|
def _0_0s(n):
|
pygeodesy/ellipsoids.py
CHANGED
|
@@ -67,10 +67,10 @@ from __future__ import division as _; del _ # noqa: E702 ;
|
|
|
67
67
|
# from pygeodesy.albers import AlbersEqualAreaCylindrical # _MODS
|
|
68
68
|
from pygeodesy.basics import copysign0, isbool, _isin, isint, typename
|
|
69
69
|
from pygeodesy.constants import EPS, EPS_2, EPS0, EPS02, EPS1, INF, NINF, \
|
|
70
|
-
|
|
70
|
+
_EPSqrt, PI_2, PI_3, PI2, PI4, R_M, R_MA, R_FM, \
|
|
71
71
|
_EPStol as _TOL, _floatuple as _T, _isfinite, \
|
|
72
|
-
_0_0s, _0_0, _0_5, _1_0, _1_EPS, _2_0,
|
|
73
|
-
_0_25, _3_0 # PYCHOK used!
|
|
72
|
+
_over, _0_0s, _0_0, _0_5, _1_0, _1_EPS, _2_0, \
|
|
73
|
+
_4_0, _90_0, _0_25, _3_0 # PYCHOK used!
|
|
74
74
|
from pygeodesy.errors import _AssertionError, IntersectionError, _ValueError, _xattr, _xkwds_not
|
|
75
75
|
from pygeodesy.fmath import cbrt, cbrt2, fdot, Fhorner, fpowers, hypot, hypot_, \
|
|
76
76
|
hypot1, hypot2, sqrt3, Fsum
|
|
@@ -96,7 +96,7 @@ from pygeodesy.utily import atan1, atan1d, atan2b, degrees90, m2radians, radians
|
|
|
96
96
|
from math import asinh, atan, atanh, cos, degrees, exp, fabs, radians, sin, sinh, sqrt, tan # as _tan
|
|
97
97
|
|
|
98
98
|
__all__ = _ALL_LAZY.ellipsoids
|
|
99
|
-
__version__ = '25.
|
|
99
|
+
__version__ = '25.12.14'
|
|
100
100
|
|
|
101
101
|
_f_0_0 = Float(f =_0_0) # zero flattening
|
|
102
102
|
_f__0_0 = Float(f_=_0_0) # zero inverse flattening
|
|
@@ -111,19 +111,19 @@ def _aux(lat, inverse, auxLat, clip=90):
|
|
|
111
111
|
return Lat(lat, clip=clip, name=_lat_ if inverse else typename(auxLat))
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def
|
|
114
|
+
def _sin2cos2(rad):
|
|
115
115
|
'''(INTERNAL) Return 2-tuple C{(sin(B{phi})**2, cos(B{phi})**2)}.
|
|
116
116
|
'''
|
|
117
|
-
if
|
|
118
|
-
s2 = sin(
|
|
117
|
+
if rad:
|
|
118
|
+
s2 = sin(rad)**2
|
|
119
119
|
if s2 > EPS:
|
|
120
120
|
c2 = _1_0 - s2
|
|
121
121
|
if c2 > EPS:
|
|
122
122
|
if c2 < EPS1:
|
|
123
123
|
return s2, c2
|
|
124
124
|
else:
|
|
125
|
-
return _1_0, _0_0 #
|
|
126
|
-
return _0_0, _1_0 #
|
|
125
|
+
return _1_0, _0_0 # rad == PI_2
|
|
126
|
+
return _0_0, _1_0 # rad == 0
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
class a_f2Tuple(_NamedTuple):
|
|
@@ -871,6 +871,12 @@ class Ellipsoid(_NamedEnumItem):
|
|
|
871
871
|
|
|
872
872
|
equatoradius = a # Requatorial
|
|
873
873
|
|
|
874
|
+
@Property_RO
|
|
875
|
+
def equatorimeter(self):
|
|
876
|
+
'''Get the ellipsoid's I{equatorial} perimeter (C{meter}).
|
|
877
|
+
'''
|
|
878
|
+
return Meter(equatorimeter=self.a * PI2)
|
|
879
|
+
|
|
874
880
|
def e2s(self, s):
|
|
875
881
|
'''Compute norm M{sqrt(1 - e2 * s**2)}.
|
|
876
882
|
|
|
@@ -1381,6 +1387,12 @@ class Ellipsoid(_NamedEnumItem):
|
|
|
1381
1387
|
|
|
1382
1388
|
polaradius = b # Rpolar
|
|
1383
1389
|
|
|
1390
|
+
@property_RO
|
|
1391
|
+
def polarimeter(self):
|
|
1392
|
+
'''Get the ellipsoid's I{polar}, meridional perimeter (C{meter}).
|
|
1393
|
+
'''
|
|
1394
|
+
return Meter(polarimeter=self.L * _4_0)
|
|
1395
|
+
|
|
1384
1396
|
# Q = A # I{meridian arc unit} C{Q}, the mean, meridional length I{per radian}
|
|
1385
1397
|
|
|
1386
1398
|
@deprecated_Property_RO
|
|
@@ -1484,7 +1496,7 @@ class Ellipsoid(_NamedEnumItem):
|
|
|
1484
1496
|
r, p = self.a, Phid(lat)
|
|
1485
1497
|
if p and self.f:
|
|
1486
1498
|
if fabs(p) < PI_2:
|
|
1487
|
-
s2, c2 =
|
|
1499
|
+
s2, c2 = _sin2cos2(p)
|
|
1488
1500
|
# R == sqrt((a2**2 * c2 + b2**2 * s2) / (a2 * c2 + b2 * s2))
|
|
1489
1501
|
# == sqrt(a2**2 * (c2 + (b2 / a2)**2 * s2) / (a2 * (c2 + b2 / a2 * s2)))
|
|
1490
1502
|
# == sqrt(a2 * (c2 + (b2 / a2)**2 * s2) / (c2 + (b2 / a2) * s2))
|
|
@@ -1709,7 +1721,7 @@ class Ellipsoid(_NamedEnumItem):
|
|
|
1709
1721
|
'''(INTERNAL) Helper for C{rocAzimuth} and C{rocBearing}.
|
|
1710
1722
|
'''
|
|
1711
1723
|
if self.f:
|
|
1712
|
-
s2, c2 =
|
|
1724
|
+
s2, c2 = _sin2cos2(radians(deg))
|
|
1713
1725
|
m, n = self.roc2_(Phid(lat))
|
|
1714
1726
|
if n < m: # == n / (c2 * n / m + s2)
|
|
1715
1727
|
c2 *= n / m
|
|
@@ -1751,7 +1763,7 @@ class Ellipsoid(_NamedEnumItem):
|
|
|
1751
1763
|
# ... requires 1 or 2 sqrt
|
|
1752
1764
|
g = self.b
|
|
1753
1765
|
if self.f:
|
|
1754
|
-
s2, c2 =
|
|
1766
|
+
s2, c2 = _sin2cos2(Phid(lat))
|
|
1755
1767
|
g = _over(g, c2 + self.b2_a2 * s2)
|
|
1756
1768
|
return Radius(rocGauss=g)
|
|
1757
1769
|
|
pygeodesy/fmath.py
CHANGED
|
@@ -10,8 +10,8 @@ from __future__ import division as _; del _ # noqa: E702 ;
|
|
|
10
10
|
from pygeodesy.basics import _copysign, copysign0, isbool, isint, isodd, \
|
|
11
11
|
isscalar, len2, map1, _xiterable, typename
|
|
12
12
|
from pygeodesy.constants import EPS0, EPS02, EPS1, NAN, PI, PI_2, PI_4, \
|
|
13
|
-
_0_0, _0_125, _0_25,
|
|
14
|
-
_copysign_0_0, isfinite, remainder
|
|
13
|
+
_0_0, _0_125, _0_25, _1_3rd, _0_5, _2_3rd, \
|
|
14
|
+
_1_0, _1_5, _copysign_0_0, isfinite, remainder
|
|
15
15
|
from pygeodesy.errors import _IsnotError, LenError, _TypeError, _ValueError, \
|
|
16
16
|
_xError, _xkwds, _xkwds_pop2, _xsError
|
|
17
17
|
from pygeodesy.fsums import _2float, Fsum, fsum, _isFsum_2Tuple, Fmt, unstr
|
|
@@ -25,13 +25,11 @@ 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.12.
|
|
28
|
+
__version__ = '25.12.23'
|
|
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
|
|
32
|
-
|
|
33
|
-
_1_6th = _1_0 / 6
|
|
34
|
-
_2_3rd = _1_3rd * 2
|
|
32
|
+
_1_6th = 1 / 6
|
|
35
33
|
_h_lt_b_ = 'abs(h) < abs(b)'
|
|
36
34
|
|
|
37
35
|
|
|
@@ -1049,7 +1047,7 @@ def sqrt3(x):
|
|
|
1049
1047
|
|
|
1050
1048
|
|
|
1051
1049
|
def sqrt_a(h, b):
|
|
1052
|
-
'''Compute C{I{a}} side of a right-angled triangle from
|
|
1050
|
+
'''Compute the C{I{a}} side of a right-angled triangle from
|
|
1053
1051
|
C{sqrt(B{h}**2 - B{b}**2)}.
|
|
1054
1052
|
|
|
1055
1053
|
@arg h: Hypotenuse or outer annulus radius (C{scalar}).
|
pygeodesy/formy.py
CHANGED
|
@@ -8,24 +8,28 @@ from __future__ import division as _; del _ # noqa: E702 ;
|
|
|
8
8
|
|
|
9
9
|
from pygeodesy.basics import _copysign, _isin # _args_kwds_count2
|
|
10
10
|
# from pygeodesy.cartesianBase import CartesianBase # _MODS
|
|
11
|
-
from pygeodesy.constants import EPS, EPS0, EPS1, PI, PI2, PI3, PI_2, R_M, \
|
|
11
|
+
from pygeodesy.constants import EPS, EPS0, EPS1, EPS_2, PI, PI2, PI3, PI_2, R_M, \
|
|
12
12
|
_0_0s, float0_, isnon0, remainder, _umod_PI2, \
|
|
13
13
|
_0_0, _0_125, _0_25, _0_5, _1_0, _2_0, _4_0, \
|
|
14
|
-
_90_0, _180_0, _360_0
|
|
14
|
+
_90_0, _180_0, _360_0
|
|
15
|
+
from pygeodesy.constants import _3_0, _10_0, MANT_DIG as _DIG53 # PYCHOK used!
|
|
15
16
|
from pygeodesy.datums import Datum, Ellipsoid, _ellipsoidal_datum, \
|
|
16
17
|
_mean_radius, _spherical_datum, _WGS84, _EWGS84
|
|
17
18
|
# from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
|
|
19
|
+
# from pygeodesy.elliptic import Elliptic # _MODS
|
|
18
20
|
from pygeodesy.errors import IntersectionError, LimitError, limiterrors, \
|
|
19
21
|
_TypeError, _ValueError, _xattr, _xError, \
|
|
20
22
|
_xcallable, _xkwds, _xkwds_pop2
|
|
21
|
-
from pygeodesy.fmath import euclid, fdot_, fprod, hypot, hypot2, sqrt0
|
|
22
|
-
from pygeodesy.fsums import fsumf_, Fmt, unstr
|
|
23
|
+
from pygeodesy.fmath import euclid, fdot_, fhorner, fprod, hypot, hypot2, sqrt0
|
|
24
|
+
from pygeodesy.fsums import fsum, fsumf_, Fmt, unstr
|
|
23
25
|
# from pygeodesy.internals import typename # from .named
|
|
24
|
-
from pygeodesy.interns import _delta_, _distant_, _inside_, _SPACE_, _too_
|
|
25
|
-
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
|
|
26
|
-
from pygeodesy.named import _name__, _name2__, _NamedTuple,
|
|
26
|
+
from pygeodesy.interns import _delta_, _distant_, _DOT_, _inside_, _SPACE_, _too_
|
|
27
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS
|
|
28
|
+
from pygeodesy.named import callername, _name__, _name2__, _NamedTuple, \
|
|
29
|
+
_xnamed, typename
|
|
27
30
|
from pygeodesy.namedTuples import Bearing2Tuple, Distance4Tuple, LatLon2Tuple, \
|
|
28
31
|
Intersection3Tuple, PhiLam2Tuple
|
|
32
|
+
from pygeodesy.props import property_ROnce
|
|
29
33
|
# from pygeodesy.streprs import Fmt, unstr # from .fsums
|
|
30
34
|
# from pygeodesy.triaxials.triaxial5 import _hartzell3 # _MODS
|
|
31
35
|
from pygeodesy.units import _isDegrees, _isHeight, _isRadius, Bearing, Degrees_, \
|
|
@@ -42,14 +46,241 @@ from contextlib import contextmanager
|
|
|
42
46
|
from math import atan, cos, degrees, fabs, radians, sin, sqrt # pow
|
|
43
47
|
|
|
44
48
|
__all__ = _ALL_LAZY.formy
|
|
45
|
-
__version__ = '25.12.
|
|
49
|
+
__version__ = '25.12.31'
|
|
46
50
|
|
|
47
51
|
_RADIANS2 = radians(_1_0)**2 # degree to radians-squared
|
|
48
52
|
_ratio_ = 'ratio'
|
|
49
|
-
_TOL53 = sqrt(pow(_0_5, _DIG53)) # elliperim
|
|
50
53
|
_xline_ = 'xline'
|
|
51
54
|
|
|
52
55
|
|
|
56
|
+
class Elliperim(object):
|
|
57
|
+
'''Singleton with various methods to compute the perimeter of an ellipse.
|
|
58
|
+
'''
|
|
59
|
+
_TOL53 = sqrt(EPS_2) # sqrt(pow(_0_5, _DIG53))
|
|
60
|
+
_TOL53_53 = _TOL53 / _DIG53 # "flat" b/a tolerance, 1.9e-10
|
|
61
|
+
# assert _DIG53 == 53
|
|
62
|
+
|
|
63
|
+
def AGM(self, a, b, maxit=_DIG53):
|
|
64
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{AGM
|
|
65
|
+
<https://PaulBourke.net/geometry/ellipsecirc>} (Arithmetic-Geometric Mean) method.
|
|
66
|
+
|
|
67
|
+
@kwarg maxit: Number of iterations (C{int}).
|
|
68
|
+
|
|
69
|
+
@raise ValueError: No convergence for B{C{maxit}} iterations.
|
|
70
|
+
'''
|
|
71
|
+
_, p, a, b = self._pab4(a, b)
|
|
72
|
+
if p is None:
|
|
73
|
+
c_ = []
|
|
74
|
+
ts = self._AGMs(a, b, max(maxit, _DIG53), c_)
|
|
75
|
+
p = fsum(ts, nonfinites=True)
|
|
76
|
+
p *= PI / c_[0]
|
|
77
|
+
return p
|
|
78
|
+
|
|
79
|
+
def _AGMs(self, a, b, maxit, c_):
|
|
80
|
+
'''(INTERNAL) Yield the C{AGM} terms and final C{c}.
|
|
81
|
+
'''
|
|
82
|
+
c = a + b
|
|
83
|
+
yield c**2
|
|
84
|
+
m = -1
|
|
85
|
+
t = self._TOL53
|
|
86
|
+
for _ in range(maxit): # 4..5 trips
|
|
87
|
+
b = sqrt(a * b)
|
|
88
|
+
a = c * _0_5
|
|
89
|
+
c = a + b
|
|
90
|
+
d = a - b
|
|
91
|
+
m *= 2
|
|
92
|
+
yield d**2 * m
|
|
93
|
+
if d <= (b * t):
|
|
94
|
+
break
|
|
95
|
+
else:
|
|
96
|
+
raise self._Error(maxit, d, b * t)
|
|
97
|
+
c_.append(c) # kludge
|
|
98
|
+
|
|
99
|
+
def Arc43(self, a, b):
|
|
100
|
+
'''Return the perimeter (and arcs) of an ellipse with semi-axes C{a} and C{b}
|
|
101
|
+
with the U{4-Arc<https://PaulBourke.net/geometry/ellipsecirc>} approximation.
|
|
102
|
+
|
|
103
|
+
@return: 3-Tuple C{(p, Ra, Rb)} with perimeter C{p}, arc radius C{Ra} at the
|
|
104
|
+
major and arc radius C{Rb} at the minor semi-axis.
|
|
105
|
+
'''
|
|
106
|
+
_r, p, a, b = self._pab4(a, b)
|
|
107
|
+
if p is None:
|
|
108
|
+
h = hypot(a, b)
|
|
109
|
+
p = atan2(b, a)
|
|
110
|
+
s, c = sincos2(p)
|
|
111
|
+
L = (h - b) * _0_5
|
|
112
|
+
Ra = L / c
|
|
113
|
+
Rb = (h - L) / s
|
|
114
|
+
p = Rb * p + Ra * (PI_2 - p)
|
|
115
|
+
p *= _4_0
|
|
116
|
+
else: # circle or flat
|
|
117
|
+
Ra, Rb = a, b
|
|
118
|
+
return (p, Rb, Ra) if _r else (p, Ra, Rb)
|
|
119
|
+
|
|
120
|
+
# def CR(self, a, b):
|
|
121
|
+
# '''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using U{Rackauckas'
|
|
122
|
+
# <https://www.ChrisRackauckas.com/assets/Papers/ChrisRackauckas-The_Circumference_of_an_Ellipse.pdf>}
|
|
123
|
+
# approximation, also U{here<https://ExtremeLearning.com.AU/a-formula-for-the-perimeter-of-an-ellipse>}.
|
|
124
|
+
# '''
|
|
125
|
+
# _, p, a, b = self._pab4(a, b)
|
|
126
|
+
# if p is None:
|
|
127
|
+
# p = a + b
|
|
128
|
+
# h = ((a - b) / p)**2
|
|
129
|
+
# p *= (fhorner(h, 135168, -85760, -5568, 3867) /
|
|
130
|
+
# fhorner(h, 135168, -119552, 22208, 345)) * PI
|
|
131
|
+
# return p
|
|
132
|
+
|
|
133
|
+
def E2k(self, a, b):
|
|
134
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} from the complete
|
|
135
|
+
elliptic integral of the 2nd kind L{E(k)<pygeodesy.elliptic.Elliptic.cE>}.
|
|
136
|
+
'''
|
|
137
|
+
return self._ellip2k(a, b, self._ellipE)
|
|
138
|
+
|
|
139
|
+
def e2k(self, a, b, E_alt=None):
|
|
140
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using U{SciPy's
|
|
141
|
+
ellipe<https://www.JohnDCook.com/perimeter_ellipse.html>} function or method
|
|
142
|
+
C{E_alt}, otherwise C{None}.
|
|
143
|
+
|
|
144
|
+
@kwarg E_alt: An other C{Elliperim}C{(a, b)} method to use in case C{SciPy's
|
|
145
|
+
ellipe} is not available.
|
|
146
|
+
'''
|
|
147
|
+
p = self._ellipe
|
|
148
|
+
if p is not None: # i.e. callable
|
|
149
|
+
p = self._ellip2k(a, b, p)
|
|
150
|
+
elif callable(E_alt): # and E_alt is not Elliperim.e2k
|
|
151
|
+
p = E_alt(a, b)
|
|
152
|
+
return p
|
|
153
|
+
|
|
154
|
+
def _ellipE(self, k):
|
|
155
|
+
'''(INTERNAL) Get the complete C{elliptic} integeral C{E(k)}.
|
|
156
|
+
'''
|
|
157
|
+
return _MODS.elliptic.Elliptic(k).cE
|
|
158
|
+
|
|
159
|
+
@property_ROnce
|
|
160
|
+
def _ellipe(self):
|
|
161
|
+
'''(INTERNAL) Wrap function C{scipy.special.ellipe}, I{once}.
|
|
162
|
+
'''
|
|
163
|
+
try:
|
|
164
|
+
from scipy.special import ellipe
|
|
165
|
+
|
|
166
|
+
def _ellipe(k):
|
|
167
|
+
return float(ellipe(k))
|
|
168
|
+
|
|
169
|
+
except (AttributeError, ImportError):
|
|
170
|
+
_ellipe = None
|
|
171
|
+
return _ellipe # overwrite property_ROnce
|
|
172
|
+
|
|
173
|
+
def _ellip2k(self, a, b, _ellip):
|
|
174
|
+
'''(INTERNAL) Helper for methods C{E2k} and C{e2k}.
|
|
175
|
+
'''
|
|
176
|
+
_, p, a, b = self._pab4(a, b)
|
|
177
|
+
if p is None: # see .ellipsoids.Ellipsoid.L
|
|
178
|
+
k = _1_0 - (b / a)**2
|
|
179
|
+
p = _ellip(k) * a * _4_0
|
|
180
|
+
return p
|
|
181
|
+
|
|
182
|
+
def _Error(self, maxit, d, t):
|
|
183
|
+
return _ValueError(maxit=maxit, txt=Fmt.no_convergence(d, t))
|
|
184
|
+
|
|
185
|
+
def GK(self, a, b):
|
|
186
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{Gauss-Kummer
|
|
187
|
+
<https://www.JohnDCook.com/blog/2023/05/28/approximate-ellipse-perimeter>} series, and
|
|
188
|
+
U{here<https://www.MathsIsFun.com/geometry/ellipse-perimeter.html>}, C{B{b / a} > 0.75}.
|
|
189
|
+
'''
|
|
190
|
+
_, p, a, b = self._pab4(a, b)
|
|
191
|
+
if p is None:
|
|
192
|
+
p = a + b
|
|
193
|
+
h = (a - b) / p
|
|
194
|
+
p *= fhorner(h**2, *self._GKs) * PI
|
|
195
|
+
return p
|
|
196
|
+
|
|
197
|
+
@property_ROnce
|
|
198
|
+
def _GKs(self):
|
|
199
|
+
'''(INTERNAL) Compute the Gauss-Kummer coefficients, I{once}.
|
|
200
|
+
'''
|
|
201
|
+
return (1, 1 / 4, 1 / 64, 1 / 256, 25 / 16384, 49 / 65536,
|
|
202
|
+
441 / 1048576, 1089 / 4194304) # overwrite property_ROnce
|
|
203
|
+
|
|
204
|
+
def HG(self, a, b, maxit=_DIG53):
|
|
205
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{HG
|
|
206
|
+
<https://web.Tecnico.ULisboa.PT/~mcasquilho/compute/com/,ellips/PerimeterOfEllipse.pdf>}
|
|
207
|
+
(HyperGeometric Gauss-Kummer) series.
|
|
208
|
+
|
|
209
|
+
@kwarg maxit: Number of iterations (C{int}), sufficient for C{B{b / a} > 0.125}.
|
|
210
|
+
|
|
211
|
+
@raise ValueError: No convergence for B{C{maxit}} iterations.
|
|
212
|
+
'''
|
|
213
|
+
_, p, a, b = self._pab4(a, b)
|
|
214
|
+
if p is None:
|
|
215
|
+
p = a + b
|
|
216
|
+
h = (a - b) / p
|
|
217
|
+
ts = self._HGs(h, max(maxit, _DIG53))
|
|
218
|
+
p *= fsum(ts, nonfinites=True) * PI
|
|
219
|
+
return p
|
|
220
|
+
|
|
221
|
+
def _HGs(self, h, maxit):
|
|
222
|
+
'''(INTERNAL) Yield the C{HG} terms.
|
|
223
|
+
'''
|
|
224
|
+
t = s_ = -1
|
|
225
|
+
s = _1_0
|
|
226
|
+
yield s
|
|
227
|
+
for u in range(-1, maxit * 2, 2):
|
|
228
|
+
t *= u / (u + 3) * h
|
|
229
|
+
t2 = t**2
|
|
230
|
+
s += t2
|
|
231
|
+
yield t2
|
|
232
|
+
if s == s_:
|
|
233
|
+
break
|
|
234
|
+
s_ = s
|
|
235
|
+
else:
|
|
236
|
+
s -= s_ - t2
|
|
237
|
+
raise self._Error(maxit, t2, s)
|
|
238
|
+
|
|
239
|
+
# def LS(self, a, b):
|
|
240
|
+
# '''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{Linderholm-Segal
|
|
241
|
+
# <https://www.JohnDCook.com/blog/2021/03/24/perimeter-of-an-ellipse>} formula, aka C{3/2 norm}.
|
|
242
|
+
# '''
|
|
243
|
+
# _, p, a, b = self._pab4(a, b)
|
|
244
|
+
# if p is None:
|
|
245
|
+
# p = pow(a, _1_5) + pow(b, _1_5)
|
|
246
|
+
# p = pow(p * _0_5, _2_3rd) * PI2
|
|
247
|
+
# return p
|
|
248
|
+
|
|
249
|
+
def _pab4(self, a, b):
|
|
250
|
+
_r = a < b
|
|
251
|
+
if _r:
|
|
252
|
+
a, b = b, a
|
|
253
|
+
if a > b:
|
|
254
|
+
if b > (a * self._TOL53_53):
|
|
255
|
+
p = None
|
|
256
|
+
elif b < 0:
|
|
257
|
+
t = callername() # underOK=True
|
|
258
|
+
t = _DOT_(typename(self), t)
|
|
259
|
+
raise _ValueError(unstr(t, a, b))
|
|
260
|
+
else: # "flat"
|
|
261
|
+
p = a * _4_0
|
|
262
|
+
else: # circle
|
|
263
|
+
p = a * PI2
|
|
264
|
+
return _r, p, a, b
|
|
265
|
+
|
|
266
|
+
def R2(self, a, b):
|
|
267
|
+
'''Return the perimeter of an ellipse with semi-axes C{a} and C{b} using U{Ramanujan's
|
|
268
|
+
2nd<https://PaulBourke.net/geometry/ellipsecirc>} approximation, C{B{b / a} > 0.9}.
|
|
269
|
+
'''
|
|
270
|
+
_, p, a, b = self._pab4(a, b)
|
|
271
|
+
if p is None:
|
|
272
|
+
p = a + b
|
|
273
|
+
h = (a - b) / p
|
|
274
|
+
h *= _3_0 * h
|
|
275
|
+
h /= sqrt(_4_0 - h) + _10_0 # /= chokes PyChecker?
|
|
276
|
+
p *= (h + _1_0) * PI
|
|
277
|
+
return p
|
|
278
|
+
|
|
279
|
+
if not _FOR_DOCS: # PYCHOK force epydoc
|
|
280
|
+
Elliperim = Elliperim() # singleton
|
|
281
|
+
del _FOR_DOCS
|
|
282
|
+
|
|
283
|
+
|
|
53
284
|
def angle2chord(rad, radius=R_M):
|
|
54
285
|
'''Get the chord length of a (central) angle or I{angular} distance.
|
|
55
286
|
|
|
@@ -369,104 +600,12 @@ def _dS(fun_, radius, wrap, *lls, **adjust):
|
|
|
369
600
|
|
|
370
601
|
|
|
371
602
|
def elliperim(a, b):
|
|
372
|
-
'''Compute the perimeter of an ellipse with semi-axes C{a} and C{b}
|
|
373
|
-
|
|
374
|
-
<https://PaulBourke.net/geometry/ellipsecirc>} (Arithmetic Geometric Mean) method.
|
|
603
|
+
'''Compute the perimeter of an ellipse with semi-axes C{a} and C{b}
|
|
604
|
+
using the C{Elliperim.e2k} or C{Elliperim.AGM} method.
|
|
375
605
|
|
|
376
606
|
@return: The perimeter (C{scalar}, same units as C{a} and C{b}).
|
|
377
607
|
'''
|
|
378
|
-
|
|
379
|
-
a, b = b, a
|
|
380
|
-
if 0 < b < a:
|
|
381
|
-
try:
|
|
382
|
-
from scipy.special import ellipe
|
|
383
|
-
a *= float(ellipe(_1_0 - (b / a)**2)) * _4_0
|
|
384
|
-
except (AttributeError, ImportError):
|
|
385
|
-
# relative accuracy is about _TOL53**2
|
|
386
|
-
if (b * _DIG53) > (a * _TOL53):
|
|
387
|
-
c = a + b
|
|
388
|
-
d = a - b
|
|
389
|
-
m, s = -1, [c**2]
|
|
390
|
-
_s = s.append
|
|
391
|
-
while d > (b * _TOL53) and len(s) < 32: # 4..5 trips
|
|
392
|
-
b = sqrt(a * b)
|
|
393
|
-
a = c * _0_5
|
|
394
|
-
c = a + b
|
|
395
|
-
d = a - b
|
|
396
|
-
m *= 2
|
|
397
|
-
_s(m * d**2)
|
|
398
|
-
a = fsumf_(*s) * PI / c
|
|
399
|
-
else: # near flat
|
|
400
|
-
a *= _4_0
|
|
401
|
-
elif b < 0:
|
|
402
|
-
raise _ValueError(unstr(elliperim, a, b))
|
|
403
|
-
else: # circle or flat
|
|
404
|
-
a *= PI2 if b else _4_0
|
|
405
|
-
return a
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
# def elliperimR2(a, b):
|
|
409
|
-
# '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using
|
|
410
|
-
# Ramanujan's U{2nd approximation<https://PaulBourke.net/geometry/ellipsecirc>}.
|
|
411
|
-
# '''
|
|
412
|
-
# if a < 0 or b < 0:
|
|
413
|
-
# raise ValueError(unstr(elliperimR2, a, b))
|
|
414
|
-
# p = a + b
|
|
415
|
-
# if p:
|
|
416
|
-
# t = ((a - b) / p)**2 * _3_0
|
|
417
|
-
# t = t / (_10_0 + sqrt(_4_0 - t)) + _1_0
|
|
418
|
-
# p *= t * PI
|
|
419
|
-
# return p
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
# def elliperim4arc3(a, b):
|
|
423
|
-
# '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using
|
|
424
|
-
# the U{4 arc approximation<https://PaulBourke.net/geometry/ellipsecirc>}.
|
|
425
|
-
#
|
|
426
|
-
# @return: 3-Tuple C{(p, ra, rb)} with perimeter C{p}, arc radius C{ra}
|
|
427
|
-
# at the major and arc radius C{rb} at the minor semi-axes.
|
|
428
|
-
# '''
|
|
429
|
-
# _r = a < b
|
|
430
|
-
# if _r:
|
|
431
|
-
# a, b = b, a
|
|
432
|
-
# if 0 < b < a:
|
|
433
|
-
# h = hypot(a, b)
|
|
434
|
-
# L = (h - b) * _0_5
|
|
435
|
-
# p = atan2(b, a)
|
|
436
|
-
# s, c = sincos2(p)
|
|
437
|
-
# ra = L / c
|
|
438
|
-
# rb = (h - L) / s
|
|
439
|
-
# p = rb * p + ra * (PI_2 - p)
|
|
440
|
-
# elif b < 0:
|
|
441
|
-
# raise ValueError(unstr(elliperim4arc3, a, b))
|
|
442
|
-
# elif b == a:
|
|
443
|
-
# ra = rb = a
|
|
444
|
-
# p = a * PI_2
|
|
445
|
-
# else: # b == 0
|
|
446
|
-
# ra, rb = _0_0, a
|
|
447
|
-
# p = a
|
|
448
|
-
# p *= _4_0
|
|
449
|
-
# if _r:
|
|
450
|
-
# ra, rb = rb, ra
|
|
451
|
-
# return p, ra, rb
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# def elliperimGKS(a, b):
|
|
455
|
-
# '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{Gauss-Kummer
|
|
456
|
-
# Series<https://www.JohnDCook.com/blog/2023/05/28/approximate-ellipse-perimeter/>}.
|
|
457
|
-
# '''
|
|
458
|
-
# if a < b:
|
|
459
|
-
# a, b = b, a
|
|
460
|
-
# if b < 0:
|
|
461
|
-
# raise ValueError(unstr(elliperimGKS, a, b))
|
|
462
|
-
# if b:
|
|
463
|
-
# p = a + b
|
|
464
|
-
# h = (a - b) / p
|
|
465
|
-
# h *= h
|
|
466
|
-
# p *= (1 + h * (1 / 4 + h * (1 / 64 + h * (1 / 256 + h * (25 / 16384 + h * (49 / 65536)))))) * PI
|
|
467
|
-
# else:
|
|
468
|
-
# p = _4_0 * a
|
|
469
|
-
# return p
|
|
608
|
+
return Elliperim.e2k(a, b, Elliperim.E2k)
|
|
470
609
|
|
|
471
610
|
|
|
472
611
|
def _ellipsoidal(earth, where):
|