pygeodesy 25.5.28__py2.py3-none-any.whl → 25.8.25__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 +10 -10
- pygeodesy/cartesianBase.py +21 -20
- pygeodesy/ecef.py +324 -260
- pygeodesy/ellipsoidalBase.py +52 -58
- pygeodesy/ellipsoids.py +30 -14
- pygeodesy/elliptic.py +246 -225
- pygeodesy/fmath.py +6 -4
- pygeodesy/fsums.py +40 -18
- pygeodesy/geodesici.py +3 -3
- pygeodesy/geodesicx/__init__.py +1 -1
- pygeodesy/geodesicx/__main__.py +2 -2
- pygeodesy/geodesicx/gx.py +3 -2
- pygeodesy/geodesicx/gxarea.py +4 -5
- pygeodesy/geodesicx/gxbases.py +3 -2
- pygeodesy/internals.py +3 -3
- pygeodesy/ktm.py +3 -3
- pygeodesy/latlonBase.py +4 -4
- pygeodesy/lazily.py +3 -3
- pygeodesy/nvectorBase.py +4 -4
- pygeodesy/sphericalTrigonometry.py +5 -5
- pygeodesy/vector3dBase.py +5 -3
- {pygeodesy-25.5.28.dist-info → pygeodesy-25.8.25.dist-info}/METADATA +12 -12
- {pygeodesy-25.5.28.dist-info → pygeodesy-25.8.25.dist-info}/RECORD +25 -25
- {pygeodesy-25.5.28.dist-info → pygeodesy-25.8.25.dist-info}/WHEEL +0 -0
- {pygeodesy-25.5.28.dist-info → pygeodesy-25.8.25.dist-info}/top_level.txt +0 -0
pygeodesy/ecef.py
CHANGED
|
@@ -8,10 +8,12 @@ Geocentric conversions transcoded from I{Charles Karney}'s C++ class U{Geocentri
|
|
|
8
8
|
into pure Python class L{EcefKarney}, class L{EcefSudano} based on I{John Sudano}'s
|
|
9
9
|
U{paper<https://www.ResearchGate.net/publication/
|
|
10
10
|
3709199_An_exact_conversion_from_an_Earth-centered_coordinate_system_to_latitude_longitude_and_altitude>},
|
|
11
|
-
class L{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
class L{EcefUPC} using the I{Universitat Politècnica de Catalunya}'s U{method, page 186
|
|
12
|
+
<https://GSSC.ESA.int/navipedia/GNSS_Book/ESA_GNSS-Book_TM-23_Vol_I.pdf>}, class L{EcefVeness}
|
|
13
|
+
transcoded from I{Chris Veness}' JavaScript classes U{LatLonEllipsoidal, Cartesian
|
|
14
|
+
<https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}, class L{EcefYou}
|
|
15
|
+
implementing I{Rey-Jer You}'s U{transformations<https://www.ResearchGate.net/publication/240359424>}
|
|
16
|
+
and classes L{EcefFarrell22} and L{EcefFarrell22} from I{Jay A. Farrell}'s U{Table 2.1 and 2.2
|
|
15
17
|
<https://Books.Google.com/books?id=fW4foWASY6wC>}, page 29-30.
|
|
16
18
|
|
|
17
19
|
Following is a copy of I{Karney}'s U{Detailed Description
|
|
@@ -59,9 +61,9 @@ plane} as opposed to I{geocentric} (ECEF) ones.
|
|
|
59
61
|
|
|
60
62
|
from pygeodesy.basics import copysign0, _isin, isscalar, issubclassof, neg, map1, \
|
|
61
63
|
_xinstanceof, _xsubclassof, typename # _args_kwds_names
|
|
62
|
-
from pygeodesy.constants import EPS, EPS0, EPS02, EPS1,
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
from pygeodesy.constants import EPS, EPS0, EPS02, EPS1, INT0, PI, PI_2, _0_0, \
|
|
65
|
+
_0_5, _1_0, _1_0_1T, _2_0, _3_0, _4_0, _6_0, \
|
|
66
|
+
_90_0, _copysign_1_0, isnon0 # PYCHOK used!
|
|
65
67
|
from pygeodesy.datums import _ellipsoidal_datum, _WGS84, a_f2Tuple, _EWGS84
|
|
66
68
|
from pygeodesy.ecefLocals import _EcefLocal
|
|
67
69
|
# from pygeodesy.ellipsoids import a_f2Tuple, _EWGS84 # from .datums
|
|
@@ -77,23 +79,23 @@ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
|
77
79
|
from pygeodesy.named import _name__, _name1__, _NamedBase, _NamedTuple, _Pass, _xnamed
|
|
78
80
|
from pygeodesy.namedTuples import LatLon2Tuple, LatLon3Tuple, \
|
|
79
81
|
PhiLam2Tuple, Vector3Tuple, Vector4Tuple
|
|
80
|
-
from pygeodesy.props import deprecated_method,
|
|
81
|
-
|
|
82
|
+
from pygeodesy.props import deprecated_method, deprecated_property, Property_RO, \
|
|
83
|
+
property_RO, property_ROver
|
|
82
84
|
# from pygeodesy.streprs import Fmt, unstr # from .fsums
|
|
83
85
|
from pygeodesy.units import _isRadius, Degrees, Height, Int, Lam, Lat, Lon, Meter, \
|
|
84
|
-
Phi, Scalar, Scalar_
|
|
86
|
+
Phi, Radians_, Scalar, Scalar_
|
|
85
87
|
from pygeodesy.utily import atan1, atan1d, atan2, atan2d, degrees90, degrees180, \
|
|
86
|
-
sincos2, sincos2_,
|
|
88
|
+
sincos2, sincos2_, sincos2d_
|
|
87
89
|
# from pygeodesy.vector3d import Vector3d # _MODS
|
|
88
90
|
|
|
89
91
|
from math import cos, degrees, fabs, radians, sqrt
|
|
90
92
|
|
|
91
93
|
__all__ = _ALL_LAZY.ecef
|
|
92
|
-
__version__ = '25.
|
|
94
|
+
__version__ = '25.08.25'
|
|
93
95
|
|
|
94
96
|
_Ecef_ = 'Ecef'
|
|
95
97
|
_prolate_ = 'prolate'
|
|
96
|
-
_TRIPS = 33 # 8..9 sufficient, EcefSudano.reverse
|
|
98
|
+
_TRIPS = 33 # 8..9 sufficient, EcefEDA.-/EcefSudano.reverse
|
|
97
99
|
_xyz_y_z = _xyz_, _y_, _z_ # _args_kwds_names(_xyzn4)[:3]
|
|
98
100
|
|
|
99
101
|
|
|
@@ -104,8 +106,7 @@ class EcefError(_ValueError):
|
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
class _EcefBase(_NamedBase):
|
|
107
|
-
'''(INTERNAL) Base class for
|
|
108
|
-
L{EcefSudano}, L{EcefVeness} and L{EcefYou}.
|
|
109
|
+
'''(INTERNAL) Base class for C{Ecef*} conversion classes.
|
|
109
110
|
'''
|
|
110
111
|
_datum = _WGS84
|
|
111
112
|
_E = _EWGS84
|
|
@@ -163,8 +164,8 @@ class _EcefBase(_NamedBase):
|
|
|
163
164
|
|
|
164
165
|
@return: C{True} if equal, C{False} otherwise.
|
|
165
166
|
'''
|
|
166
|
-
return other is self or (isinstance(other, self
|
|
167
|
-
|
|
167
|
+
return other is self or (isinstance(other, type(self)) and
|
|
168
|
+
other.ellipsoid == self.ellipsoid)
|
|
168
169
|
|
|
169
170
|
@Property_RO
|
|
170
171
|
def datum(self):
|
|
@@ -207,13 +208,15 @@ class _EcefBase(_NamedBase):
|
|
|
207
208
|
|
|
208
209
|
E = self.ellipsoid
|
|
209
210
|
n = E.roc1_(sa, ca) if self._isYou else E.roc1_(sa)
|
|
211
|
+
c = (h + n) * ca
|
|
212
|
+
x = cb * c
|
|
213
|
+
y = sb * c
|
|
210
214
|
z = (h + n * E.e21) * sa
|
|
211
|
-
x = (h + n) * ca
|
|
212
215
|
|
|
213
216
|
m = self._Matrix(sa, ca, sb, cb) if M else None
|
|
214
|
-
return Ecef9Tuple(x
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
218
|
+
0, m, self.datum, # C=0, always
|
|
219
|
+
name=self._name__(name))
|
|
217
220
|
|
|
218
221
|
def forward(self, latlonh, lon=None, height=0, M=False, **name):
|
|
219
222
|
'''Convert from geodetic C{(lat, lon, height)} to geocentric C{(x, y, z)}.
|
|
@@ -296,14 +299,14 @@ class _EcefBase(_NamedBase):
|
|
|
296
299
|
'''
|
|
297
300
|
return self._xnamed(EcefMatrix(sa, ca, sb, cb))
|
|
298
301
|
|
|
299
|
-
def _polon(self, y, x,
|
|
302
|
+
def _polon(self, y, x, p, **lon00_name):
|
|
300
303
|
'''(INTERNAL) Handle I{"polar"} longitude.
|
|
301
304
|
'''
|
|
302
|
-
return atan2d(y, x) if
|
|
305
|
+
return atan2d(y, x) if p else _xkwds_get(lon00_name, lon00=self.lon00)
|
|
303
306
|
|
|
304
|
-
def reverse(self, xyz, y=None, z=None, M=False, **
|
|
307
|
+
def reverse(self, xyz, y=None, z=None, M=False, **tol_lon00_name): # PYCHOK no cover
|
|
305
308
|
'''I{Must be overloaded}.'''
|
|
306
|
-
self._notOverloaded(xyz, y=y, z=z, M=M, **
|
|
309
|
+
self._notOverloaded(xyz, y=y, z=z, M=M, **tol_lon00_name)
|
|
307
310
|
|
|
308
311
|
def toStr(self, prec=9, **unused): # PYCHOK signature
|
|
309
312
|
'''Return this C{Ecef*} as a string.
|
|
@@ -314,6 +317,33 @@ class _EcefBase(_NamedBase):
|
|
|
314
317
|
'''
|
|
315
318
|
return self.attrs(_a_, _f_, _datum_, _name_, prec=prec) # _ellipsoid_
|
|
316
319
|
|
|
320
|
+
def _xyzllhCpn9(self, xyz, y, z, **lon00_name):
|
|
321
|
+
'''(INTERNAL) Get C{x, y, z} and determine case C{C}, C{lat}, C{lon}, etc.
|
|
322
|
+
'''
|
|
323
|
+
x, y, z, n = _xyzn4(xyz, y, z, self._Geocentrics, **lon00_name)
|
|
324
|
+
|
|
325
|
+
s, c, p = _norm3(y, x) # distance to polar axis
|
|
326
|
+
if p < EPS0: # polar
|
|
327
|
+
lat = copysign0(_90_0, z)
|
|
328
|
+
h = fabs(z) - self.ellipsoid.b
|
|
329
|
+
C = 2
|
|
330
|
+
p = 0 # force lon00
|
|
331
|
+
elif fabs(z) < EPS0: # equatorial
|
|
332
|
+
lat = _0_0
|
|
333
|
+
h = p - self.ellipsoid.a
|
|
334
|
+
C = 3
|
|
335
|
+
else: # see _norm7 below, EcefKarney
|
|
336
|
+
h = hypot(z, p) # distance to earth center
|
|
337
|
+
if h > self.ellipsoid._heightMax:
|
|
338
|
+
lat = atan1d(z / h, p / h)
|
|
339
|
+
C = 4 # too high
|
|
340
|
+
# p *= _0_5
|
|
341
|
+
else: # pass h for EcefVeness
|
|
342
|
+
lat = None # -90..90
|
|
343
|
+
C = 1 # normal
|
|
344
|
+
lon = self._polon(s, c, p, **lon00_name)
|
|
345
|
+
return x, y, z, lat, lon, h, C, p, self._name__(n)
|
|
346
|
+
|
|
317
347
|
|
|
318
348
|
class EcefFarrell21(_EcefBase):
|
|
319
349
|
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF)
|
|
@@ -333,13 +363,12 @@ class EcefFarrell21(_EcefBase):
|
|
|
333
363
|
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
334
364
|
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
335
365
|
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
336
|
-
|
|
337
|
-
|
|
366
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
367
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
338
368
|
|
|
339
369
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
340
370
|
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
341
|
-
ones C{(x, y, z)}, case C{C
|
|
342
|
-
if available.
|
|
371
|
+
ones C{(x, y, z)}, case C{C}, C{M=None} always and C{datum}.
|
|
343
372
|
|
|
344
373
|
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
345
374
|
not C{scalar} for C{scalar} B{C{xyz}} or C{sqrt} domain or
|
|
@@ -347,47 +376,46 @@ class EcefFarrell21(_EcefBase):
|
|
|
347
376
|
|
|
348
377
|
@see: L{EcefFarrell22} and L{EcefVeness}.
|
|
349
378
|
'''
|
|
350
|
-
x, y, z, name =
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
380
|
+
if lat is None:
|
|
381
|
+
E = self.ellipsoid
|
|
382
|
+
a = E.a
|
|
383
|
+
a2 = E.a2
|
|
384
|
+
b2 = E.b2
|
|
385
|
+
e2 = E.e2
|
|
386
|
+
e2_ = E.e2abs * E.a2_b2 # (E.e * E.a_b)**2 = 0.0820944... WGS84
|
|
387
|
+
e4 = E.e4
|
|
388
|
+
|
|
389
|
+
try: # names as page 29
|
|
390
|
+
z2 = z**2
|
|
391
|
+
ez = z2 * (_1_0 - e2) # E.e2s2(z)
|
|
392
|
+
|
|
393
|
+
p2 = p**2
|
|
394
|
+
G = p2 + ez - e2 * (a2 - b2) # p2 + ez - e4 * a2
|
|
395
|
+
F = b2 * z2 * 54
|
|
396
|
+
c = e4 * p2 * F / G**3
|
|
397
|
+
s = sqrt(c * (c + _2_0))
|
|
398
|
+
c = cbrt(s + c + _1_0)
|
|
399
|
+
G *= fsumf_(c, _1_0, _1_0 / c) # k
|
|
400
|
+
P = F / (G**2 * _3_0)
|
|
401
|
+
Q = sqrt(_2_0 * e4 * P + _1_0)
|
|
402
|
+
Q1 = Q + _1_0
|
|
403
|
+
s = fsumf_(a2 * (Q1 / Q) * _0_5,
|
|
404
|
+
-P * ez / (Q * Q1),
|
|
405
|
+
-P * p2 * _0_5)
|
|
406
|
+
r = P * p * e2 / Q1 - sqrt(s)
|
|
407
|
+
r = p + r * e2
|
|
408
|
+
v = b2 / (sqrt(r**2 + ez) * a) # z0 / z
|
|
409
|
+
|
|
410
|
+
h = hypot(r, z) * (_1_0 - v)
|
|
411
|
+
lat = atan1d((e2_ * v + _1_0) * z, p)
|
|
412
|
+
# note, phi and lam are swapped on page 29
|
|
413
|
+
|
|
414
|
+
except (ValueError, ZeroDivisionError) as X:
|
|
415
|
+
raise EcefError(x=x, y=y, z=z, cause=X)
|
|
387
416
|
|
|
388
417
|
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
389
|
-
|
|
390
|
-
name=self._name__(name))
|
|
418
|
+
C, None, self.datum, name=name)
|
|
391
419
|
|
|
392
420
|
|
|
393
421
|
class EcefFarrell22(_EcefBase):
|
|
@@ -407,13 +435,12 @@ class EcefFarrell22(_EcefBase):
|
|
|
407
435
|
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
408
436
|
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
409
437
|
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
410
|
-
|
|
411
|
-
|
|
438
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
439
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
412
440
|
|
|
413
441
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
414
442
|
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
415
|
-
ones C{(x, y, z)}, case C{C
|
|
416
|
-
if available.
|
|
443
|
+
ones C{(x, y, z)}, case C{C}, C{M=None} always and C{datum}.
|
|
417
444
|
|
|
418
445
|
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
419
446
|
not C{scalar} for C{scalar} B{C{xyz}} or C{sqrt} domain or
|
|
@@ -421,33 +448,19 @@ class EcefFarrell22(_EcefBase):
|
|
|
421
448
|
|
|
422
449
|
@see: L{EcefFarrell21} and L{EcefVeness}.
|
|
423
450
|
'''
|
|
424
|
-
x, y, z, name =
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
s, c = sincos2(atan2(z * a, p * b)) # == _norm3
|
|
435
|
-
lat = atan1d(z + s**3 * b * E.e22,
|
|
436
|
-
p - c**3 * a * E.e2)
|
|
437
|
-
|
|
438
|
-
s, c = sincos2d(lat)
|
|
439
|
-
if c: # E.roc1_(s) = E.a / sqrt(1 - E.e2 * s**2)
|
|
440
|
-
h = p / c - (E.roc1_(s) if s else a)
|
|
441
|
-
else: # polar
|
|
442
|
-
h = fabs(z) - b
|
|
451
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
452
|
+
if lat is None:
|
|
453
|
+
E = self.ellipsoid
|
|
454
|
+
a, b = E.a, E.b
|
|
455
|
+
s, c, _ = _norm3(z * a, p * b)
|
|
456
|
+
s, c, _ = _norm3(z + s**3 * b * E.e22,
|
|
457
|
+
p - c**3 * a * E.e2)
|
|
458
|
+
lat = atan1d(s, c)
|
|
459
|
+
h = (p / fabs(c) - (E.roc1_(s) if s else a)) if c else (fabs(z) - b)
|
|
443
460
|
# note, phi and lam are swapped on page 30
|
|
444
461
|
|
|
445
|
-
except (ValueError, ZeroDivisionError) as e:
|
|
446
|
-
raise EcefError(x=x, y=y, z=z, cause=e)
|
|
447
|
-
|
|
448
462
|
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
449
|
-
|
|
450
|
-
name=self._name__(name))
|
|
463
|
+
C, None, self.datum, name=name)
|
|
451
464
|
|
|
452
465
|
|
|
453
466
|
class EcefKarney(_EcefBase):
|
|
@@ -463,12 +476,6 @@ class EcefKarney(_EcefBase):
|
|
|
463
476
|
the rotation matrix.
|
|
464
477
|
'''
|
|
465
478
|
|
|
466
|
-
@Property_RO
|
|
467
|
-
def hmax(self):
|
|
468
|
-
'''Get the distance or height limit (C{meter}, conventionally).
|
|
469
|
-
'''
|
|
470
|
-
return self.equatoradius / EPS_2 # self.equatoradius * _2_EPS, 12M lighyears
|
|
471
|
-
|
|
472
479
|
def reverse(self, xyz, y=None, z=None, M=False, **lon00_name):
|
|
473
480
|
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}.
|
|
474
481
|
|
|
@@ -478,8 +485,8 @@ class EcefKarney(_EcefBase):
|
|
|
478
485
|
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
479
486
|
@kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}).
|
|
480
487
|
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
481
|
-
|
|
482
|
-
|
|
488
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
489
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
483
490
|
|
|
484
491
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
485
492
|
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
@@ -499,27 +506,14 @@ class EcefKarney(_EcefBase):
|
|
|
499
506
|
is not below M{−E.a * (1 − E.e2) / sqrt(1 − E.e2 * sin(lat)**2)}. Like
|
|
500
507
|
C{forward} above, M{v1 = Transpose(M) ⋅ v0}.
|
|
501
508
|
'''
|
|
502
|
-
def _norm3(y, x):
|
|
503
|
-
h = hypot(y, x) # EPS0, EPS_2
|
|
504
|
-
return (y / h, x / h, h) if h > 0 else (_0_0, _1_0, h)
|
|
505
|
-
|
|
506
509
|
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **lon00_name)
|
|
507
510
|
|
|
508
511
|
E = self.ellipsoid
|
|
509
512
|
f = E.f
|
|
510
513
|
|
|
511
|
-
sb, cb, R =
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
# We are really far away (> 12M light years). Treat the earth
|
|
515
|
-
# as a point and h above as an acceptable approximation to the
|
|
516
|
-
# height. This avoids overflow, e.g., in the computation of d
|
|
517
|
-
# below. It's possible that h has overflowed to INF, that's OK.
|
|
518
|
-
# Treat finite x, y, but R overflows to +INF by scaling by 2.
|
|
519
|
-
sb, cb, R = _norm3(y * _0_5, x * _0_5)
|
|
520
|
-
sa, ca, _ = _norm3(z * _0_5, R)
|
|
521
|
-
C = 1
|
|
522
|
-
|
|
514
|
+
sa, ca, sb, cb, R, h, C = _norm7(y, x, z, E)
|
|
515
|
+
if C: # PYCHOK no cover
|
|
516
|
+
pass # too high, far
|
|
523
517
|
elif E.e4: # E.isEllipsoidal
|
|
524
518
|
# Treat prolate spheroids by swapping R and Z here and by
|
|
525
519
|
# switching the arguments to phi = atan2(...) at the end.
|
|
@@ -569,7 +563,7 @@ class EcefKarney(_EcefBase):
|
|
|
569
563
|
k2 += E.e2
|
|
570
564
|
sa, ca, h = _norm3(z / k1, R / k2)
|
|
571
565
|
h *= k1 - E.e21
|
|
572
|
-
C =
|
|
566
|
+
C = 1
|
|
573
567
|
|
|
574
568
|
else: # e = E.e4 * q == 0 and r <= 0
|
|
575
569
|
# This leads to k = 0 (oblate, equatorial plane) and k + E.e^2 = 0
|
|
@@ -595,7 +589,7 @@ class EcefKarney(_EcefBase):
|
|
|
595
589
|
# difficult. Origin maps to North pole, same as with ellipsoid.
|
|
596
590
|
sa, ca, _ = _norm3((z if h else _1_0), R)
|
|
597
591
|
h -= E.a
|
|
598
|
-
C =
|
|
592
|
+
C = 2
|
|
599
593
|
|
|
600
594
|
# lon00 <https://GitHub.com/mrJean1/PyGeodesy/issues/77>
|
|
601
595
|
lon = self._polon(sb, cb, R, **lon00_name)
|
|
@@ -608,9 +602,9 @@ class EcefSudano(_EcefBase):
|
|
|
608
602
|
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF) coordinates
|
|
609
603
|
based on I{John J. Sudano}'s U{paper<https://www.ResearchGate.net/publication/3709199>}.
|
|
610
604
|
'''
|
|
611
|
-
_tol =
|
|
605
|
+
_tol = EPS0 # DEPRECATED
|
|
612
606
|
|
|
613
|
-
def reverse(self, xyz, y=None, z=None, M=None, **lon00_name): # PYCHOK unused M
|
|
607
|
+
def reverse(self, xyz, y=None, z=None, M=None, tol=EPS, **lon00_name): # PYCHOK unused M
|
|
614
608
|
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using
|
|
615
609
|
I{Sudano}'s U{iterative method<https://www.ResearchGate.net/publication/3709199>}.
|
|
616
610
|
|
|
@@ -619,78 +613,129 @@ class EcefSudano(_EcefBase):
|
|
|
619
613
|
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
620
614
|
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
621
615
|
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
616
|
+
@kwarg tol: Convergence tolerance for C{sin}(latitude) (C{scalar}).
|
|
622
617
|
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
623
|
-
|
|
624
|
-
|
|
618
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
619
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
625
620
|
|
|
626
621
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic
|
|
627
622
|
coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)},
|
|
628
|
-
|
|
623
|
+
case C{C}, C{M=None} always and C{datum} if available.
|
|
629
624
|
|
|
630
625
|
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
631
626
|
not C{scalar} for C{scalar} B{C{xyz}} or no convergence.
|
|
632
|
-
'''
|
|
633
|
-
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **lon00_name)
|
|
634
|
-
|
|
635
|
-
E = self.ellipsoid
|
|
636
|
-
e = E.e2 * E.a
|
|
637
|
-
R = hypot(x, y) # Rh
|
|
638
|
-
d = e - R
|
|
639
|
-
|
|
640
|
-
lat = atan1d(z, R * E.e21)
|
|
641
|
-
sa, ca = sincos2d(fabs(lat))
|
|
642
|
-
# Sudano's Eq (A-6) and (A-7) refactored/reduced,
|
|
643
|
-
# replacing Rn from Eq (A-4) with n = E.a / ca:
|
|
644
|
-
# N = ca**2 * ((z + E.e2 * n * sa) * ca - R * sa)
|
|
645
|
-
# = ca**2 * (z * ca + E.e2 * E.a * sa - R * sa)
|
|
646
|
-
# = ca**2 * (z * ca + (E.e2 * E.a - R) * sa)
|
|
647
|
-
# D = ca**3 * (E.e2 * n / E.e2s2(sa)) - R
|
|
648
|
-
# = ca**2 * (E.e2 * E.a / E.e2s2(sa) - R / ca**2)
|
|
649
|
-
# N / D = (z * ca + (E.e2 * E.a - R) * sa) /
|
|
650
|
-
# (E.e2 * E.a / E.e2s2(sa) - R / ca**2)
|
|
651
|
-
tol = self.tolerance
|
|
652
|
-
_S2 = Fsum(sa).fsum2f_
|
|
653
|
-
for i in range(1, _TRIPS):
|
|
654
|
-
ca2 = _1_0 - sa**2
|
|
655
|
-
if ca2 < EPS_2: # PYCHOK no cover
|
|
656
|
-
ca = _0_0
|
|
657
|
-
break
|
|
658
|
-
ca = sqrt(ca2)
|
|
659
|
-
r = e / E.e2s2(sa) - R / ca2
|
|
660
|
-
if fabs(r) < EPS_2:
|
|
661
|
-
break
|
|
662
|
-
lat = None
|
|
663
|
-
sa, t = _S2(-z * ca / r, -d * sa / r)
|
|
664
|
-
if fabs(t) < tol:
|
|
665
|
-
break
|
|
666
|
-
else:
|
|
667
|
-
t = unstr(self.reverse, x=x, y=y, z=z)
|
|
668
|
-
raise EcefError(t, txt=Fmt.no_convergence(r, tol))
|
|
669
627
|
|
|
628
|
+
@see: Class L{EcefUPC}.
|
|
629
|
+
'''
|
|
630
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
670
631
|
if lat is None:
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
632
|
+
E = self.ellipsoid
|
|
633
|
+
e = E.e2 * E.a
|
|
634
|
+
d = e - p
|
|
635
|
+
|
|
636
|
+
sa, ca, _ = _norm3(fabs(z), p * E.e21)
|
|
637
|
+
_S2 = Fsum(sa).fsum2f_
|
|
638
|
+
tol = Scalar_(tol=tol, low=self.tolerance, Error=EcefError)
|
|
639
|
+
# Sudano's Eq (A-6) and (A-7) refactored/reduced,
|
|
640
|
+
# replacing Rn from Eq (A-4) with n = E.a / ca:
|
|
641
|
+
# N = ca**2 * ((z + E.e2 * n * sa) * ca - p * sa)
|
|
642
|
+
# = ca**2 * (z * ca + E.e2 * E.a * sa - p * sa)
|
|
643
|
+
# = ca**2 * (z * ca + (E.e2 * E.a - p) * sa)
|
|
644
|
+
# D = ca**3 * (E.e2 * n / E.e2s2(sa)) - p
|
|
645
|
+
# = ca**2 * (E.e2 * E.a / E.e2s2(sa) - p / ca**2)
|
|
646
|
+
# N / D = (z * ca + (E.e2 * E.a - p) * sa) /
|
|
647
|
+
# (E.e2 * E.a / E.e2s2(sa) - p / ca**2)
|
|
648
|
+
for i in range(1, _TRIPS): # 6+ max
|
|
649
|
+
ca2 = _1_0 - sa**2
|
|
650
|
+
if ca2 < EPS02:
|
|
651
|
+
break
|
|
652
|
+
D = p / ca2 - e / E.e2s2(sa)
|
|
653
|
+
if fabs(D) < EPS0:
|
|
654
|
+
break
|
|
655
|
+
ca = sqrt(ca2)
|
|
656
|
+
sa, D = _S2(z * ca / D, d * sa / D)
|
|
657
|
+
if fabs(D) < tol:
|
|
658
|
+
break
|
|
659
|
+
else:
|
|
660
|
+
t = unstr(self.reverse, x=x, y=y, z=z)
|
|
661
|
+
raise EcefError(t, txt=Fmt.no_convergence(D, tol))
|
|
662
|
+
|
|
663
|
+
sa = copysign0(sa, z)
|
|
664
|
+
lat = atan1d(sa, ca)
|
|
665
|
+
# h = (fabs(z) + p - E.a * cos(a + E.e21) * sa / ca) / (ca + sa)
|
|
666
|
+
# Sudano's Eq (7) doesn't produce the correct height, ...
|
|
667
|
+
h = E._heightB(sa, ca, z, p) # ... use Veness' (Bowring eqn 7)
|
|
668
|
+
else:
|
|
669
|
+
i = 0
|
|
677
670
|
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
678
|
-
|
|
679
|
-
iteration=i, name=
|
|
671
|
+
C, None, self.datum, # M=None
|
|
672
|
+
iteration=i, name=name)
|
|
680
673
|
|
|
681
|
-
@
|
|
674
|
+
@deprecated_property
|
|
682
675
|
def tolerance(self):
|
|
683
|
-
'''
|
|
684
|
-
'''
|
|
676
|
+
'''DEPRECATED on 2025.08.22, use keyword argument C{tol}.'''
|
|
685
677
|
return self._tol
|
|
686
678
|
|
|
687
679
|
@tolerance.setter # PYCHOK setter!
|
|
688
680
|
def tolerance(self, tol):
|
|
689
|
-
|
|
681
|
+
self._tol = Scalar_(tolerance=tol, low=EPS0, Error=EcefError)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
class EcefUPC(_EcefBase):
|
|
685
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF) coordinates based on
|
|
686
|
+
I{UPC}'s U{method<https://GSSC.ESA.int/navipedia/index.php/Ellipsoidal_and_Cartesian_Coordinates_Conversion>}.
|
|
687
|
+
'''
|
|
690
688
|
|
|
691
|
-
|
|
689
|
+
def reverse(self, xyz, y=None, z=None, M=None, tol=EPS, **lon00_name): # PYCHOK unused M
|
|
690
|
+
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using I{UPC}'s
|
|
691
|
+
U{iterative method<https://GSSC.ESA.int/navipedia/GNSS_Book/ESA_GNSS-Book_TM-23_Vol_I.pdf>}, page 186.
|
|
692
|
+
|
|
693
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
694
|
+
coordinate (C{meter}).
|
|
695
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
696
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
697
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
698
|
+
@kwarg tol: Convergence tolerance for the latitude (C{radians}).
|
|
699
|
+
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
700
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
701
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
702
|
+
|
|
703
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic
|
|
704
|
+
coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)},
|
|
705
|
+
case C{C}, C{M=None} always and C{datum} if available.
|
|
706
|
+
|
|
707
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
708
|
+
not C{scalar} for C{scalar} B{C{xyz}} or no convergence.
|
|
709
|
+
|
|
710
|
+
@see: Class L{EcefSudano}.
|
|
692
711
|
'''
|
|
693
|
-
|
|
712
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
713
|
+
if lat is None:
|
|
714
|
+
E = self.ellipsoid
|
|
715
|
+
a = E.a
|
|
716
|
+
e2 = E.e2 # signed
|
|
717
|
+
|
|
718
|
+
z_p = z / p
|
|
719
|
+
ph0 = atan1(z_p, E.e21)
|
|
720
|
+
tol = Radians_(tol=tol, low=EPS0, Error=EcefError)
|
|
721
|
+
for i in range(1, _TRIPS): # 5..6 max
|
|
722
|
+
s, c = sincos2(ph0)
|
|
723
|
+
N = a / sqrt(_1_0 - s**2 * e2)
|
|
724
|
+
h = p / c - N
|
|
725
|
+
phi = atan1(z_p, _1_0 - e2 * (N / (N + h)))
|
|
726
|
+
r = fabs(phi - ph0)
|
|
727
|
+
if r < tol:
|
|
728
|
+
lat = copysign0(degrees(phi), z)
|
|
729
|
+
break
|
|
730
|
+
ph0 = phi
|
|
731
|
+
else:
|
|
732
|
+
t = unstr(self.reverse, x=x, y=y, z=z)
|
|
733
|
+
raise EcefError(t, txt=Fmt.no_convergence(r, tol))
|
|
734
|
+
else:
|
|
735
|
+
i = 0
|
|
736
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
737
|
+
C, None, self.datum, # M=None
|
|
738
|
+
iteration=i, name=name)
|
|
694
739
|
|
|
695
740
|
|
|
696
741
|
class EcefVeness(_EcefBase):
|
|
@@ -718,8 +763,8 @@ class EcefVeness(_EcefBase):
|
|
|
718
763
|
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
719
764
|
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
720
765
|
@kwarg lon00_name: Optional C{B{name}=NN} (C{str}) and optional keyword argument
|
|
721
|
-
|
|
722
|
-
|
|
766
|
+
C{B{lon00}=INT0} (C{degrees}), an arbitrary I{"polar"} longitude
|
|
767
|
+
returned if C{B{x}=0} and C{B{y}=0}, see property C{lon00}.
|
|
723
768
|
|
|
724
769
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
725
770
|
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
@@ -737,41 +782,23 @@ class EcefVeness(_EcefBase):
|
|
|
737
782
|
system to latitude longitude and altitude}<https://www.ResearchGate.net/
|
|
738
783
|
publication/3709199>}.
|
|
739
784
|
'''
|
|
740
|
-
x, y, z, name =
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
p = hypot(x, y) # distance from minor axis
|
|
746
|
-
r = hypot(p, z) # polar radius
|
|
747
|
-
if min(p, r) > EPS0:
|
|
748
|
-
b = E.b * E.e22
|
|
785
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
786
|
+
if lat is None:
|
|
787
|
+
E = self.ellipsoid
|
|
788
|
+
a = E.a
|
|
789
|
+
B = E.b * E.e22
|
|
749
790
|
# parametric latitude (Bowring eqn 17, replaced)
|
|
750
|
-
t = (E.b * z) / (a * p) * (_1_0 +
|
|
791
|
+
t = (E.b * z) / (a * p) * (_1_0 + B / h) # h = hypot(z, p)
|
|
751
792
|
c = _1_0 / hypot1(t)
|
|
752
793
|
s = c * t
|
|
753
794
|
# geodetic latitude (Bowring eqn 18)
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
# height
|
|
758
|
-
sa, ca = sincos2d(lat)
|
|
759
|
-
# r = a / E.e2s(sa) # length of normal terminated by minor axis
|
|
760
|
-
# h = p * ca + z * sa - (a * a / r)
|
|
761
|
-
h = fsumf_(p * ca, z * sa, -a * E.e2s(sa))
|
|
762
|
-
C = 1
|
|
795
|
+
s, c, _ = _norm3(z + s**3 * B,
|
|
796
|
+
p - c**3 * a * E.e2)
|
|
797
|
+
lat = atan1d(s, c)
|
|
798
|
+
h = E._heightB(s, c, z, p) # height (Bowring eqn 7)
|
|
763
799
|
|
|
764
|
-
# see <https://GIS.StackExchange.com/questions/28446>
|
|
765
|
-
elif p > EPS: # lat arbitrarily zero, equatorial lon
|
|
766
|
-
C, lat, h = 2, _0_0, (p - a)
|
|
767
|
-
|
|
768
|
-
else: # polar lat, lon arbitrarily lon00
|
|
769
|
-
C, lat, h = 3, (_N_90_0 if z < 0 else _90_0), (fabs(z) - E.b)
|
|
770
|
-
|
|
771
|
-
lon = self._polon(y, x, p, **lon00_name)
|
|
772
800
|
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
773
|
-
C, None, self.datum,
|
|
774
|
-
name=self._name__(name))
|
|
801
|
+
C, None, self.datum, name=name)
|
|
775
802
|
|
|
776
803
|
|
|
777
804
|
class EcefYou(_EcefBase):
|
|
@@ -788,14 +815,12 @@ class EcefYou(_EcefBase):
|
|
|
788
815
|
|
|
789
816
|
def __init__(self, a_ellipsoid=_EWGS84, f=None, **lon00_name): # PYCHOK signature
|
|
790
817
|
_EcefBase.__init__(self, a_ellipsoid, f=f, **lon00_name) # inherited documentation
|
|
791
|
-
self._ee2 = EcefYou._ee2(self.ellipsoid)
|
|
792
818
|
|
|
793
|
-
|
|
794
|
-
def _ee2(E):
|
|
819
|
+
E = self.ellipsoid
|
|
795
820
|
e2 = E.a2 - E.b2
|
|
796
821
|
if e2 < 0 or E.f < 0:
|
|
797
822
|
raise EcefError(ellipsoid=E, txt=_prolate_)
|
|
798
|
-
|
|
823
|
+
self._ee2 = sqrt0(e2), e2
|
|
799
824
|
|
|
800
825
|
def reverse(self, xyz, y=None, z=None, M=None, **lon00_name): # PYCHOK unused M
|
|
801
826
|
'''Convert geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}
|
|
@@ -812,47 +837,44 @@ class EcefYou(_EcefBase):
|
|
|
812
837
|
|
|
813
838
|
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
814
839
|
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
815
|
-
ones C{(x, y, z)}, case C{C
|
|
816
|
-
available.
|
|
840
|
+
ones C{(x, y, z)}, case C{C}, C{M=None} always and C{datum} if available.
|
|
817
841
|
|
|
818
842
|
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or
|
|
819
843
|
B{C{z}} not C{scalar} for C{scalar} B{C{xyz}} or the
|
|
820
844
|
ellipsoid is I{prolate}.
|
|
821
845
|
'''
|
|
822
|
-
x, y, z, name =
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
846
|
+
x, y, z, lat, lon, h, C, p, name = self._xyzllhCpn9(xyz, y, z, **lon00_name)
|
|
847
|
+
if lat is None:
|
|
848
|
+
E = self.ellipsoid
|
|
849
|
+
a, b = E.a, E.b
|
|
850
|
+
e, e2 = self._ee2
|
|
851
|
+
|
|
852
|
+
u = hypot2_(x, y, z) - e2
|
|
853
|
+
u += hypot(u, e * z * _2_0)
|
|
854
|
+
u *= _0_5
|
|
855
|
+
if u > EPS02:
|
|
856
|
+
u = sqrt(u)
|
|
857
|
+
q = hypot(u, e)
|
|
858
|
+
B = atan1(q * z, u * p) # beta0 = atan(q / u * z / p)
|
|
859
|
+
sB, cB = sincos2(B)
|
|
860
|
+
if cB and sB:
|
|
861
|
+
q *= a
|
|
862
|
+
d = (q / cB - e2 * cB) / sB
|
|
863
|
+
if isnon0(d):
|
|
864
|
+
B += fsumf_(u * b, -q, e2) / d
|
|
865
|
+
sB, cB = sincos2(B)
|
|
866
|
+
elif u < (-EPS02):
|
|
867
|
+
raise EcefError(x=x, y=y, z=z, u=u, txt=_singular_)
|
|
868
|
+
else: # near polar # PYCHOK no cover
|
|
869
|
+
sB, cB, C = _copysign_1_0(z), _0_0, 2
|
|
870
|
+
|
|
871
|
+
lat = atan1d( a * sB, b * cB) # atan(E.a_b * tan(B))
|
|
872
|
+
h = hypot(p - a * cB, z - b * sB)
|
|
873
|
+
if hypot2_(x, y, z * E.a_b) < E.a2: # or lat < 0 or z < 0
|
|
874
|
+
h = neg(h) # inside ellipsoid
|
|
849
875
|
|
|
850
|
-
h = hypot(z - E.b * sB, q - E.a * cB)
|
|
851
|
-
if hypot2_(x, y, z * E.a_b) < E.a2:
|
|
852
|
-
h = neg(h) # inside ellipsoid
|
|
853
876
|
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
854
|
-
|
|
855
|
-
name=self._name__(name))
|
|
877
|
+
C, None, self.datum, name=name)
|
|
856
878
|
|
|
857
879
|
|
|
858
880
|
class EcefMatrix(_NamedTuple):
|
|
@@ -872,7 +894,7 @@ class EcefMatrix(_NamedTuple):
|
|
|
872
894
|
'''
|
|
873
895
|
_NamedTuple._validate(self, underOK=True)
|
|
874
896
|
|
|
875
|
-
def __new__(cls, sa, ca, sb, cb, *_more):
|
|
897
|
+
def __new__(cls, sa, ca, sb, cb, *_more, **name):
|
|
876
898
|
'''New L{EcefMatrix} matrix.
|
|
877
899
|
|
|
878
900
|
@arg sa: C{sin(phi)} (C{float}).
|
|
@@ -908,7 +930,7 @@ class EcefMatrix(_NamedTuple):
|
|
|
908
930
|
cb, -sb * sa, sb * ca,
|
|
909
931
|
_0_0, ca, sa)
|
|
910
932
|
|
|
911
|
-
return _NamedTuple.__new__(cls, *t)
|
|
933
|
+
return _NamedTuple.__new__(cls, *t, **name)
|
|
912
934
|
|
|
913
935
|
def column(self, column):
|
|
914
936
|
'''Get this matrix' B{C{column}} 0, 1 or 2 as C{3-tuple}.
|
|
@@ -1017,10 +1039,11 @@ class EcefMatrix(_NamedTuple):
|
|
|
1017
1039
|
|
|
1018
1040
|
class Ecef9Tuple(_NamedTuple, _EcefLocal):
|
|
1019
1041
|
'''9-Tuple C{(x, y, z, lat, lon, height, C, M, datum)} with I{geocentric} C{x},
|
|
1020
|
-
C{y} and C{z} plus I{geodetic} C{lat}, C{lon} and C{height}, case C{C}
|
|
1021
|
-
|
|
1022
|
-
and C{
|
|
1023
|
-
C{
|
|
1042
|
+
C{y} and C{z} plus I{geodetic} C{lat}, C{lon} and C{height}, case C{C} and
|
|
1043
|
+
optionally, rotation matrix C{M} (L{EcefMatrix}) and C{datum}, with C{lat}
|
|
1044
|
+
and C{lon} in C{degrees} and C{x}, C{y}, C{z} and C{height} in C{meter},
|
|
1045
|
+
conventionally. Case C{C=1} means normal, C{C=2} near polar and C{C=3}
|
|
1046
|
+
equatorial latitude and C{C=4} height exceeds C{heightMax}.
|
|
1024
1047
|
'''
|
|
1025
1048
|
_Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _C_, _M_, _datum_)
|
|
1026
1049
|
_Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, Int, _Pass, _Pass)
|
|
@@ -1040,6 +1063,12 @@ class Ecef9Tuple(_NamedTuple, _EcefLocal):
|
|
|
1040
1063
|
def _ecef9(self): # in ._EcefLocal._Ltp_ecef2local
|
|
1041
1064
|
return self
|
|
1042
1065
|
|
|
1066
|
+
@property_RO
|
|
1067
|
+
def ellipsoid(self):
|
|
1068
|
+
'''Get the ellipsoid (L{Ellipsoid}).
|
|
1069
|
+
'''
|
|
1070
|
+
return (self.datum or _WGS84).ellipsoid
|
|
1071
|
+
|
|
1043
1072
|
@Property_RO
|
|
1044
1073
|
def lam(self):
|
|
1045
1074
|
'''Get the longitude in C{radians} (C{float}).
|
|
@@ -1056,10 +1085,11 @@ class Ecef9Tuple(_NamedTuple, _EcefLocal):
|
|
|
1056
1085
|
and U{Featherstone, et.al.<https://Search.ProQuest.com/docview/872827242>}, page 7.
|
|
1057
1086
|
'''
|
|
1058
1087
|
x, y = self.x, self.y
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1088
|
+
a = fabs(y)
|
|
1089
|
+
if a > EPS0:
|
|
1090
|
+
r = PI_2 - atan2(x, hypot(x, a) + a) * _2_0
|
|
1091
|
+
if y < 0:
|
|
1092
|
+
r = -r
|
|
1063
1093
|
else: # y == 0
|
|
1064
1094
|
r = PI if x < 0 else _0_0
|
|
1065
1095
|
return Lam(Vermeille=r)
|
|
@@ -1093,12 +1123,19 @@ class Ecef9Tuple(_NamedTuple, _EcefLocal):
|
|
|
1093
1123
|
@Property_RO
|
|
1094
1124
|
def lonVermeille(self):
|
|
1095
1125
|
'''Get the longitude in C{degrees [-225..+225]} after U{Vermeille
|
|
1096
|
-
<https://Search.ProQuest.com/docview/639493848>} 2004,
|
|
1126
|
+
<https://Search.ProQuest.com/docview/639493848>} 2004, page 95.
|
|
1097
1127
|
|
|
1098
1128
|
@see: Property C{lamVermeille}.
|
|
1099
1129
|
'''
|
|
1100
1130
|
return Lon(Vermeille=degrees(self.lamVermeille))
|
|
1101
1131
|
|
|
1132
|
+
@Property_RO
|
|
1133
|
+
def Mx(self):
|
|
1134
|
+
'''Compute rotation matrix (L{EcefMatrix}), seperate from C{M}.
|
|
1135
|
+
'''
|
|
1136
|
+
sa, ca, sb, cb, _, _, _ = _norm7(self.y, self.x, self.z, self.ellipsoid)
|
|
1137
|
+
return EcefMatrix(sa, ca, sb, cb, name=self.name)
|
|
1138
|
+
|
|
1102
1139
|
@Property_RO
|
|
1103
1140
|
def phi(self):
|
|
1104
1141
|
'''Get the latitude in C{radians} (C{float}).
|
|
@@ -1271,6 +1308,32 @@ def _llhn4(latlonh, lon, height, suffix=NN, Error=EcefError, **name): # in .ltp
|
|
|
1271
1308
|
raise Error(cause=x, **d)
|
|
1272
1309
|
|
|
1273
1310
|
|
|
1311
|
+
def _norm3(y, x):
|
|
1312
|
+
'''(INTERNAL) Return C{y, x, h} normalized.
|
|
1313
|
+
'''
|
|
1314
|
+
h = hypot(y, x) # EPS0, EPS_2
|
|
1315
|
+
return (y / h, x / h, h) if h else (_0_0, _1_0, h)
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def _norm7(y, x, z=0, E=_EWGS84):
|
|
1319
|
+
'''(INTERNAL) Return C{phi, lam, p, h, C}.
|
|
1320
|
+
'''
|
|
1321
|
+
sb, cb, p = _norm3(y, x) # lam, distance to polar axis
|
|
1322
|
+
sa, ca, h = _norm3(z, p) # phi, distance to earth center
|
|
1323
|
+
if h > E._heightMax:
|
|
1324
|
+
# We are really far away (> 12M light years). Treat the earth
|
|
1325
|
+
# as a point and h above as an acceptable approximation to the
|
|
1326
|
+
# height. This avoids overflow, e.g., in the computation of d
|
|
1327
|
+
# below. It's possible that h has overflowed to INF, that's OK.
|
|
1328
|
+
# Treat finite x, y, but R overflows to +INF by scaling by 2.
|
|
1329
|
+
sb, cb, p = _norm3(y * _0_5, x * _0_5)
|
|
1330
|
+
sa, ca, _ = _norm3(z * _0_5, p)
|
|
1331
|
+
C = 4
|
|
1332
|
+
else:
|
|
1333
|
+
C = 0
|
|
1334
|
+
return sa, ca, sb, cb, p, h, C
|
|
1335
|
+
|
|
1336
|
+
|
|
1274
1337
|
def _xEcef(Ecef): # PYCHOK .latlonBase
|
|
1275
1338
|
'''(INTERNAL) Validate B{C{Ecef}} I{class}.
|
|
1276
1339
|
'''
|
|
@@ -1299,8 +1362,9 @@ def _xyzn4(xyz, y, z, Types, Error=EcefError, lon00=0, # PYCHOK unused
|
|
|
1299
1362
|
# assert _xyz_y_z == _args_kwds_names(_xyzn4)[:3]
|
|
1300
1363
|
|
|
1301
1364
|
|
|
1302
|
-
_Ecefs = (
|
|
1303
|
-
|
|
1365
|
+
_Ecefs = tuple(_ for _ in locals().values()
|
|
1366
|
+
if issubclassof(_, _EcefBase) and
|
|
1367
|
+
_ is not _EcefBase)
|
|
1304
1368
|
__all__ += _ALL_DOCS(_EcefBase)
|
|
1305
1369
|
|
|
1306
1370
|
# **) MIT License
|