pygeodesy 25.4.25__py2.py3-none-any.whl → 25.5.5__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 CHANGED
@@ -1,11 +1,11 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- u'''A pure Python implementation of geodesy tools for various ellipsoidal and spherical earth
5
- models using precision exact, elliptic, trigonometric, vector-based, iterative and approximate
6
- methods for geodetic (lat-/longitude), geocentric (U{ECEF<https://WikiPedia.org/wiki/ECEF>}
7
- cartesian) and certain U{triaxial ellipsoidal<https://GeographicLib.SourceForge.io/1.44/triaxial.html>}
8
- coordinates.
4
+ u'''A pure Python implementation of geodesy tools for various ellipsoidal and spherical earth models
5
+ using precision exact, elliptic, trigonometric, vector-based, iterative and approximate methods for
6
+ geodetic (lat-/longitude), geocentric (U{ECEF<https://WikiPedia.org/wiki/ECEF>} cartesian), local (U{LTP
7
+ <https://WikiPedia.org/wiki/Local_tangent_plane_coordinates>}) and certain U{triaxial ellipsoidal
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
11
  2005-2024} and from several U{C++ classes<https://GeographicLib.SourceForge.io/C++/doc/annotated.html>} by I{Charles
@@ -406,6 +406,7 @@ if _init__all__ and not _lazy_import2: # import and set __all__
406
406
  import pygeodesy.deprecated as deprecated # PYCHOK exported
407
407
  import pygeodesy.dms as dms # PYCHOK exported
408
408
  import pygeodesy.ecef as ecef # PYCHOK exported
409
+ import pygeodesy.ecefLocals as ecefLocals # PYCHOK exported
409
410
  import pygeodesy.elevations as elevations # PYCHOK exported
410
411
  import pygeodesy.ellipsoidalBase as ellipsoidalBase # PYCHOK INTERNAL
411
412
  import pygeodesy.ellipsoidalBaseDI as ellipsoidalBaseDI # PYCHOK INTERNAL
@@ -491,6 +492,7 @@ if _init__all__ and not _lazy_import2: # import and set __all__
491
492
  from pygeodesy.deprecated import * # PYCHOK __all__ DEPRECATED
492
493
  from pygeodesy.dms import * # PYCHOK __all__
493
494
  from pygeodesy.ecef import * # PYCHOK __all__
495
+ # from pygeodesy.ecefLocals import * # PYCHOK __all__
494
496
  from pygeodesy.elevations import * # PYCHOK __all__
495
497
  # from pygeodesy.ellipsoidalBase import * # PYCHOK __(_)__ INTERNAL
496
498
  # from pygeodesy.ellipsoidalBaseDI import * # PYCHOK __(_)__ INTERNAL
@@ -602,7 +604,7 @@ else:
602
604
 
603
605
  from pygeodesy.internals import _version2, _DOT_ # PYCHOK import
604
606
  # from pygeodesy.interns import _DOT_ # from .internals
605
- __version__ = '25.04.25'
607
+ __version__ = '25.05.05'
606
608
  # see setup.py for similar logic
607
609
  version = _DOT_(*_version2(__version__, n=3))
608
610
 
pygeodesy/basics.py CHANGED
@@ -616,7 +616,7 @@ def map1(fun1, *xs): # XXX map_
616
616
 
617
617
  @return: Function results (C{tuple}).
618
618
  '''
619
- return tuple(map(fun1, xs))
619
+ return tuple(map(fun1, xs)) # if len(xs) != 1 else fun1(xs[0])
620
620
 
621
621
 
622
622
  def map2(fun, *xs, **strict):
pygeodesy/booleans.py CHANGED
@@ -22,7 +22,7 @@ from pygeodesy.basics import _isin, isodd, issubclassof, map2, \
22
22
  from pygeodesy.constants import EPS, EPS2, INT0, _0_0, _0_5, _1_0
23
23
  from pygeodesy.errors import ClipError, _IsnotError, _TypeError, \
24
24
  _ValueError, _xattr, _xkwds_get, _xkwds_pop2
25
- from pygeodesy.fmath import favg, fdot_, hypot, hypot2
25
+ from pygeodesy.fmath import favg, Fdot_, fdot_, hypot, hypot2
26
26
  # from pygeodesy.fsums import fsum1 # _MODS
27
27
  # from pygeodesy.internals import typename # from .basics
28
28
  from pygeodesy.interns import NN, _BANG_, _clipid_, _COMMASPACE_, \
@@ -45,7 +45,7 @@ from pygeodesy.utily import fabs, _unrollon, _Wrap
45
45
  # from math import fabs # from .utily
46
46
 
47
47
  __all__ = _ALL_LAZY.booleans
48
- __version__ = '25.04.14'
48
+ __version__ = '25.04.30'
49
49
 
50
50
  _0EPS = EPS # near-zero, positive
51
51
  _EPS0 = -EPS # near-zero, negative
@@ -1612,8 +1612,8 @@ class _EdgeGH(object):
1612
1612
 
1613
1613
  def _alpha2(self, x, y, dx, dy):
1614
1614
  # Return C{(alpha)}, see .points.nearestOn5
1615
- a = fdot_(y, dy, x, dx) / self._hypot2
1616
- d = fdot_(y, dx, -x, dy) / self._hypot0
1615
+ a = Fdot_(y, dy, x, dx).fover(self._hypot2)
1616
+ d = Fdot_(y, dx, -x, dy).fover(self._hypot0)
1617
1617
  return a, fabs(d)
1618
1618
 
1619
1619
  def _Error(self, n, *args, **kwds): # PYCHOK no cover
@@ -1655,10 +1655,10 @@ class _EdgeGH(object):
1655
1655
  if fabs(d) > _0EPS: # non-parallel edges
1656
1656
  dx = x - c1_x
1657
1657
  dy = y - c1_y
1658
- ca = fdot_(sx, dy, -sy, dx) / d
1658
+ ca = Fdot_(sx, dy, -sy, dx).fover(d)
1659
1659
  if _0EPS < ca < _EPS1 or (self._xtend and
1660
1660
  _EPS0 < ca < _1EPS):
1661
- sa = fdot_(cx, dy, -cy, dx) / d
1661
+ sa = Fdot_(cx, dy, -cy, dx).fover(d)
1662
1662
  if _0EPS < sa < _EPS1 or (self._xtend and
1663
1663
  _EPS0 < sa < _1EPS):
1664
1664
  yield (y + sa * sy), (x + sa * sx), sa, ca
@@ -1669,7 +1669,7 @@ class _EdgeGH(object):
1669
1669
 
1670
1670
  elif self._raiser and not (ca < _EPS0 or ca > _1EPS): # PYCHOK no cover
1671
1671
  # intersection at c1 or c2 or at c1 or c2 and s1 or s2
1672
- sa = fdot_(cx, dy, -cy, dx) / d
1672
+ sa = Fdot_(cx, dy, -cy, dx).fover(d)
1673
1673
  e = 2 if sa < _EPS0 or sa > _1EPS else 3
1674
1674
  raise self._Error(e, c1, c2, ca=ca)
1675
1675
 
@@ -16,6 +16,7 @@ from pygeodesy.constants import EPS, EPS0, INT0, PI2, _isfinite, isnear0, \
16
16
  from pygeodesy.datums import Datum, _earth_ellipsoid, _spherical_datum, \
17
17
  Transform, _WGS84
18
18
  # from pygeodesy.ecef import EcefKarney # _MODS
19
+ from pygeodesy.ecefLocals import _EcefLocal
19
20
  from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, _xattr, \
20
21
  _xdatum, _xkwds, _xkwds_get, _xkwds_pop2
21
22
  from pygeodesy.fmath import cbrt, hypot, hypot_, hypot2, fabs, sqrt # hypot
@@ -25,7 +26,7 @@ from pygeodesy.fsums import fsumf_, Fmt
25
26
  from pygeodesy.interns import _COMMASPACE_, _datum_, _no_, _phi_
26
27
  from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!
27
28
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
28
- from pygeodesy.named import _name2__, _NamedLocal, _Pass
29
+ from pygeodesy.named import _name2__, _Pass
29
30
  from pygeodesy.namedTuples import LatLon4Tuple, _NamedTupleTo , Vector3Tuple, \
30
31
  Vector4Tuple, Bearing2Tuple # PYCHOK .sphericalBase
31
32
  # from pygeodesy.nvectorBase import _N_vector # _MODS
@@ -43,14 +44,14 @@ from pygeodesy.vector3d import Vector3d, _xyzhdlln4
43
44
  # from math import degrees, fabs, radians, sqrt # from .fmath, .utily
44
45
 
45
46
  __all__ = _ALL_LAZY.cartesianBase
46
- __version__ = '25.04.21'
47
+ __version__ = '25.04.28'
47
48
 
48
49
  _r_ = 'r'
49
50
  _resections = _MODS.into(resections=__name__)
50
51
  _theta_ = 'theta'
51
52
 
52
53
 
53
- class CartesianBase(Vector3d, _NamedLocal):
54
+ class CartesianBase(Vector3d, _EcefLocal):
54
55
  '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.
55
56
  '''
56
57
  _datum = None # L{Datum}, to be overriden
@@ -200,7 +201,7 @@ class CartesianBase(Vector3d, _NamedLocal):
200
201
  '''
201
202
  n, kwds = _name2__(name_Cartesian_kwds, _or_nameof=self)
202
203
  if Cartesian is None:
203
- r = self._Ltp._local2ecef(delta, nine=True)
204
+ r = self._ltp._local2ecef(delta, nine=True) # _EcefLocal._ltp
204
205
  else:
205
206
  d = self.datum
206
207
  if not d:
@@ -208,7 +209,7 @@ class CartesianBase(Vector3d, _NamedLocal):
208
209
  t = _xkwds_get(kwds, datum=d)
209
210
  if _xattr(t, ellipsoid=None) != d.ellipsoid:
210
211
  raise _TypeError(datum=t, txt=str(d))
211
- c = self._Ltp._local2ecef(delta, nine=False)
212
+ c = self._ltp._local2ecef(delta, nine=False) # _EcefLocal._ltp
212
213
  r = Cartesian(*c, **kwds)
213
214
  return r.renamed(n) if n else r
214
215
 
pygeodesy/ecef.py CHANGED
@@ -63,19 +63,19 @@ from pygeodesy.constants import EPS, EPS0, EPS02, EPS1, EPS2, EPS_2, INT0, PI, P
63
63
  _0_0, _0_0001, _0_01, _0_5, _1_0, _1_0_1T, _N_1_0, \
64
64
  _2_0, _N_2_0, _3_0, _4_0, _6_0, _60_0, _90_0, _N_90_0, \
65
65
  _100_0, _copysign_1_0, isnon0 # PYCHOK used!
66
- from pygeodesy.datums import a_f2Tuple, _ellipsoidal_datum, _WGS84, _EWGS84
66
+ from pygeodesy.datums import _ellipsoidal_datum, _WGS84, a_f2Tuple, _EWGS84
67
+ from pygeodesy.ecefLocals import _EcefLocal
67
68
  # from pygeodesy.ellipsoids import a_f2Tuple, _EWGS84 # from .datums
68
69
  from pygeodesy.errors import _IndexError, LenError, _ValueError, _TypesError, \
69
70
  _xattr, _xdatum, _xkwds, _xkwds_get
70
- from pygeodesy.fmath import cbrt, fdot, Fpowers, hypot, hypot1, hypot2_, sqrt0
71
+ from pygeodesy.fmath import cbrt, fdot, hypot, hypot1, hypot2_, sqrt0
71
72
  from pygeodesy.fsums import Fsum, fsumf_, Fmt, unstr
72
73
  # from pygeodesy.internals import typename # from .basics
73
74
  from pygeodesy.interns import NN, _a_, _C_, _datum_, _ellipsoid_, _f_, _height_, \
74
75
  _lat_, _lon_, _M_, _name_, _singular_, _SPACE_, \
75
76
  _x_, _xyz_, _y_, _z_
76
77
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
77
- from pygeodesy.named import _name__, _name1__, _NamedBase, _NamedLocal, \
78
- _NamedTuple, _Pass, _xnamed
78
+ from pygeodesy.named import _name__, _name1__, _NamedBase, _NamedTuple, _Pass, _xnamed
79
79
  from pygeodesy.namedTuples import LatLon2Tuple, LatLon3Tuple, \
80
80
  PhiLam2Tuple, Vector3Tuple, Vector4Tuple
81
81
  from pygeodesy.props import deprecated_method, Property_RO, property_RO, \
@@ -90,7 +90,7 @@ from pygeodesy.utily import atan1, atan1d, atan2, atan2d, degrees90, degrees180,
90
90
  from math import cos, degrees, fabs, radians, sqrt
91
91
 
92
92
  __all__ = _ALL_LAZY.ecef
93
- __version__ = '25.04.24'
93
+ __version__ = '25.04.28'
94
94
 
95
95
  _Ecef_ = 'Ecef'
96
96
  _prolate_ = 'prolate'
@@ -110,6 +110,7 @@ class _EcefBase(_NamedBase):
110
110
  '''
111
111
  _datum = _WGS84
112
112
  _E = _EWGS84
113
+ _isYou = False
113
114
  _lon00 = INT0 # arbitrary, "polar" lon for LocalCartesian, Ltp
114
115
 
115
116
  def __init__(self, a_ellipsoid=_EWGS84, f=None, lon00=INT0, **name):
@@ -272,12 +273,6 @@ class _EcefBase(_NamedBase):
272
273
  return (Ecef9Tuple, # overwrite property_ROver
273
274
  _MODS.vector3d.Vector3d) # _MODS.cartesianBase.CartesianBase
274
275
 
275
- @Property_RO
276
- def _isYou(self):
277
- '''(INTERNAL) Is this an C{EcefYou}?.
278
- '''
279
- return isinstance(self, EcefYou)
280
-
281
276
  @property
282
277
  def lon00(self):
283
278
  '''Get the I{"polar"} longitude (C{degrees}), see method C{reverse}.
@@ -790,6 +785,7 @@ class EcefYou(_EcefBase):
790
785
  11589/115114_9021_geod2ellip_final.pdf>} Studia Geophysica et Geodaetica, 2008, 52,
791
786
  pages 1-18 and U{PyMap3D <https://PyPI.org/project/pymap3d>}.
792
787
  '''
788
+ _isYou = True
793
789
 
794
790
  def __init__(self, a_ellipsoid=_EWGS84, f=None, **lon00_name): # PYCHOK signature
795
791
  _EcefBase.__init__(self, a_ellipsoid, f=f, **lon00_name) # inherited documentation
@@ -825,13 +821,14 @@ class EcefYou(_EcefBase):
825
821
  ellipsoid is I{prolate}.
826
822
  '''
827
823
  x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **lon00_name)
824
+ q = hypot(x, y) # R
828
825
 
829
826
  E = self.ellipsoid
830
827
  e, e2 = self._ee2
831
828
 
832
- q = hypot(x, y) # R
833
- u = Fpowers(2, x, y, z) - e2
834
- u = u.fadd_(hypot(u, e * z * _2_0)).fover(_2_0)
829
+ u = hypot2_(x, y, z) - e2
830
+ u += hypot(u, e * z * _2_0)
831
+ u *= _0_5
835
832
  if u > EPS02:
836
833
  u = sqrt(u)
837
834
  p = hypot(u, e)
@@ -1019,7 +1016,7 @@ class EcefMatrix(_NamedTuple):
1019
1016
  return xyz_
1020
1017
 
1021
1018
 
1022
- class Ecef9Tuple(_NamedTuple, _NamedLocal):
1019
+ class Ecef9Tuple(_NamedTuple, _EcefLocal):
1023
1020
  '''9-Tuple C{(x, y, z, lat, lon, height, C, M, datum)} with I{geocentric} C{x},
1024
1021
  C{y} and C{z} plus I{geodetic} C{lat}, C{lon} and C{height}, case C{C} (see
1025
1022
  the C{Ecef*.reverse} methods) and optionally, rotation matrix C{M} (L{EcefMatrix})
@@ -1041,7 +1038,7 @@ class Ecef9Tuple(_NamedTuple, _NamedLocal):
1041
1038
  return self.toDatum(datum2)
1042
1039
 
1043
1040
  @property_RO
1044
- def _ecef9(self):
1041
+ def _ecef9(self): # in ._EcefLocal._Ltp_ecef2local
1045
1042
  return self
1046
1043
 
1047
1044
  @Property_RO
@@ -1103,13 +1100,6 @@ class Ecef9Tuple(_NamedTuple, _NamedLocal):
1103
1100
  '''
1104
1101
  return Lon(Vermeille=degrees(self.lamVermeille))
1105
1102
 
1106
- def _ltp_toLocal(self, ltp, Xyz_kwds, **Xyz): # overloads C{_NamedLocal}'s
1107
- '''(INTERNAL) Invoke C{ltp._xLtp(ltp)._ecef2local}.
1108
- '''
1109
- Xyz_ = self._ltp_toLocal2(Xyz_kwds, **Xyz) # in ._NamedLocal
1110
- ltp = self._ltp._xLtp(ltp, self._Ltp) # both in ._NamedLocal
1111
- return ltp._ecef2local(self, *Xyz_)
1112
-
1113
1103
  @Property_RO
1114
1104
  def phi(self):
1115
1105
  '''Get the latitude in C{radians} (C{float}).
@@ -0,0 +1,186 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) ECEF to local coodinate conversions, separated from
5
+ module C{ecef} to defer importing the latter into C{CartesianBase}
6
+ and C{LatLonBase}.
7
+ '''
8
+
9
+ from pygeodesy.basics import _isin, _xsubclassof
10
+ # from pygeodesy.ecef import EcefKarney # _MODS
11
+ from pygeodesy.errors import _xkwds_item2, _xkwds_pop2
12
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
13
+ # from pygeodesy import ltp as _ltp # _MODS.into
14
+ # from pygeodesy import ltpTuples as _ltpTuples # _MODS.into
15
+ from pygeodesy.named import _Named, notOverloaded
16
+ from pygeodesy.props import Property_RO, property_RO, property_ROver
17
+
18
+ __all__ = _ALL_LAZY.ecefLocals
19
+ __version__ = '25.04.28'
20
+
21
+ _ltp = _MODS.into(ltp=__name__)
22
+ _ltpTuples = _MODS.into(ltpTuples=__name__)
23
+
24
+
25
+ class _EcefLocal(_Named):
26
+ '''(INTERNAL) Base class for C{CartesianBase}, C{Ecef9Tuple} and
27
+ C{LatLonBase}, providing ECEF to local coordinate conversions.
28
+ '''
29
+
30
+ @property_ROver
31
+ def Ecef(self):
32
+ '''Get the ECEF I{class} (L{EcefKarney}), I{once}.
33
+ '''
34
+ return _MODS.ecef.EcefKarney
35
+
36
+ @property_RO
37
+ def _ecef9(self):
38
+ '''I{Must be overloaded}.'''
39
+ notOverloaded(self)
40
+
41
+ @property_RO
42
+ def _ecef9datum(self):
43
+ try:
44
+ d = self.datum # PYCHOK C{CartesianBase}, ...
45
+ except AttributeError:
46
+ d = None
47
+ return d or self._ecef9.datum
48
+
49
+ @Property_RO
50
+ def _ltp(self):
51
+ '''(INTERNAL) Cache this instance' LTP (L{Ltp}).
52
+ '''
53
+ return _ltp.Ltp(self._ecef9, ecef=self.Ecef(self._ecef9datum), name=self.name)
54
+
55
+ def _ltp_ecef2local(self, ltp, Xyz_kwds, _None=None, **Xyz): # in C{Ecef9Tuple}
56
+ '''(INTERNAL) Invoke C{_ltp._xLtp(ltp, self._ltp)._ecef2local}.
57
+ '''
58
+ C, _ = Xyz_ = _xkwds_pop2(Xyz_kwds, **Xyz)
59
+ if C is not _None: # validate C, see .toLocal
60
+ n, X = _xkwds_item2(Xyz)
61
+ if X is not C:
62
+ _xsubclassof(X, **{n: C})
63
+ ltp = _ltp._xLtp(ltp, self._ltp)
64
+ return ltp._ecef2local(self._ecef9, *Xyz_)
65
+
66
+ def toAer(self, ltp=None, **Aer_and_kwds):
67
+ '''Convert this instance to I{local} I{Azimuth, Elevation, slant Range} (AER) components.
68
+
69
+ @kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}), overriding this
70
+ instance' L{LTP<pygeodesy.ecefLocals._EcefLocal.toLtp>}.
71
+ @kwarg Aer_and_kwds: Optional AER class C{B{Aer}=}L{Aer<pygeodesy.ltpTuples.Aer>}
72
+ to use and optionally, additional B{C{Aer}} keyword arguments.
73
+
74
+ @return: An B{C{Aer}} instance.
75
+
76
+ @raise TypeError: Invalid B{C{ltp}}.
77
+
78
+ @see: Method L{toLocal<pygeodesy.ecefLocals._EcefLocal.toLocal>}.
79
+ '''
80
+ return self._ltp_ecef2local(ltp, Aer_and_kwds, Aer=_ltpTuples.Aer)
81
+
82
+ def toEnu(self, ltp=None, **Enu_and_kwds):
83
+ '''Convert this instance to I{local} I{East, North, Up} (ENU) components.
84
+
85
+ @kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}), overriding this
86
+ instance' L{LTP<pygeodesy.ecefLocals._EcefLocal.toLtp>}.
87
+ @kwarg Enu_and_kwds: Optional ENU class C{B{Enu}=}L{Enu<pygeodesy.ltpTuples.Enu>}
88
+ to use and optionally, additional B{C{Enu}} keyword arguments.
89
+
90
+ @return: An B{C{Enu}} instance.
91
+
92
+ @raise TypeError: Invalid B{C{ltp}}.
93
+
94
+ @see: Method L{toLocal<pygeodesy.ecefLocals._EcefLocal.toLocal>}.
95
+ '''
96
+ return self._ltp_ecef2local(ltp, Enu_and_kwds, Enu=_ltpTuples.Enu)
97
+
98
+ def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):
99
+ '''Convert this instance to I{local} components in a I{local tangent plane} (LTP)
100
+
101
+ @kwarg Xyz: Optional I{local} components class (L{XyzLocal}, L{Aer}, L{Enu},
102
+ L{Ned}) or C{None}.
103
+ @kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}), overriding this
104
+ cartesian's L{LTP<pygeodesy.ecefLocals._EcefLocal.toLtp>}.
105
+ @kwarg Xyz_kwds: Optionally, additional B{C{Xyz}} keyword arguments, ignored
106
+ if C{B{Xyz} is None}.
107
+
108
+ @return: An B{C{Xyz}} instance or a L{Local9Tuple}C{(x, y, z, lat, lon,
109
+ height, ltp, ecef, M)} if C{B{Xyz} is None} (with C{M=None}).
110
+
111
+ @raise TypeError: Invalid B{C{ltp}}.
112
+ '''
113
+ return self._ltp_ecef2local(ltp, Xyz_kwds, Xyz=Xyz, _None=Xyz)
114
+
115
+ def toLtp(self, Ecef=None, **name):
116
+ '''Return the I{local tangent plane} (LTP) for this instance.
117
+
118
+ @kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ... L{EcefYou}), overriding
119
+ this instance' L{Ecef<pygeodesy.ecefLocals._EcefLocal.Ecef>}.
120
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
121
+
122
+ @return: An B{C{Ltp}} instance.
123
+ '''
124
+ if _isin(Ecef, None, self.Ecef) and not name:
125
+ ltp = self._ltp
126
+ else: # like self._ltp
127
+ ecef = (Ecef if Ecef else self.Ecef)(self._ecef9datum)
128
+ ltp = _ltp.Ltp(self._ecef9, ecef=ecef, name=self._name__(name))
129
+ return ltp
130
+
131
+ def toNed(self, ltp=None, **Ned_and_kwds):
132
+ '''Convert this instance to I{local} I{North, East, Down} (NED) components.
133
+
134
+ @kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}), overriding this
135
+ instance' L{LTP<pygeodesy.ecefLocals._EcefLocal.toLtp>}.
136
+ @kwarg Ned_and_kwds: Optional NED class C{B{Ned}=}L{Ned<pygeodesy.ltpTuples.Ned>}
137
+ to use and optionally, additional B{C{Ned}} keyword arguments.
138
+
139
+ @return: An B{C{Ned}} instance.
140
+
141
+ @raise TypeError: Invalid B{C{ltp}}.
142
+
143
+ @see: Method L{toLocal<pygeodesy.ecefLocals._EcefLocal.toLocal>}.
144
+ '''
145
+ return self._ltp_ecef2local(ltp, Ned_and_kwds, Ned=_ltpTuples.Ned)
146
+
147
+ def toXyz(self, ltp=None, **Xyz_and_kwds):
148
+ '''Convert this instance to I{local} I{X, Y, Z} (XYZ) components.
149
+
150
+ @kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}), overriding this
151
+ instance' L{LTP<pygeodesy.ecefLocals._EcefLocal.toLtp>}.
152
+ @kwarg Xyz_and_kwds: Optional XYZ class C{B{Xyz}=}L{Xyz<pygeodesy.ltpTuples.XyzLocal>}
153
+ to use and optionally, additional B{C{Xyz}} keyword arguments.
154
+
155
+ @return: An B{C{Xyz}} instance.
156
+
157
+ @raise TypeError: Invalid B{C{ltp}}.
158
+
159
+ @see: Method L{toLocal<pygeodesy.ecefLocals._EcefLocal.toLocal>}.
160
+ '''
161
+ return self._ltp_ecef2local(ltp, Xyz_and_kwds, Xyz=_ltpTuples.XyzLocal)
162
+
163
+
164
+ __all__ += _ALL_DOCS(_EcefLocal)
165
+
166
+ # **) MIT License
167
+ #
168
+ # Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
169
+ #
170
+ # Permission is hereby granted, free of charge, to any person obtaining a
171
+ # copy of this software and associated documentation files (the "Software"),
172
+ # to deal in the Software without restriction, including without limitation
173
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
174
+ # and/or sell copies of the Software, and to permit persons to whom the
175
+ # Software is furnished to do so, subject to the following conditions:
176
+ #
177
+ # The above copyright notice and this permission notice shall be included
178
+ # in all copies or substantial portions of the Software.
179
+ #
180
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
181
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
182
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
183
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
184
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
185
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
186
+ # OTHER DEALINGS IN THE SOFTWARE.
pygeodesy/ellipsoids.py CHANGED
@@ -95,7 +95,7 @@ from pygeodesy.utily import atan1, atan1d, atan2b, degrees90, m2radians, radians
95
95
  from math import asinh, atan, atanh, cos, degrees, exp, fabs, radians, sin, sinh, sqrt, tan # as _tan
96
96
 
97
97
  __all__ = _ALL_LAZY.ellipsoids
98
- __version__ = '25.04.23'
98
+ __version__ = '25.04.28'
99
99
 
100
100
  _f_0_0 = Float(f =_0_0) # zero flattening
101
101
  _f__0_0 = Float(f_=_0_0) # zero inverse flattening
@@ -140,12 +140,14 @@ class a_f2Tuple(_NamedTuple):
140
140
  @arg f: Flattening (C{scalar} < 1, negative for I{prolate}).
141
141
  @kwarg name: Optional C{B{name}=NN} (C{str}).
142
142
 
143
- @return: An L{a_f2Tuple}C{(a, f)} instance.
143
+ @return: An L{a_f2Tuple}C{(a, f)}.
144
144
 
145
145
  @raise UnitError: Invalid B{C{a}} or B{C{f}}.
146
146
 
147
- @note: C{abs(B{f}) < EPS} is forced to C{B{f}=0}, I{spherical}.
148
- Negative C{B{f}} produces a I{prolate} ellipsoid.
147
+ @note: C{abs(B{f}) < }L{EPS<pygeodesy.constants.EPS>} is
148
+ forced to C{B{f}=0}, I{spherical}.
149
+
150
+ @note: Negative C{B{f}} produces a I{prolate} ellipsoid.
149
151
  '''
150
152
  a = Radius_(a=a) # low=EPS, high=None
151
153
  f = Float_( f=f, low=None, high=EPS1)
@@ -359,11 +361,16 @@ class Ellipsoid(_NamedEnumItem):
359
361
  '''
360
362
  return a_f2Tuple(self.a, self.f, name=self.name)
361
363
 
364
+ a_f2 = a_f # synonym
365
+
362
366
  @Property_RO
363
367
  def A(self):
364
368
  '''Get the UTM I{meridional (or rectifying)} radius (C{meter}).
365
369
 
366
- @see: I{Meridian arc unit} U{Q<https://StudyLib.net/doc/7443565/>}.
370
+ @note: C{A * PI / 2} ≈= L{L<Ellipsoid.L>}, the I{quarter meridian}.
371
+
372
+ @see: I{Meridian arc unit} U{Q<https://StudyLib.net/doc/7443565/>},
373
+ the mean, meridional length I{per radian}.
367
374
  '''
368
375
  A, n = self.a, self.n
369
376
  if n:
@@ -375,6 +382,11 @@ class Ellipsoid(_NamedEnumItem):
375
382
  # A *= fhorner(n**2, 1048576, 262144, 16384, 4096, 1600, 784, 441) / 1048576) / (1 + n)
376
383
  A = Radius(A=Fhorner(n**2, 1048576, 262144, 16384, 4096, 1600, 784, 441).fover(d))
377
384
  return A
385
+ # # Moritz, H. <https://Geodesy.Geology.Ohio-State.EDU/course/refpapers/00740128.pdf>
386
+ # # q = 4 / self.rocPolar
387
+ # # Q = (1 - 3 / 4 * e'2 + 45 / 64 * e'4 - 175 / 256 * e'6 + 11025 / 16384 * e'8) * rocPolar
388
+ # # = (4 + e'2 * (-3 + e'2 * (45 / 16 + e'2 * (-175 / 64 + e'2 * 11025 / 4096)))) / q
389
+ # return Radius(Q=Fhorner(self.e22, 4, -3, 45 / 16, -175 / 64, 11025 / 4096).fover(q))
378
390
 
379
391
  @Property_RO
380
392
  def _albersCyl(self):
@@ -1339,23 +1351,7 @@ class Ellipsoid(_NamedEnumItem):
1339
1351
 
1340
1352
  polaradius = b # Rpolar
1341
1353
 
1342
- # @Property_RO
1343
- # def Q(self):
1344
- # '''Get the I{meridian arc unit} C{Q}, the mean, meridional length I{per radian} C({float}).
1345
- #
1346
- # @note: C{Q * PI / 2} ≈ C{L}, the I{quarter meridian}.
1347
- #
1348
- # @see: Property C{A} and U{Engsager, K., Poder, K.<https://StudyLib.net/doc/7443565/
1349
- # a-highly-accurate-world-wide-algorithm-for-the-transverse...>}.
1350
- # '''
1351
- # n = self.n
1352
- # d = (n + _1_0) / self.a
1353
- # return Float(Q=Fhorner(n**2, _1_0, _0_25, _1_16th, _0_25).fover(d) if d else self.b)
1354
-
1355
- # # Moritz, H. <https://Geodesy.Geology.Ohio-State.EDU/course/refpapers/00740128.pdf>
1356
- # # Q = (1 - 3/4 * e'2 + 45/64 * e'4 - 175/256 * e'6 + 11025/16384 * e'8) * rocPolar
1357
- # # = (4 + e'2 * (-3 + e'2 * (45/16 + e'2 * (-175/64 + e'2 * 11025/4096)))) * rocPolar / 4
1358
- # return Fhorner(self.e22, 4, -3, 45 / 16, -175 / 64, 11025 / 4096).fover(4 / self.rocPolar)
1354
+ # Q = A # I{meridian arc unit} C{Q}, the mean, meridional length I{per radian}
1359
1355
 
1360
1356
  @deprecated_Property_RO
1361
1357
  def quarteradius(self): # PYCHOK no cover
@@ -1382,16 +1378,13 @@ class Ellipsoid(_NamedEnumItem):
1382
1378
  <https://WikiPedia.org/wiki/Earth_radius>}.
1383
1379
  '''
1384
1380
  return Radius(R2=sqrt(self.c2) if self.f else self.a)
1385
-
1386
- Rauthalic = R2
1387
-
1388
- # @Property_RO
1389
- # def R2(self):
1390
1381
  # # Moritz, H. <https://Geodesy.Geology.Ohio-State.EDU/course/refpapers/00740128.pdf>
1391
1382
  # # R2 = (1 - 2/3 * e'2 + 26/45 * e'4 - 100/189 * e'6 + 7034/14175 * e'8) * rocPolar
1392
1383
  # # = (3 + e'2 * (-2 + e'2 * (26/15 + e'2 * (-100/63 + e'2 * 7034/4725)))) * rocPolar / 3
1393
1384
  # return Fhorner(self.e22, 3, -2, 26 / 15, -100 / 63, 7034 / 4725).fover(3 / self.rocPolar)
1394
1385
 
1386
+ Rauthalic = R2
1387
+
1395
1388
  @Property_RO
1396
1389
  def R2x(self):
1397
1390
  '''Get the I{authalic} earth radius (C{meter}), M{sqrt(c2x)}.
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.04.18'
28
+ __version__ = '25.04.30'
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
@@ -66,13 +66,27 @@ class Fdot(Fsum):
66
66
  self._facc_dot(n, a, b, **kwds)
67
67
 
68
68
 
69
+ class Fdot_(Fdot):
70
+ '''Precision dot product.
71
+ '''
72
+ def __init__(self, *xys, **start_name_f2product_nonfinites_RESIDUAL):
73
+ '''New L{Fdot_} precision dot product M{sum(xys[i] * xys[i+1] for i in
74
+ range(0, len(xys), B{2}))}.
75
+
76
+ @arg xys: Pairwise values (each C{scalar}, an L{Fsum} or L{Fsum2Tuple}),
77
+ all positional.
78
+
79
+ @see: Class L{Fdot<Fdot.__init__>} for further details.
80
+ '''
81
+ Fdot.__init__(self, xys[0::2], *xys[1::2], **start_name_f2product_nonfinites_RESIDUAL)
82
+
83
+
69
84
  class Fhorner(Fsum):
70
85
  '''Precision polynomial evaluation using the Horner form.
71
86
  '''
72
87
  def __init__(self, x, *cs, **incx_name_f2product_nonfinites_RESIDUAL):
73
- '''New L{Fhorner} form evaluation of polynomial M{sum(cs[i] * x**i for
74
- i=0..n)} with in- or decreasing exponent M{sum(... i=n..0)}, where C{n
75
- = len(cs) - 1}.
88
+ '''New L{Fhorner} form evaluation of polynomial M{sum(cs[i] * x**i for i=0..n)}
89
+ with in- or decreasing exponent M{sum(... i=n..0)}, where C{n = len(cs) - 1}.
76
90
 
77
91
  @arg x: Polynomial argument (C{scalar}, an L{Fsum} or L{Fsum2Tuple}).
78
92
  @arg cs: Polynomial coeffients (each C{scalar}, an L{Fsum} or L{Fsum2Tuple}),
@@ -454,7 +468,8 @@ def fdot_(*xys, **start_f2product_nonfinites):
454
468
 
455
469
  @return: Dot product (C{float}).
456
470
  '''
457
- return fdot(xys[0::2], *xys[1::2], **start_f2product_nonfinites)
471
+ D = Fdot_(*xys, **_xkwds(start_f2product_nonfinites, nonfinites=True))
472
+ return float(D)
458
473
 
459
474
 
460
475
  def fdot3(xs, ys, zs, **start_f2product_nonfinites):
@@ -555,7 +570,7 @@ def fidw(xs, ds, beta=2):
555
570
 
556
571
 
557
572
  try:
558
- from math import fma as _fma
573
+ from math import fma as _fma # in .resections
559
574
  except ImportError: # PYCHOK DSPACE!
560
575
 
561
576
  def _fma(x, y, z): # no need for accuracy
pygeodesy/fsums.py CHANGED
@@ -64,7 +64,7 @@ from math import fabs, isinf, isnan, \
64
64
  ceil as _ceil, floor as _floor # PYCHOK used! .ltp
65
65
 
66
66
  __all__ = _ALL_LAZY.fsums
67
- __version__ = '25.04.14'
67
+ __version__ = '25.04.26'
68
68
 
69
69
  from pygeodesy.interns import (
70
70
  _PLUS_ as _add_op_, # in .auxilats.auxAngle
@@ -1485,7 +1485,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1485
1485
  @deprecated_method
1486
1486
  def f2mul(self, *others, **raiser):
1487
1487
  '''DEPRECATED on 2024.09.13, use method L{f2mul_<Fsum.f2mul_>}.'''
1488
- return self._fset(self.f2mul_(others, **raiser))
1488
+ return self._fset(self._f2mul(self.f2mul, others, **raiser))
1489
1489
 
1490
1490
  def f2mul_(self, *others, **f2product_nonfinites): # in .fmath.f2mul
1491
1491
  '''Return C{B{self} * B{other} * B{other} ...} for all B{C{others}} using cascaded,