pygeodesy 24.3.24__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-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/ecef.py
ADDED
|
@@ -0,0 +1,1348 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''I{Geocentric} Earth-Centered, Earth-Fixed (ECEF) coordinates.
|
|
5
|
+
|
|
6
|
+
Geocentric conversions transcoded from I{Charles Karney}'s C++ class U{Geocentric
|
|
7
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geocentric.html>}
|
|
8
|
+
into pure Python class L{EcefKarney}, class L{EcefSudano} based on I{John Sudano}'s
|
|
9
|
+
U{paper<https://www.ResearchGate.net/publication/
|
|
10
|
+
3709199_An_exact_conversion_from_an_Earth-centered_coordinate_system_to_latitude_longitude_and_altitude>},
|
|
11
|
+
class L{EcefVeness} transcoded from I{Chris Veness}' JavaScript classes U{LatLonEllipsoidal,
|
|
12
|
+
Cartesian<https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}, class L{EcefYou}
|
|
13
|
+
implementing I{Rey-Jer You}'s U{transformations<https://www.ResearchGate.net/publication/240359424>} and
|
|
14
|
+
classes L{EcefFarrell22} and L{EcefFarrell22} from I{Jay A. Farrell}'s U{Table 2.1 and 2.2
|
|
15
|
+
<https://Books.Google.com/books?id=fW4foWASY6wC>}, page 29-30.
|
|
16
|
+
|
|
17
|
+
Following is a copy of I{Karney}'s U{Detailed Description
|
|
18
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geocentric.html>}.
|
|
19
|
+
|
|
20
|
+
Convert between geodetic coordinates C{lat}-, C{lon}gitude and height C{h} (measured vertically
|
|
21
|
+
from the surface of the ellipsoid) to geocentric C{x}, C{y} and C{z} coordinates, also known as
|
|
22
|
+
I{Earth-Centered, Earth-Fixed} (U{ECEF<https://WikiPedia.org/wiki/ECEF>}).
|
|
23
|
+
|
|
24
|
+
The origin of geocentric coordinates is at the center of the earth. The C{z} axis goes thru
|
|
25
|
+
the North pole, C{lat} = 90°. The C{x} axis goes thru C{lat} = 0°, C{lon} = 0°.
|
|
26
|
+
|
|
27
|
+
The I{local (cartesian) origin} is at (C{lat0}, C{lon0}, C{height0}). The I{local} C{x} axis points
|
|
28
|
+
East, the I{local} C{y} axis points North and the I{local} C{z} axis is normal to the ellipsoid. The
|
|
29
|
+
plane C{z = -height0} is tangent to the ellipsoid, hence the alternate name I{local tangent plane}.
|
|
30
|
+
|
|
31
|
+
Forward conversion from geodetic to geocentric (ECEF) coordinates is straightforward.
|
|
32
|
+
|
|
33
|
+
For the reverse transformation we use Hugues Vermeille's U{I{Direct transformation from geocentric
|
|
34
|
+
coordinates to geodetic coordinates}<https://DOI.org/10.1007/s00190-002-0273-6>}, J. Geodesy
|
|
35
|
+
(2002) 76, page 451-454.
|
|
36
|
+
|
|
37
|
+
Several changes have been made to ensure that the method returns accurate results for all finite
|
|
38
|
+
inputs (even if h is infinite). The changes are described in Appendix B of C. F. F. Karney
|
|
39
|
+
U{I{Geodesics on an ellipsoid of revolution}<https://ArXiv.org/abs/1102.1215v1>}, Feb. 2011, 85,
|
|
40
|
+
105-117 (U{preprint<https://ArXiv.org/abs/1102.1215v1>}). Vermeille similarly updated his method
|
|
41
|
+
in U{I{An analytical method to transform geocentric into geodetic coordinates}
|
|
42
|
+
<https://DOI.org/10.1007/s00190-010-0419-x>}, J. Geodesy (2011) 85, page 105-117. See U{Geocentric
|
|
43
|
+
coordinates<https://GeographicLib.SourceForge.io/C++/doc/geocentric.html>} for more information.
|
|
44
|
+
|
|
45
|
+
The errors in these routines are close to round-off. Specifically, for points within 5,000 Km of
|
|
46
|
+
the surface of the ellipsoid (either inside or outside the ellipsoid), the error is bounded by 7
|
|
47
|
+
nm (7 nanometers) for the WGS84 ellipsoid. See U{Geocentric coordinates
|
|
48
|
+
<https://GeographicLib.SourceForge.io/C++/doc/geocentric.html>} for further information on the errors.
|
|
49
|
+
|
|
50
|
+
@note: The C{reverse} methods of all C{Ecef...} classes return by default C{INT0} as the (geodetic)
|
|
51
|
+
longitude for I{polar} ECEF location C{x == y == 0}. Use keyword argument C{lon00} or property
|
|
52
|
+
C{lon00} to configure that value.
|
|
53
|
+
|
|
54
|
+
@see: Module L{ltp} and class L{LocalCartesian}, a transcription of I{Charles Karney}'s C++ class
|
|
55
|
+
U{LocalCartesian<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1LocalCartesian.html>},
|
|
56
|
+
for conversion between geodetic and I{local cartesian} coordinates in a I{local tangent
|
|
57
|
+
plane} as opposed to I{geocentric} (ECEF) ones.
|
|
58
|
+
'''
|
|
59
|
+
|
|
60
|
+
from pygeodesy.basics import copysign0, isscalar, issubclassof, neg, map1, \
|
|
61
|
+
_xinstanceof, _xsubclassof
|
|
62
|
+
from pygeodesy.constants import EPS, EPS0, EPS02, EPS1, EPS2, EPS_2, INT0, PI, PI_2, \
|
|
63
|
+
_0_0, _0_0001, _0_01, _0_5, _1_0, _1_0_1T, _N_1_0, \
|
|
64
|
+
_2_0, _N_2_0, _3_0, _4_0, _6_0, _60_0, _90_0, _N_90_0, \
|
|
65
|
+
_100_0, _copysign_1_0, isnon0 # PYCHOK used!
|
|
66
|
+
from pygeodesy.datums import a_f2Tuple, _ellipsoidal_datum, _WGS84, _EWGS84
|
|
67
|
+
# from pygeodesy.ellipsoids import a_f2Tuple, _EWGS84 # from .datums
|
|
68
|
+
from pygeodesy.errors import _IndexError, LenError, _ValueError, _TypesError, \
|
|
69
|
+
_xattr, _xdatum, _xkwds, _xkwds_get
|
|
70
|
+
from pygeodesy.fmath import cbrt, fdot, hypot, hypot1, hypot2_
|
|
71
|
+
from pygeodesy.fsums import Fsum, fsumf_, Fmt, unstr
|
|
72
|
+
from pygeodesy.interns import NN, _a_, _C_, _datum_, _ellipsoid_, _f_, _height_, \
|
|
73
|
+
_lat_, _lon_, _M_, _name_, _singular_, _SPACE_, \
|
|
74
|
+
_x_, _xyz_, _y_, _z_
|
|
75
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
76
|
+
from pygeodesy.named import _NamedBase, _NamedTuple, notOverloaded, _Pass, _xnamed
|
|
77
|
+
from pygeodesy.namedTuples import LatLon2Tuple, LatLon3Tuple, \
|
|
78
|
+
PhiLam2Tuple, Vector3Tuple, Vector4Tuple
|
|
79
|
+
from pygeodesy.props import deprecated_method, Property_RO, property_RO, property_doc_
|
|
80
|
+
# from pygeodesy.streprs import Fmt, unstr # from .fsums
|
|
81
|
+
from pygeodesy.units import _isRadius, Degrees, Height, Int, Lam, Lat, Lon, Meter, \
|
|
82
|
+
Phi, Scalar, Scalar_
|
|
83
|
+
from pygeodesy.utily import atan1, atan1d, atan2d, degrees90, degrees180, \
|
|
84
|
+
sincos2, sincos2_, sincos2d, sincos2d_
|
|
85
|
+
|
|
86
|
+
from math import atan2, cos, degrees, fabs, radians, sqrt
|
|
87
|
+
|
|
88
|
+
__all__ = _ALL_LAZY.ecef
|
|
89
|
+
__version__ = '24.03.22'
|
|
90
|
+
|
|
91
|
+
_Ecef_ = 'Ecef'
|
|
92
|
+
_prolate_ = 'prolate'
|
|
93
|
+
_TRIPS = 33 # 8..9 sufficient, EcefSudano.reverse
|
|
94
|
+
_xyz_y_z = _xyz_, _y_, _z_ # _args_kwds_names(_xyzn4)[:3]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class EcefError(_ValueError):
|
|
98
|
+
'''An ECEF or C{Ecef*} related issue.
|
|
99
|
+
'''
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class _EcefBase(_NamedBase):
|
|
104
|
+
'''(INTERNAL) Base class for L{EcefFarrell21}, L{EcefFarrell22}, L{EcefKarney},
|
|
105
|
+
L{EcefSudano}, L{EcefVeness} and L{EcefYou}.
|
|
106
|
+
'''
|
|
107
|
+
_datum = _WGS84
|
|
108
|
+
_E = _EWGS84
|
|
109
|
+
_lon00 = INT0 # arbitrary, "polar" lon for LocalCartesian, Ltp
|
|
110
|
+
|
|
111
|
+
def __init__(self, a_ellipsoid=_EWGS84, f=None, name=NN, lon00=INT0):
|
|
112
|
+
'''New C{Ecef*} converter.
|
|
113
|
+
|
|
114
|
+
@arg a_ellipsoid: A (non-prolate) ellipsoid (L{Ellipsoid}, L{Ellipsoid2},
|
|
115
|
+
L{Datum} or L{a_f2Tuple}) or C{scalar} ellipsoid's
|
|
116
|
+
equatorial radius (C{meter}).
|
|
117
|
+
@kwarg f: C{None} or the ellipsoid flattening (C{scalar}), required
|
|
118
|
+
for C{scalar} B{C{a_ellipsoid}}, C{B{f}=0} represents a
|
|
119
|
+
sphere, negative B{C{f}} a prolate ellipsoid.
|
|
120
|
+
@kwarg name: Optional name (C{str}).
|
|
121
|
+
@kwarg lon00: An arbitrary, I{"polar"} longitude (C{degrees}), see the
|
|
122
|
+
methods C{reverse}.
|
|
123
|
+
|
|
124
|
+
@raise EcefError: If B{C{a_ellipsoid}} not L{Ellipsoid}, L{Ellipsoid2},
|
|
125
|
+
L{Datum} or L{a_f2Tuple} or C{scalar} or B{C{f}} not
|
|
126
|
+
C{scalar} or if C{scalar} B{C{a_ellipsoid}} not positive
|
|
127
|
+
or B{C{f}} not less than 1.0.
|
|
128
|
+
'''
|
|
129
|
+
try:
|
|
130
|
+
E = a_ellipsoid
|
|
131
|
+
if f is None:
|
|
132
|
+
pass
|
|
133
|
+
elif _isRadius(E) and isscalar(f):
|
|
134
|
+
E = a_f2Tuple(E, f)
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError # _invalid_
|
|
137
|
+
|
|
138
|
+
if E not in (_EWGS84, _WGS84):
|
|
139
|
+
d = _ellipsoidal_datum(E, name=name)
|
|
140
|
+
E = d.ellipsoid
|
|
141
|
+
if E.a < EPS or E.f > EPS1:
|
|
142
|
+
raise ValueError # _invalid_
|
|
143
|
+
self._datum = d
|
|
144
|
+
self._E = E
|
|
145
|
+
|
|
146
|
+
except (TypeError, ValueError) as x:
|
|
147
|
+
t = unstr(self.classname, a=a_ellipsoid, f=f)
|
|
148
|
+
raise EcefError(_SPACE_(t, _ellipsoid_), cause=x)
|
|
149
|
+
|
|
150
|
+
if name:
|
|
151
|
+
self.name = name
|
|
152
|
+
if lon00 is not INT0:
|
|
153
|
+
self.lon00 = lon00
|
|
154
|
+
|
|
155
|
+
def __eq__(self, other):
|
|
156
|
+
'''Compare this and an other Ecef.
|
|
157
|
+
|
|
158
|
+
@arg other: The other ecef (C{Ecef*}).
|
|
159
|
+
|
|
160
|
+
@return: C{True} if equal, C{False} otherwise.
|
|
161
|
+
'''
|
|
162
|
+
return other is self or (isinstance(other, self.__class__) and
|
|
163
|
+
other.ellipsoid == self.ellipsoid)
|
|
164
|
+
|
|
165
|
+
@Property_RO
|
|
166
|
+
def datum(self):
|
|
167
|
+
'''Get the datum (L{Datum}).
|
|
168
|
+
'''
|
|
169
|
+
return self._datum
|
|
170
|
+
|
|
171
|
+
@Property_RO
|
|
172
|
+
def ellipsoid(self):
|
|
173
|
+
'''Get the ellipsoid (L{Ellipsoid} or L{Ellipsoid2}).
|
|
174
|
+
'''
|
|
175
|
+
return self._E
|
|
176
|
+
|
|
177
|
+
@Property_RO
|
|
178
|
+
def equatoradius(self):
|
|
179
|
+
'''Get the C{ellipsoid}'s equatorial radius, semi-axis (C{meter}).
|
|
180
|
+
'''
|
|
181
|
+
return self.ellipsoid.a
|
|
182
|
+
|
|
183
|
+
a = equatorialRadius = equatoradius # Karney property
|
|
184
|
+
|
|
185
|
+
@Property_RO
|
|
186
|
+
def flattening(self): # Karney property
|
|
187
|
+
'''Get the C{ellipsoid}'s flattening (C{scalar}), positive for
|
|
188
|
+
I{oblate}, negative for I{prolate} or C{0} for I{near-spherical}.
|
|
189
|
+
'''
|
|
190
|
+
return self.ellipsoid.f
|
|
191
|
+
|
|
192
|
+
f = flattening
|
|
193
|
+
|
|
194
|
+
def _forward(self, lat, lon, h, name, M=False, _philam=False): # in .ltp.LocalCartesian.forward and -.reset
|
|
195
|
+
'''(INTERNAL) Common for all C{Ecef*}.
|
|
196
|
+
'''
|
|
197
|
+
if _philam: # lat, lon in radians
|
|
198
|
+
sa, ca, sb, cb = sincos2_(lat, lon)
|
|
199
|
+
lat = Lat(degrees90( lat), Error=EcefError)
|
|
200
|
+
lon = Lon(degrees180(lon), Error=EcefError)
|
|
201
|
+
else:
|
|
202
|
+
sa, ca, sb, cb = sincos2d_(lat, lon)
|
|
203
|
+
|
|
204
|
+
E = self.ellipsoid
|
|
205
|
+
n = E.roc1_(sa, ca) if self._isYou else E.roc1_(sa)
|
|
206
|
+
z = (h + n * E.e21) * sa
|
|
207
|
+
x = (h + n) * ca
|
|
208
|
+
|
|
209
|
+
m = self._Matrix(sa, ca, sb, cb) if M else None
|
|
210
|
+
return Ecef9Tuple(x * cb, x * sb, z, lat, lon, h,
|
|
211
|
+
0, m, self.datum,
|
|
212
|
+
name=name or self.name)
|
|
213
|
+
|
|
214
|
+
def forward(self, latlonh, lon=None, height=0, M=False, name=NN):
|
|
215
|
+
'''Convert from geodetic C{(lat, lon, height)} to geocentric C{(x, y, z)}.
|
|
216
|
+
|
|
217
|
+
@arg latlonh: Either a C{LatLon}, an L{Ecef9Tuple} or C{scalar}
|
|
218
|
+
latitude (C{degrees}).
|
|
219
|
+
@kwarg lon: Optional C{scalar} longitude for C{scalar} B{C{latlonh}}
|
|
220
|
+
(C{degrees}).
|
|
221
|
+
@kwarg height: Optional height (C{meter}), vertically above (or below)
|
|
222
|
+
the surface of the ellipsoid.
|
|
223
|
+
@kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}).
|
|
224
|
+
@kwarg name: Optional name (C{str}).
|
|
225
|
+
|
|
226
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
227
|
+
geocentric C{(x, y, z)} coordinates for the given geodetic ones
|
|
228
|
+
C{(lat, lon, height)}, case C{C} 0, optional C{M} (L{EcefMatrix})
|
|
229
|
+
and C{datum} if available.
|
|
230
|
+
|
|
231
|
+
@raise EcefError: If B{C{latlonh}} not C{LatLon}, L{Ecef9Tuple} or
|
|
232
|
+
C{scalar} or B{C{lon}} not C{scalar} for C{scalar}
|
|
233
|
+
B{C{latlonh}} or C{abs(lat)} exceeds 90°.
|
|
234
|
+
|
|
235
|
+
@note: Use method C{.forward_} to specify C{lat} and C{lon} in C{radians}
|
|
236
|
+
and avoid double angle conversions.
|
|
237
|
+
'''
|
|
238
|
+
llhn = _llhn4(latlonh, lon, height, name=name)
|
|
239
|
+
return self._forward(*llhn, M=M)
|
|
240
|
+
|
|
241
|
+
def forward_(self, phi, lam, height=0, M=False, name=NN):
|
|
242
|
+
'''Like method C{.forward} except with geodetic lat- and longitude given
|
|
243
|
+
in I{radians}.
|
|
244
|
+
|
|
245
|
+
@arg phi: Latitude in I{radians} (C{scalar}).
|
|
246
|
+
@arg lam: Longitude in I{radians} (C{scalar}).
|
|
247
|
+
@kwarg height: Optional height (C{meter}), vertically above (or below)
|
|
248
|
+
the surface of the ellipsoid.
|
|
249
|
+
@kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}).
|
|
250
|
+
@kwarg name: Optional name (C{str}).
|
|
251
|
+
|
|
252
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
|
|
253
|
+
with C{lat} set to C{degrees90(B{phi})} and C{lon} to
|
|
254
|
+
C{degrees180(B{lam})}.
|
|
255
|
+
|
|
256
|
+
@raise EcefError: If B{C{phi}} or B{C{lam}} invalid or not C{scalar}.
|
|
257
|
+
'''
|
|
258
|
+
try: # like function C{_llhn4} above
|
|
259
|
+
plhn = Phi(phi), Lam(lam), Height(height), name
|
|
260
|
+
except (TypeError, ValueError) as x:
|
|
261
|
+
raise EcefError(phi=phi, lam=lam, height=height, cause=x)
|
|
262
|
+
return self._forward(*plhn, M=M, _philam=True)
|
|
263
|
+
|
|
264
|
+
@property_RO
|
|
265
|
+
def _Geocentrics(self):
|
|
266
|
+
'''(INTERNAL) Get the valid geocentric classes. I{once}.
|
|
267
|
+
'''
|
|
268
|
+
_EcefBase._Geocentrics = t = (Ecef9Tuple, # overwrite property_RO
|
|
269
|
+
_MODS.cartesianBase.CartesianBase)
|
|
270
|
+
return t
|
|
271
|
+
|
|
272
|
+
@Property_RO
|
|
273
|
+
def _isYou(self):
|
|
274
|
+
'''(INTERNAL) Is this an C{EcefYou}?.
|
|
275
|
+
'''
|
|
276
|
+
return isinstance(self, EcefYou)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def lon00(self):
|
|
280
|
+
'''Get the I{"polar"} longitude (C{degrees}), see method C{reverse}.
|
|
281
|
+
'''
|
|
282
|
+
return self._lon00
|
|
283
|
+
|
|
284
|
+
@lon00.setter # PYCHOK setter!
|
|
285
|
+
def lon00(self, lon00):
|
|
286
|
+
'''Set the I{"polar"} longitude (C{degrees}), see method C{reverse}.
|
|
287
|
+
'''
|
|
288
|
+
self._lon00 = Degrees(lon00=lon00)
|
|
289
|
+
|
|
290
|
+
def _Matrix(self, sa, ca, sb, cb):
|
|
291
|
+
'''Creation a rotation matrix.
|
|
292
|
+
|
|
293
|
+
@arg sa: C{sin(phi)} (C{float}).
|
|
294
|
+
@arg ca: C{cos(phi)} (C{float}).
|
|
295
|
+
@arg sb: C{sin(lambda)} (C{float}).
|
|
296
|
+
@arg cb: C{cos(lambda)} (C{float}).
|
|
297
|
+
|
|
298
|
+
@return: An L{EcefMatrix}.
|
|
299
|
+
'''
|
|
300
|
+
return self._xnamed(EcefMatrix(sa, ca, sb, cb))
|
|
301
|
+
|
|
302
|
+
def _polon(self, y, x, R, **name_lon00):
|
|
303
|
+
'''(INTERNAL) Handle I{"polar"} longitude.
|
|
304
|
+
'''
|
|
305
|
+
return atan2d(y, x) if R else _xkwds_get(name_lon00, lon00=self.lon00)
|
|
306
|
+
|
|
307
|
+
def reverse(self, xyz, y=None, z=None, M=False, **name_lon00): # PYCHOK no cover
|
|
308
|
+
'''I{Must be overloaded}.'''
|
|
309
|
+
notOverloaded(self, xyz, y=y, z=z, M=M, **name_lon00)
|
|
310
|
+
|
|
311
|
+
def toStr(self, prec=9, **unused): # PYCHOK signature
|
|
312
|
+
'''Return this C{Ecef*} as a string.
|
|
313
|
+
|
|
314
|
+
@kwarg prec: Precision, number of decimal digits (0..9).
|
|
315
|
+
|
|
316
|
+
@return: This C{Ecef*} (C{str}).
|
|
317
|
+
'''
|
|
318
|
+
return self.attrs(_a_, _f_, _datum_, _name_, prec=prec) # _ellipsoid_
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class EcefFarrell21(_EcefBase):
|
|
322
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF)
|
|
323
|
+
coordinates based on I{Jay A. Farrell}'s U{Table 2.1<https://Books.Google.com/
|
|
324
|
+
books?id=fW4foWASY6wC>}, page 29.
|
|
325
|
+
'''
|
|
326
|
+
|
|
327
|
+
def reverse(self, xyz, y=None, z=None, M=None, **name_lon00): # PYCHOK unused M
|
|
328
|
+
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using
|
|
329
|
+
I{Farrell}'s U{Table 2.1<https://Books.Google.com/books?id=fW4foWASY6wC>},
|
|
330
|
+
page 29.
|
|
331
|
+
|
|
332
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
333
|
+
coordinate (C{meter}).
|
|
334
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
335
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
336
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
337
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
338
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
339
|
+
the default and property C{lon00} setting and returned if
|
|
340
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
341
|
+
|
|
342
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
343
|
+
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
344
|
+
ones C{(x, y, z)}, case C{C=1}, C{M=None} always and C{datum}
|
|
345
|
+
if available.
|
|
346
|
+
|
|
347
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
348
|
+
not C{scalar} for C{scalar} B{C{xyz}} or C{sqrt} domain or
|
|
349
|
+
zero division error.
|
|
350
|
+
|
|
351
|
+
@see: L{EcefFarrell22} and L{EcefVeness}.
|
|
352
|
+
'''
|
|
353
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
354
|
+
|
|
355
|
+
E = self.ellipsoid
|
|
356
|
+
a = E.a
|
|
357
|
+
a2 = E.a2
|
|
358
|
+
b2 = E.b2
|
|
359
|
+
e2 = E.e2
|
|
360
|
+
e2_ = E.e2abs * E.a2_b2 # (E.e * E.a_b)**2 = 0.0820944... WGS84
|
|
361
|
+
e4 = E.e4
|
|
362
|
+
|
|
363
|
+
try: # names as page 29
|
|
364
|
+
z2 = z**2
|
|
365
|
+
ez = z2 * (_1_0 - e2) # E.e2s2(z)
|
|
366
|
+
|
|
367
|
+
p = hypot(x, y)
|
|
368
|
+
p2 = p**2
|
|
369
|
+
G = p2 + ez - e2 * (a2 - b2) # p2 + ez - e4 * a2
|
|
370
|
+
F = b2 * z2 * 54
|
|
371
|
+
t = e4 * p2 * F / G**3
|
|
372
|
+
t = cbrt(sqrt(t * (t + _2_0)) + t + _1_0)
|
|
373
|
+
G *= fsumf_(t , _1_0, _1_0 / t)
|
|
374
|
+
P = F / (G**2 * _3_0)
|
|
375
|
+
Q = sqrt(_2_0 * e4 * P + _1_0)
|
|
376
|
+
Q1 = Q + _1_0
|
|
377
|
+
r0 = P * p * e2 / Q1 - sqrt(fsumf_(a2 * (Q1 / Q) * _0_5,
|
|
378
|
+
-P * ez / (Q * Q1),
|
|
379
|
+
-P * p2 * _0_5))
|
|
380
|
+
r = p + e2 * r0
|
|
381
|
+
v = b2 / (sqrt(r**2 + ez) * a)
|
|
382
|
+
|
|
383
|
+
h = hypot(r, z) * (_1_0 - v)
|
|
384
|
+
lat = atan1d((e2_ * v + _1_0) * z, p)
|
|
385
|
+
lon = self._polon(y, x, p, **name_lon00)
|
|
386
|
+
# note, phi and lam are swapped on page 29
|
|
387
|
+
|
|
388
|
+
except (ValueError, ZeroDivisionError) as e:
|
|
389
|
+
raise EcefError(x=x, y=y, z=z, cause=e)
|
|
390
|
+
|
|
391
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
392
|
+
1, None, self.datum,
|
|
393
|
+
name=name or self.name)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class EcefFarrell22(_EcefBase):
|
|
397
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF)
|
|
398
|
+
coordinates based on I{Jay A. Farrell}'s U{Table 2.2<https://Books.Google.com/
|
|
399
|
+
books?id=fW4foWASY6wC>}, page 30.
|
|
400
|
+
'''
|
|
401
|
+
|
|
402
|
+
def reverse(self, xyz, y=None, z=None, M=None, **name_lon00): # PYCHOK unused M
|
|
403
|
+
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using
|
|
404
|
+
I{Farrell}'s U{Table 2.2<https://Books.Google.com/books?id=fW4foWASY6wC>},
|
|
405
|
+
page 30.
|
|
406
|
+
|
|
407
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
408
|
+
coordinate (C{meter}).
|
|
409
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
410
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
411
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
412
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
413
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
414
|
+
the default and property C{lon00} setting and returned in case
|
|
415
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
416
|
+
|
|
417
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
418
|
+
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
419
|
+
ones C{(x, y, z)}, case C{C=1}, C{M=None} always and C{datum}
|
|
420
|
+
if available.
|
|
421
|
+
|
|
422
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
423
|
+
not C{scalar} for C{scalar} B{C{xyz}} or C{sqrt} domain or
|
|
424
|
+
zero division error.
|
|
425
|
+
|
|
426
|
+
@see: L{EcefFarrell21} and L{EcefVeness}.
|
|
427
|
+
'''
|
|
428
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
429
|
+
|
|
430
|
+
E = self.ellipsoid
|
|
431
|
+
a = E.a
|
|
432
|
+
b = E.b
|
|
433
|
+
|
|
434
|
+
try: # see EcefVeness.reverse
|
|
435
|
+
p = hypot(x, y)
|
|
436
|
+
lon = self._polon(y, x, p, **name_lon00)
|
|
437
|
+
|
|
438
|
+
s, c = sincos2(atan2(z * a, p * b)) # == _norm3
|
|
439
|
+
lat = atan1d(z + s**3 * b * E.e22,
|
|
440
|
+
p - c**3 * a * E.e2)
|
|
441
|
+
|
|
442
|
+
s, c = sincos2d(lat)
|
|
443
|
+
if c: # E.roc1_(s) = E.a / sqrt(1 - E.e2 * s**2)
|
|
444
|
+
h = p / c - (E.roc1_(s) if s else a)
|
|
445
|
+
else: # polar
|
|
446
|
+
h = fabs(z) - b
|
|
447
|
+
# note, phi and lam are swapped on page 30
|
|
448
|
+
|
|
449
|
+
except (ValueError, ZeroDivisionError) as e:
|
|
450
|
+
raise EcefError(x=x, y=y, z=z, cause=e)
|
|
451
|
+
|
|
452
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
453
|
+
1, None, self.datum,
|
|
454
|
+
name=name or self.name)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class EcefKarney(_EcefBase):
|
|
458
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF)
|
|
459
|
+
coordinates transcoded from I{Karney}'s C++ U{Geocentric<https://GeographicLib.SourceForge.io/
|
|
460
|
+
C++/doc/classGeographicLib_1_1Geocentric.html>} methods.
|
|
461
|
+
|
|
462
|
+
@note: On methods C{.forward} and C{.forwar_}, let C{v} be a unit vector located
|
|
463
|
+
at C{(lat, lon, h)}. We can express C{v} as column vectors in one of two
|
|
464
|
+
ways, C{v1} in East, North, Up (ENU) coordinates (where the components are
|
|
465
|
+
relative to a local coordinate system at C{C(lat0, lon0, h0)}) or as C{v0}
|
|
466
|
+
in geocentric C{x, y, z} coordinates. Then, M{v0 = M ⋅ v1} where C{M} is
|
|
467
|
+
the rotation matrix.
|
|
468
|
+
'''
|
|
469
|
+
|
|
470
|
+
@Property_RO
|
|
471
|
+
def hmax(self):
|
|
472
|
+
'''Get the distance or height limit (C{meter}, conventionally).
|
|
473
|
+
'''
|
|
474
|
+
return self.equatoradius / EPS_2 # self.equatoradius * _2_EPS, 12M lighyears
|
|
475
|
+
|
|
476
|
+
def reverse(self, xyz, y=None, z=None, M=False, **name_lon00):
|
|
477
|
+
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}.
|
|
478
|
+
|
|
479
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
480
|
+
coordinate (C{meter}).
|
|
481
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
482
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
483
|
+
@kwarg M: Optionally, return the rotation L{EcefMatrix} (C{bool}).
|
|
484
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
485
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
486
|
+
the default and property C{lon00} setting and returned in case
|
|
487
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
488
|
+
|
|
489
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
490
|
+
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
491
|
+
ones C{(x, y, z)}, case C{C}, optional C{M} (L{EcefMatrix}) and
|
|
492
|
+
C{datum} if available.
|
|
493
|
+
|
|
494
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
495
|
+
not C{scalar} for C{scalar} B{C{xyz}}.
|
|
496
|
+
|
|
497
|
+
@note: In general, there are multiple solutions and the result which minimizes
|
|
498
|
+
C{height} is returned, i.e., the C{(lat, lon)} corresponding to the
|
|
499
|
+
closest point on the ellipsoid. If there are still multiple solutions
|
|
500
|
+
with different latitudes (applies only if C{z} = 0), then the solution
|
|
501
|
+
with C{lat} > 0 is returned. If there are still multiple solutions with
|
|
502
|
+
different longitudes (applies only if C{x} = C{y} = 0), then C{lon00} is
|
|
503
|
+
returned. The returned C{lon} is in the range [−180°, 180°] and C{height}
|
|
504
|
+
is not below M{−E.a * (1 − E.e2) / sqrt(1 − E.e2 * sin(lat)**2)}. Like
|
|
505
|
+
C{forward} above, M{v1 = Transpose(M) ⋅ v0}.
|
|
506
|
+
'''
|
|
507
|
+
def _norm3(y, x):
|
|
508
|
+
h = hypot(y, x) # EPS0, EPS_2
|
|
509
|
+
return (y / h, x / h, h) if h > 0 else (_0_0, _1_0, h)
|
|
510
|
+
|
|
511
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
512
|
+
|
|
513
|
+
E = self.ellipsoid
|
|
514
|
+
f = E.f
|
|
515
|
+
|
|
516
|
+
sb, cb, R = _norm3(y, x)
|
|
517
|
+
h = hypot(R, z) # distance to earth center
|
|
518
|
+
if h > self.hmax: # PYCHOK no cover
|
|
519
|
+
# We are really far away (> 12M light years). Treat the earth
|
|
520
|
+
# as a point and h above as an acceptable approximation to the
|
|
521
|
+
# height. This avoids overflow, e.g., in the computation of d
|
|
522
|
+
# below. It's possible that h has overflowed to INF, that's OK.
|
|
523
|
+
# Treat finite x, y, but R overflows to +INF by scaling by 2.
|
|
524
|
+
sb, cb, R = _norm3(y * _0_5, x * _0_5)
|
|
525
|
+
sa, ca, _ = _norm3(z * _0_5, R)
|
|
526
|
+
C = 1
|
|
527
|
+
|
|
528
|
+
elif E.e4: # E.isEllipsoidal
|
|
529
|
+
# Treat prolate spheroids by swapping R and Z here and by
|
|
530
|
+
# switching the arguments to phi = atan2(...) at the end.
|
|
531
|
+
p = (R / E.a)**2
|
|
532
|
+
q = (z / E.a)**2 * E.e21
|
|
533
|
+
if f < 0:
|
|
534
|
+
p, q = q, p
|
|
535
|
+
r = fsumf_(p, q, -E.e4)
|
|
536
|
+
e = E.e4 * q
|
|
537
|
+
if e or r > 0:
|
|
538
|
+
# Avoid possible division by zero when r = 0 by multiplying
|
|
539
|
+
# equations for s and t by r^3 and r, respectively.
|
|
540
|
+
s = d = e * p / _4_0 # s = r^3 * s
|
|
541
|
+
u = r = r / _6_0
|
|
542
|
+
r2 = r**2
|
|
543
|
+
r3 = r2 * r
|
|
544
|
+
t3 = r3 + s
|
|
545
|
+
d *= t3 + r3
|
|
546
|
+
if d < 0:
|
|
547
|
+
# t is complex, but the way u is defined, the result is real.
|
|
548
|
+
# There are three possible cube roots. We choose the root
|
|
549
|
+
# which avoids cancellation. Note, d < 0 implies r < 0.
|
|
550
|
+
u += cos(atan2(sqrt(-d), -t3) / _3_0) * r * _2_0
|
|
551
|
+
else:
|
|
552
|
+
# Pick the sign on the sqrt to maximize abs(t3). This
|
|
553
|
+
# minimizes loss of precision due to cancellation. The
|
|
554
|
+
# result is unchanged because of the way the t is used
|
|
555
|
+
# in definition of u.
|
|
556
|
+
if d > 0:
|
|
557
|
+
t3 += copysign0(sqrt(d), t3) # t3 = (r * t)^3
|
|
558
|
+
# N.B. cbrt always returns the real root, cbrt(-8) = -2.
|
|
559
|
+
t = cbrt(t3) # t = r * t
|
|
560
|
+
if t: # t can be zero; but then r2 / t -> 0.
|
|
561
|
+
u = fsumf_(u, t, r2 / t)
|
|
562
|
+
v = sqrt(e + u**2) # guaranteed positive
|
|
563
|
+
# Avoid loss of accuracy when u < 0. Underflow doesn't occur in
|
|
564
|
+
# E.e4 * q / (v - u) because u ~ e^4 when q is small and u < 0.
|
|
565
|
+
u = (e / (v - u)) if u < 0 else (u + v) # u+v, guaranteed positive
|
|
566
|
+
# Need to guard against w going negative due to roundoff in u - q.
|
|
567
|
+
w = E.e2abs * (u - q) / (_2_0 * v)
|
|
568
|
+
# Rearrange expression for k to avoid loss of accuracy due to
|
|
569
|
+
# subtraction. Division by 0 not possible because u > 0, w >= 0.
|
|
570
|
+
k1 = k2 = (u / (sqrt(u + w**2) + w)) if w > 0 else sqrt(u)
|
|
571
|
+
if f < 0:
|
|
572
|
+
k1 -= E.e2
|
|
573
|
+
else:
|
|
574
|
+
k2 += E.e2
|
|
575
|
+
sa, ca, h = _norm3(z / k1, R / k2)
|
|
576
|
+
h *= k1 - E.e21
|
|
577
|
+
C = 2
|
|
578
|
+
|
|
579
|
+
else: # e = E.e4 * q == 0 and r <= 0
|
|
580
|
+
# This leads to k = 0 (oblate, equatorial plane) and k + E.e^2 = 0
|
|
581
|
+
# (prolate, rotation axis) and the generation of 0/0 in the general
|
|
582
|
+
# formulas for phi and h, using the general formula and division
|
|
583
|
+
# by 0 in formula for h. Handle this case by taking the limits:
|
|
584
|
+
# f > 0: z -> 0, k -> E.e2 * sqrt(q) / sqrt(E.e4 - p)
|
|
585
|
+
# f < 0: r -> 0, k + E.e2 -> -E.e2 * sqrt(q) / sqrt(E.e4 - p)
|
|
586
|
+
q = E.e4 - p
|
|
587
|
+
if f < 0:
|
|
588
|
+
p, q = q, p
|
|
589
|
+
e = E.a
|
|
590
|
+
else:
|
|
591
|
+
e = E.b2_a
|
|
592
|
+
sa, ca, h = _norm3(sqrt(q * E._1_e21), sqrt(p))
|
|
593
|
+
if z < 0: # for tiny negative z, not for prolate
|
|
594
|
+
sa = neg(sa)
|
|
595
|
+
h *= neg(e / E.e2abs)
|
|
596
|
+
C = 3
|
|
597
|
+
|
|
598
|
+
else: # E.e4 == 0, spherical case
|
|
599
|
+
# Dealing with underflow in the general case with E.e2 = 0 is
|
|
600
|
+
# difficult. Origin maps to North pole, same as with ellipsoid.
|
|
601
|
+
sa, ca, _ = _norm3((z if h else _1_0), R)
|
|
602
|
+
h -= E.a
|
|
603
|
+
C = 4
|
|
604
|
+
|
|
605
|
+
# lon00 <https://GitHub.com/mrJean1/PyGeodesy/issues/77>
|
|
606
|
+
lon = self._polon(sb, cb, R, **name_lon00)
|
|
607
|
+
m = self._Matrix(sa, ca, sb, cb) if M else None
|
|
608
|
+
return Ecef9Tuple(x, y, z, atan1d(sa, ca), lon, h,
|
|
609
|
+
C, m, self.datum,
|
|
610
|
+
name=name or self.name)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class EcefSudano(_EcefBase):
|
|
614
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF) coordinates
|
|
615
|
+
based on I{John J. Sudano}'s U{paper<https://www.ResearchGate.net/publication/3709199>}.
|
|
616
|
+
'''
|
|
617
|
+
_tol = EPS2
|
|
618
|
+
|
|
619
|
+
def reverse(self, xyz, y=None, z=None, M=None, **name_lon00): # PYCHOK unused M
|
|
620
|
+
'''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} using
|
|
621
|
+
I{Sudano}'s U{iterative method<https://www.ResearchGate.net/publication/3709199>}.
|
|
622
|
+
|
|
623
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
624
|
+
coordinate (C{meter}).
|
|
625
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
626
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
627
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
628
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
629
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
630
|
+
the default and property C{lon00} setting and returned in case
|
|
631
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
632
|
+
|
|
633
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic
|
|
634
|
+
coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)},
|
|
635
|
+
iteration C{C}, C{M=None} always and C{datum} if available.
|
|
636
|
+
|
|
637
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
638
|
+
not C{scalar} for C{scalar} B{C{xyz}} or no convergence.
|
|
639
|
+
'''
|
|
640
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
641
|
+
|
|
642
|
+
E = self.ellipsoid
|
|
643
|
+
e = E.e2 * E.a
|
|
644
|
+
R = hypot(x, y) # Rh
|
|
645
|
+
d = e - R
|
|
646
|
+
|
|
647
|
+
_a = fabs
|
|
648
|
+
lat = atan1d(z, R * E.e21)
|
|
649
|
+
sa, ca = sincos2d(fabs(lat))
|
|
650
|
+
# Sudano's Eq (A-6) and (A-7) refactored/reduced,
|
|
651
|
+
# replacing Rn from Eq (A-4) with n = E.a / ca:
|
|
652
|
+
# N = ca**2 * ((z + E.e2 * n * sa) * ca - R * sa)
|
|
653
|
+
# = ca**2 * (z * ca + E.e2 * E.a * sa - R * sa)
|
|
654
|
+
# = ca**2 * (z * ca + (E.e2 * E.a - R) * sa)
|
|
655
|
+
# D = ca**3 * (E.e2 * n / E.e2s2(sa)) - R
|
|
656
|
+
# = ca**2 * (E.e2 * E.a / E.e2s2(sa) - R / ca**2)
|
|
657
|
+
# N / D = (z * ca + (E.e2 * E.a - R) * sa) /
|
|
658
|
+
# (E.e2 * E.a / E.e2s2(sa) - R / ca**2)
|
|
659
|
+
tol = self.tolerance
|
|
660
|
+
_S2 = Fsum(sa).fsum2_
|
|
661
|
+
_rt = sqrt
|
|
662
|
+
for i in range(1, _TRIPS):
|
|
663
|
+
ca2 = _1_0 - sa**2
|
|
664
|
+
if ca2 < EPS_2: # PYCHOK no cover
|
|
665
|
+
ca = _0_0
|
|
666
|
+
break
|
|
667
|
+
ca = _rt(ca2)
|
|
668
|
+
r = e / E.e2s2(sa) - R / ca2
|
|
669
|
+
if _a(r) < EPS_2:
|
|
670
|
+
break
|
|
671
|
+
lat = None
|
|
672
|
+
sa, r = _S2(-z * ca / r, -d * sa / r)
|
|
673
|
+
if _a(r) < tol:
|
|
674
|
+
break
|
|
675
|
+
else:
|
|
676
|
+
t = unstr(self.reverse, x=x, y=y, z=z)
|
|
677
|
+
raise EcefError(t, txt=Fmt.no_convergence(r, tol))
|
|
678
|
+
|
|
679
|
+
if lat is None:
|
|
680
|
+
lat = copysign0(atan1d(_a(sa), ca), z)
|
|
681
|
+
lon = self._polon(y, x, R, **name_lon00)
|
|
682
|
+
|
|
683
|
+
h = fsumf_(R * ca, _a(z * sa), -E.a * E.e2s(sa)) # use Veness'
|
|
684
|
+
# because Sudano's Eq (7) doesn't produce the correct height
|
|
685
|
+
# h = (fabs(z) + R - E.a * cos(a + E.e21) * sa / ca) / (ca + sa)
|
|
686
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
687
|
+
i, None, self.datum, # C=i, M=None
|
|
688
|
+
iteration=i, name=name or self.name)
|
|
689
|
+
|
|
690
|
+
@property_doc_(''' the convergence tolerance (C{float}).''')
|
|
691
|
+
def tolerance(self):
|
|
692
|
+
'''Get the convergence tolerance (C{scalar}).
|
|
693
|
+
'''
|
|
694
|
+
return self._tol
|
|
695
|
+
|
|
696
|
+
@tolerance.setter # PYCHOK setter!
|
|
697
|
+
def tolerance(self, tol):
|
|
698
|
+
'''Set the convergence tolerance (C{scalar}).
|
|
699
|
+
|
|
700
|
+
@raise EcefError: Non-scalar or invalid B{C{tol}}.
|
|
701
|
+
'''
|
|
702
|
+
self._tol = Scalar_(tolerance=tol, low=EPS, Error=EcefError)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class EcefVeness(_EcefBase):
|
|
706
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF) coordinates
|
|
707
|
+
transcoded from I{Chris Veness}' JavaScript classes U{LatLonEllipsoidal, Cartesian<https://
|
|
708
|
+
www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}.
|
|
709
|
+
|
|
710
|
+
@see: U{I{A Guide to Coordinate Systems in Great Britain}<https://www.OrdnanceSurvey.co.UK/
|
|
711
|
+
documents/resources/guide-coordinate-systems-great-britain.pdf>}, section I{B) Converting
|
|
712
|
+
between 3D Cartesian and ellipsoidal latitude, longitude and height coordinates}.
|
|
713
|
+
'''
|
|
714
|
+
|
|
715
|
+
def reverse(self, xyz, y=None, z=None, M=None, **name_lon00): # PYCHOK unused M
|
|
716
|
+
'''Conversion from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}
|
|
717
|
+
transcoded from I{Chris Veness}' U{JavaScript<https://www.Movable-Type.co.UK/
|
|
718
|
+
scripts/geodesy/docs/latlon-ellipsoidal.js.html>}.
|
|
719
|
+
|
|
720
|
+
Uses B. R. Bowring’s formulation for μm precision in concise form U{I{The accuracy
|
|
721
|
+
of geodetic latitude and height equations}<https://www.ResearchGate.net/publication/
|
|
722
|
+
233668213>}, Survey Review, Vol 28, 218, Oct 1985.
|
|
723
|
+
|
|
724
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
725
|
+
coordinate (C{meter}).
|
|
726
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
727
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
728
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
729
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
730
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
731
|
+
the default and property C{lon00} setting and returned in case
|
|
732
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
733
|
+
|
|
734
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
735
|
+
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
736
|
+
ones C{(x, y, z)}, case C{C}, C{M=None} always and C{datum} if available.
|
|
737
|
+
|
|
738
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
739
|
+
not C{scalar} for C{scalar} B{C{xyz}}.
|
|
740
|
+
|
|
741
|
+
@see: Toms, Ralph M. U{I{An Efficient Algorithm for Geocentric to Geodetic
|
|
742
|
+
Coordinate Conversion}<https://www.OSTI.gov/scitech/biblio/110235>},
|
|
743
|
+
Sept 1995 and U{I{An Improved Algorithm for Geocentric to Geodetic
|
|
744
|
+
Coordinate Conversion}<https://www.OSTI.gov/scitech/servlets/purl/231228>},
|
|
745
|
+
Apr 1996, both from Lawrence Livermore National Laboratory (LLNL) and
|
|
746
|
+
Sudano, John J, U{I{An exact conversion from an Earth-centered coordinate
|
|
747
|
+
system to latitude longitude and altitude}<https://www.ResearchGate.net/
|
|
748
|
+
publication/3709199>}.
|
|
749
|
+
'''
|
|
750
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
751
|
+
|
|
752
|
+
E = self.ellipsoid
|
|
753
|
+
a = E.a
|
|
754
|
+
|
|
755
|
+
p = hypot(x, y) # distance from minor axis
|
|
756
|
+
r = hypot(p, z) # polar radius
|
|
757
|
+
if min(p, r) > EPS0:
|
|
758
|
+
b = E.b * E.e22
|
|
759
|
+
# parametric latitude (Bowring eqn 17, replaced)
|
|
760
|
+
t = (E.b * z) / (a * p) * (_1_0 + b / r)
|
|
761
|
+
c = _1_0 / hypot1(t)
|
|
762
|
+
s = c * t
|
|
763
|
+
# geodetic latitude (Bowring eqn 18)
|
|
764
|
+
lat = atan1d(z + s**3 * b,
|
|
765
|
+
p - c**3 * a * E.e2)
|
|
766
|
+
|
|
767
|
+
# height above ellipsoid (Bowring eqn 7)
|
|
768
|
+
sa, ca = sincos2d(lat)
|
|
769
|
+
# r = a / E.e2s(sa) # length of normal terminated by minor axis
|
|
770
|
+
# h = p * ca + z * sa - (a * a / r)
|
|
771
|
+
h = fsumf_(p * ca, z * sa, -a * E.e2s(sa))
|
|
772
|
+
C = 1
|
|
773
|
+
|
|
774
|
+
# see <https://GIS.StackExchange.com/questions/28446>
|
|
775
|
+
elif p > EPS: # lat arbitrarily zero, equatorial lon
|
|
776
|
+
C, lat, h = 2, _0_0, (p - a)
|
|
777
|
+
|
|
778
|
+
else: # polar lat, lon arbitrarily lon00
|
|
779
|
+
C, lat, h = 3, (_N_90_0 if z < 0 else _90_0), (fabs(z) - E.b)
|
|
780
|
+
|
|
781
|
+
lon = self._polon(y, x, p, **name_lon00)
|
|
782
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
783
|
+
C, None, self.datum, # M=None
|
|
784
|
+
name=name or self.name)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
class EcefYou(_EcefBase):
|
|
788
|
+
'''Conversion between geodetic and geocentric, I{Earth-Centered, Earth-Fixed} (ECEF) coordinates
|
|
789
|
+
using I{Rey-Jer You}'s U{transformation<https://www.ResearchGate.net/publication/240359424>}
|
|
790
|
+
for I{non-prolate} ellipsoids.
|
|
791
|
+
|
|
792
|
+
@see: Featherstone, W.E., Claessens, S.J. U{I{Closed-form transformation between geodetic and
|
|
793
|
+
ellipsoidal coordinates}<https://Espace.Curtin.edu.AU/bitstream/handle/20.500.11937/
|
|
794
|
+
11589/115114_9021_geod2ellip_final.pdf>} Studia Geophysica et Geodaetica, 2008, 52,
|
|
795
|
+
pages 1-18 and U{PyMap3D <https://PyPI.org/project/pymap3d>}.
|
|
796
|
+
'''
|
|
797
|
+
|
|
798
|
+
def __init__(self, a_ellipsoid=_EWGS84, f=None, **name_lon00): # PYCHOK signature
|
|
799
|
+
_EcefBase.__init__(self, a_ellipsoid, f=f, **name_lon00) # inherited documentation
|
|
800
|
+
_ = EcefYou._e2(self.ellipsoid)
|
|
801
|
+
|
|
802
|
+
@staticmethod
|
|
803
|
+
def _e2(E):
|
|
804
|
+
e2 = E.a2 - E.b2
|
|
805
|
+
if e2 < 0 or E.f < 0:
|
|
806
|
+
raise EcefError(ellipsoid=E, txt=_prolate_)
|
|
807
|
+
return e2
|
|
808
|
+
|
|
809
|
+
def reverse(self, xyz, y=None, z=None, M=None, **name_lon00): # PYCHOK unused M
|
|
810
|
+
'''Convert geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}
|
|
811
|
+
using I{Rey-Jer You}'s transformation.
|
|
812
|
+
|
|
813
|
+
@arg xyz: A geocentric (C{Cartesian}, L{Ecef9Tuple}) or C{scalar} ECEF C{x}
|
|
814
|
+
coordinate (C{meter}).
|
|
815
|
+
@kwarg y: ECEF C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
816
|
+
@kwarg z: ECEF C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
817
|
+
@kwarg M: I{Ignored}, rotation matrix C{M} not available.
|
|
818
|
+
@kwarg name_lon00: Optional keyword arguments C{B{name}=NN} (C{str}) and
|
|
819
|
+
I{"polar"} longitude C{B{lon00}=INT0} (C{degrees}), overriding
|
|
820
|
+
the default and property C{lon00} setting and returned in case
|
|
821
|
+
C{B{x}=0} and C{B{y}=0}.
|
|
822
|
+
|
|
823
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
|
|
824
|
+
geodetic coordinates C{(lat, lon, height)} for the given geocentric
|
|
825
|
+
ones C{(x, y, z)}, case C{C=1}, C{M=None} always and C{datum} if
|
|
826
|
+
available.
|
|
827
|
+
|
|
828
|
+
@raise EcefError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or
|
|
829
|
+
B{C{z}} not C{scalar} for C{scalar} B{C{xyz}} or the
|
|
830
|
+
ellipsoid is I{prolate}.
|
|
831
|
+
'''
|
|
832
|
+
x, y, z, name = _xyzn4(xyz, y, z, self._Geocentrics, **name_lon00)
|
|
833
|
+
|
|
834
|
+
E = self.ellipsoid
|
|
835
|
+
e2 = EcefYou._e2(E)
|
|
836
|
+
e = sqrt(e2) if e2 > 0 else _0_0 # XXX sqrt0(e2)?
|
|
837
|
+
|
|
838
|
+
q = hypot( x, y) # R
|
|
839
|
+
r2 = hypot2_(x, y, z)
|
|
840
|
+
u = fsumf_(r2, -e2, hypot(r2 - e2, e * z * _2_0)) * _0_5
|
|
841
|
+
if u > EPS02:
|
|
842
|
+
u = sqrt(u)
|
|
843
|
+
p = hypot(u, e)
|
|
844
|
+
B = atan1(p * z, u * q) # beta0 = atan(p / u * z / q)
|
|
845
|
+
sB, cB = sincos2(B)
|
|
846
|
+
if cB and sB:
|
|
847
|
+
p *= E.a
|
|
848
|
+
d = (p / cB - e2 * cB) / sB
|
|
849
|
+
if isnon0(d):
|
|
850
|
+
B += fsumf_(u * E.b, -p, e2) / d
|
|
851
|
+
sB, cB = sincos2(B)
|
|
852
|
+
elif u < 0:
|
|
853
|
+
raise EcefError(x=x, y=y, z=z, txt=_singular_)
|
|
854
|
+
else:
|
|
855
|
+
sB, cB = _copysign_1_0(z), _0_0
|
|
856
|
+
|
|
857
|
+
lat = atan1d(E.a * sB, E.b * cB) # atan(E.a_b * tan(B))
|
|
858
|
+
lon = self._polon(y, x, q, **name_lon00)
|
|
859
|
+
|
|
860
|
+
h = hypot(z - E.b * sB, q - E.a * cB)
|
|
861
|
+
if hypot2_(x, y, z * E.a_b) < E.a2:
|
|
862
|
+
h = neg(h) # inside ellipsoid
|
|
863
|
+
return Ecef9Tuple(x, y, z, lat, lon, h,
|
|
864
|
+
1, None, self.datum, # C=1, M=None
|
|
865
|
+
name=name or self.name)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
class EcefMatrix(_NamedTuple):
|
|
869
|
+
'''A rotation matrix known as I{East-North-Up (ENU) to ECEF}.
|
|
870
|
+
|
|
871
|
+
@see: U{From ENU to ECEF<https://WikiPedia.org/wiki/
|
|
872
|
+
Geographic_coordinate_conversion#From_ECEF_to_ENU>} and
|
|
873
|
+
U{Issue #74<https://Github.com/mrJean1/PyGeodesy/issues/74>}.
|
|
874
|
+
'''
|
|
875
|
+
_Names_ = ('_0_0_', '_0_1_', '_0_2_', # row-order
|
|
876
|
+
'_1_0_', '_1_1_', '_1_2_',
|
|
877
|
+
'_2_0_', '_2_1_', '_2_2_')
|
|
878
|
+
_Units_ = (Scalar,) * len(_Names_)
|
|
879
|
+
|
|
880
|
+
def _validate(self, **unused): # PYCHOK unused
|
|
881
|
+
'''(INTERNAL) Allow C{_Names_} with leading underscore.
|
|
882
|
+
'''
|
|
883
|
+
_NamedTuple._validate(self, underOK=True)
|
|
884
|
+
|
|
885
|
+
def __new__(cls, sa, ca, sb, cb, *_more):
|
|
886
|
+
'''New L{EcefMatrix} matrix.
|
|
887
|
+
|
|
888
|
+
@arg sa: C{sin(phi)} (C{float}).
|
|
889
|
+
@arg ca: C{cos(phi)} (C{float}).
|
|
890
|
+
@arg sb: C{sin(lambda)} (C{float}).
|
|
891
|
+
@arg cb: C{cos(lambda)} (C{float}).
|
|
892
|
+
@arg _more: (INTERNAL) from C{.multiply}.
|
|
893
|
+
|
|
894
|
+
@raise EcefError: If B{C{sa}}, B{C{ca}}, B{C{sb}} or
|
|
895
|
+
B{C{cb}} outside M{[-1.0, +1.0]}.
|
|
896
|
+
'''
|
|
897
|
+
t = sa, ca, sb, cb
|
|
898
|
+
if _more: # all 9 matrix elements ...
|
|
899
|
+
t += _more # ... from .multiply
|
|
900
|
+
|
|
901
|
+
elif max(map(fabs, t)) > _1_0:
|
|
902
|
+
raise EcefError(unstr(EcefMatrix.__name__, *t))
|
|
903
|
+
|
|
904
|
+
else: # build matrix from the following quaternion operations
|
|
905
|
+
# qrot(lam, [0,0,1]) * qrot(phi, [0,-1,0]) * [1,1,1,1]/2
|
|
906
|
+
# or
|
|
907
|
+
# qrot(pi/2 + lam, [0,0,1]) * qrot(-pi/2 + phi, [-1,0,0])
|
|
908
|
+
# where
|
|
909
|
+
# qrot(t,v) = [cos(t/2), sin(t/2)*v[1], sin(t/2)*v[2], sin(t/2)*v[3]]
|
|
910
|
+
|
|
911
|
+
# Local X axis (East) in geocentric coords
|
|
912
|
+
# M[0] = -slam; M[3] = clam; M[6] = 0;
|
|
913
|
+
# Local Y axis (North) in geocentric coords
|
|
914
|
+
# M[1] = -clam * sphi; M[4] = -slam * sphi; M[7] = cphi;
|
|
915
|
+
# Local Z axis (Up) in geocentric coords
|
|
916
|
+
# M[2] = clam * cphi; M[5] = slam * cphi; M[8] = sphi;
|
|
917
|
+
t = (-sb, -cb * sa, cb * ca,
|
|
918
|
+
cb, -sb * sa, sb * ca,
|
|
919
|
+
_0_0, ca, sa)
|
|
920
|
+
|
|
921
|
+
return _NamedTuple.__new__(cls, *t)
|
|
922
|
+
|
|
923
|
+
def column(self, column):
|
|
924
|
+
'''Get this matrix' B{C{column}} 0, 1 or 2 as C{3-tuple}.
|
|
925
|
+
'''
|
|
926
|
+
if 0 <= column < 3:
|
|
927
|
+
return self[column::3]
|
|
928
|
+
raise _IndexError(column=column)
|
|
929
|
+
|
|
930
|
+
def copy(self, **unused): # PYCHOK signature
|
|
931
|
+
'''Make a shallow or deep copy of this instance.
|
|
932
|
+
|
|
933
|
+
@return: The copy (C{This class} or subclass thereof).
|
|
934
|
+
'''
|
|
935
|
+
return self.classof(*self)
|
|
936
|
+
|
|
937
|
+
__copy__ = __deepcopy__ = copy
|
|
938
|
+
|
|
939
|
+
@Property_RO
|
|
940
|
+
def matrix3(self):
|
|
941
|
+
'''Get this matrix' rows (C{3-tuple} of 3 C{3-tuple}s).
|
|
942
|
+
'''
|
|
943
|
+
return tuple(map(self.row, range(3)))
|
|
944
|
+
|
|
945
|
+
@Property_RO
|
|
946
|
+
def matrixTransposed3(self):
|
|
947
|
+
'''Get this matrix' I{Transposed} rows (C{3-tuple} of 3 C{3-tuple}s).
|
|
948
|
+
'''
|
|
949
|
+
return tuple(map(self.column, range(3)))
|
|
950
|
+
|
|
951
|
+
def multiply(self, other):
|
|
952
|
+
'''Matrix multiply M{M0' ⋅ M} this matrix I{Transposed}
|
|
953
|
+
with an other matrix.
|
|
954
|
+
|
|
955
|
+
@arg other: The other matrix (L{EcefMatrix}).
|
|
956
|
+
|
|
957
|
+
@return: The matrix product (L{EcefMatrix}).
|
|
958
|
+
|
|
959
|
+
@raise TypeError: If B{C{other}} is not L{EcefMatrix}.
|
|
960
|
+
'''
|
|
961
|
+
_xinstanceof(EcefMatrix, other=other)
|
|
962
|
+
# like LocalCartesian.MatrixMultiply, C{self.matrixTransposed3 X other.matrix3}
|
|
963
|
+
# <https://GeographicLib.SourceForge.io/C++/doc/LocalCartesian_8cpp_source.html>
|
|
964
|
+
# X = (fdot(self.column(r), *other.column(c)) for r in (0,1,2) for c in (0,1,2))
|
|
965
|
+
X = (fdot(self[r::3], *other[c::3]) for r in range(3) for c in range(3))
|
|
966
|
+
return _xnamed(EcefMatrix(*X), EcefMatrix.multiply.__name__)
|
|
967
|
+
|
|
968
|
+
def rotate(self, xyz, *xyz0):
|
|
969
|
+
'''Forward rotation M{M0' ⋅ ([x, y, z] - [x0, y0, z0])'}.
|
|
970
|
+
|
|
971
|
+
@arg xyz: Local C{(x, y, z)} coordinates (C{3-tuple}).
|
|
972
|
+
@arg xyz0: Optional, local C{(x0, y0, z0)} origin (C{3-tuple}).
|
|
973
|
+
|
|
974
|
+
@return: Rotated C{(x, y, z)} location (C{3-tuple}).
|
|
975
|
+
|
|
976
|
+
@raise LenError: Unequal C{len(B{xyz})} and C{len(B{xyz0})}.
|
|
977
|
+
'''
|
|
978
|
+
if xyz0:
|
|
979
|
+
if len(xyz0) != len(xyz):
|
|
980
|
+
raise LenError(self.rotate, xyz0=len(xyz0), xyz=len(xyz))
|
|
981
|
+
xyz = tuple(c - c0 for c, c0 in zip(xyz, xyz0))
|
|
982
|
+
|
|
983
|
+
# x' = M[0] * x + M[3] * y + M[6] * z
|
|
984
|
+
# y' = M[1] * x + M[4] * y + M[7] * z
|
|
985
|
+
# z' = M[2] * x + M[5] * y + M[8] * z
|
|
986
|
+
return (fdot(xyz, *self[0::3]), # .column(0)
|
|
987
|
+
fdot(xyz, *self[1::3]), # .column(1)
|
|
988
|
+
fdot(xyz, *self[2::3])) # .column(2)
|
|
989
|
+
|
|
990
|
+
def row(self, row):
|
|
991
|
+
'''Get this matrix' B{C{row}} 0, 1 or 2 as C{3-tuple}.
|
|
992
|
+
'''
|
|
993
|
+
if 0 <= row < 3:
|
|
994
|
+
r = row * 3
|
|
995
|
+
return self[r:r+3]
|
|
996
|
+
raise _IndexError(row=row)
|
|
997
|
+
|
|
998
|
+
def unrotate(self, xyz, *xyz0):
|
|
999
|
+
'''Inverse rotation M{[x0, y0, z0] + M0 ⋅ [x,y,z]'}.
|
|
1000
|
+
|
|
1001
|
+
@arg xyz: Local C{(x, y, z)} coordinates (C{3-tuple}).
|
|
1002
|
+
@arg xyz0: Optional, local C{(x0, y0, z0)} origin (C{3-tuple}).
|
|
1003
|
+
|
|
1004
|
+
@return: Unrotated C{(x, y, z)} location (C{3-tuple}).
|
|
1005
|
+
|
|
1006
|
+
@raise LenError: Unequal C{len(B{xyz})} and C{len(B{xyz0})}.
|
|
1007
|
+
'''
|
|
1008
|
+
if xyz0:
|
|
1009
|
+
if len(xyz0) != len(xyz):
|
|
1010
|
+
raise LenError(self.unrotate, xyz0=len(xyz0), xyz=len(xyz))
|
|
1011
|
+
_xyz = _1_0_1T + xyz
|
|
1012
|
+
# x' = x0 + M[0] * x + M[1] * y + M[2] * z
|
|
1013
|
+
# y' = y0 + M[3] * x + M[4] * y + M[5] * z
|
|
1014
|
+
# z' = z0 + M[6] * x + M[7] * y + M[8] * z
|
|
1015
|
+
xyz_ = (fdot(_xyz, xyz0[0], *self[0:3]), # .row(0)
|
|
1016
|
+
fdot(_xyz, xyz0[1], *self[3:6]), # .row(1)
|
|
1017
|
+
fdot(_xyz, xyz0[2], *self[6:9])) # .row(2)
|
|
1018
|
+
else:
|
|
1019
|
+
# x' = M[0] * x + M[1] * y + M[2] * z
|
|
1020
|
+
# y' = M[3] * x + M[4] * y + M[5] * z
|
|
1021
|
+
# z' = M[6] * x + M[7] * y + M[8] * z
|
|
1022
|
+
xyz_ = (fdot(xyz, *self[0:3]), # .row(0)
|
|
1023
|
+
fdot(xyz, *self[3:6]), # .row(1)
|
|
1024
|
+
fdot(xyz, *self[6:9])) # .row(2)
|
|
1025
|
+
return xyz_
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
class Ecef9Tuple(_NamedTuple):
|
|
1029
|
+
'''9-Tuple C{(x, y, z, lat, lon, height, C, M, datum)} with I{geocentric}
|
|
1030
|
+
C{x}, C{y} and C{z} plus I{geodetic} C{lat}, C{lon} and C{height}, case
|
|
1031
|
+
C{C} (see the C{Ecef*.reverse} methods) and optionally, the rotation
|
|
1032
|
+
matrix C{M} (L{EcefMatrix}) and C{datum}, with C{lat} and C{lon} in
|
|
1033
|
+
C{degrees} and C{x}, C{y}, C{z} and C{height} in C{meter}, conventionally.
|
|
1034
|
+
'''
|
|
1035
|
+
_Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _C_, _M_, _datum_)
|
|
1036
|
+
_Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, Int, _Pass, _Pass)
|
|
1037
|
+
|
|
1038
|
+
@property_RO
|
|
1039
|
+
def _CartesianBase(self):
|
|
1040
|
+
'''(INTERNAL) Get class C{CartesianBase}, I{once}.
|
|
1041
|
+
'''
|
|
1042
|
+
Ecef9Tuple._CartesianBase = C = _MODS.cartesianBase.CartesianBase # overwrite property_RO
|
|
1043
|
+
return C
|
|
1044
|
+
|
|
1045
|
+
@deprecated_method
|
|
1046
|
+
def convertDatum(self, datum2): # for backward compatibility
|
|
1047
|
+
'''DEPRECATED, use method L{toDatum}.'''
|
|
1048
|
+
return self.toDatum(datum2)
|
|
1049
|
+
|
|
1050
|
+
@Property_RO
|
|
1051
|
+
def lam(self):
|
|
1052
|
+
'''Get the longitude in C{radians} (C{float}).
|
|
1053
|
+
'''
|
|
1054
|
+
return self.philam.lam
|
|
1055
|
+
|
|
1056
|
+
@Property_RO
|
|
1057
|
+
def lamVermeille(self):
|
|
1058
|
+
'''Get the longitude in C{radians} M{[-PI*3/2..+PI*3/2]} after U{Vermeille
|
|
1059
|
+
<https://Search.ProQuest.com/docview/639493848>} (2004), page 95.
|
|
1060
|
+
|
|
1061
|
+
@see: U{Karney<https://GeographicLib.SourceForge.io/C++/doc/geocentric.html>},
|
|
1062
|
+
U{Vermeille<https://Search.ProQuest.com/docview/847292978>} 2011, pp 112-113, 116
|
|
1063
|
+
and U{Featherstone, et.al.<https://Search.ProQuest.com/docview/872827242>}, page 7.
|
|
1064
|
+
'''
|
|
1065
|
+
x, y = self.x, self.y
|
|
1066
|
+
if y > EPS0:
|
|
1067
|
+
r = atan2(x, hypot(y, x) + y) * _N_2_0 + PI_2
|
|
1068
|
+
elif y < -EPS0:
|
|
1069
|
+
r = atan2(x, hypot(y, x) - y) * _2_0 - PI_2
|
|
1070
|
+
else: # y == 0
|
|
1071
|
+
r = PI if x < 0 else _0_0
|
|
1072
|
+
return Lam(Vermeille=r)
|
|
1073
|
+
|
|
1074
|
+
@Property_RO
|
|
1075
|
+
def latlon(self):
|
|
1076
|
+
'''Get the lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
|
|
1077
|
+
'''
|
|
1078
|
+
return LatLon2Tuple(self.lat, self.lon, name=self.name)
|
|
1079
|
+
|
|
1080
|
+
@Property_RO
|
|
1081
|
+
def latlonheight(self):
|
|
1082
|
+
'''Get the lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
|
|
1083
|
+
'''
|
|
1084
|
+
return self.latlon.to3Tuple(self.height)
|
|
1085
|
+
|
|
1086
|
+
@Property_RO
|
|
1087
|
+
def latlonheightdatum(self):
|
|
1088
|
+
'''Get the lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
|
|
1089
|
+
'''
|
|
1090
|
+
return self.latlonheight.to4Tuple(self.datum)
|
|
1091
|
+
|
|
1092
|
+
@Property_RO
|
|
1093
|
+
def latlonVermeille(self):
|
|
1094
|
+
'''Get the latitude and I{Vermeille} longitude in C{degrees [-225..+225]} (L{LatLon2Tuple}C{(lat, lon)}).
|
|
1095
|
+
|
|
1096
|
+
@see: Property C{lonVermeille}.
|
|
1097
|
+
'''
|
|
1098
|
+
return LatLon2Tuple(self.lat, self.lonVermeille, name=self.name)
|
|
1099
|
+
|
|
1100
|
+
@Property_RO
|
|
1101
|
+
def lonVermeille(self):
|
|
1102
|
+
'''Get the longitude in C{degrees [-225..+225]} after U{Vermeille
|
|
1103
|
+
<https://Search.ProQuest.com/docview/639493848>} (2004), p 95.
|
|
1104
|
+
|
|
1105
|
+
@see: Property C{lamVermeille}.
|
|
1106
|
+
'''
|
|
1107
|
+
return Lon(Vermeille=degrees(self.lamVermeille))
|
|
1108
|
+
|
|
1109
|
+
@Property_RO
|
|
1110
|
+
def phi(self):
|
|
1111
|
+
'''Get the latitude in C{radians} (C{float}).
|
|
1112
|
+
'''
|
|
1113
|
+
return self.philam.phi
|
|
1114
|
+
|
|
1115
|
+
@Property_RO
|
|
1116
|
+
def philam(self):
|
|
1117
|
+
'''Get the lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
|
|
1118
|
+
'''
|
|
1119
|
+
return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
|
|
1120
|
+
|
|
1121
|
+
@Property_RO
|
|
1122
|
+
def philamheight(self):
|
|
1123
|
+
'''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
|
|
1124
|
+
'''
|
|
1125
|
+
return self.philam.to3Tuple(self.height)
|
|
1126
|
+
|
|
1127
|
+
@Property_RO
|
|
1128
|
+
def philamheightdatum(self):
|
|
1129
|
+
'''Get the lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
|
|
1130
|
+
'''
|
|
1131
|
+
return self.philamheight.to4Tuple(self.datum)
|
|
1132
|
+
|
|
1133
|
+
@Property_RO
|
|
1134
|
+
def philamVermeille(self):
|
|
1135
|
+
'''Get the latitude and I{Vermeille} longitude in C{radians [-PI*3/2..+PI*3/2]} (L{PhiLam2Tuple}C{(phi, lam)}).
|
|
1136
|
+
|
|
1137
|
+
@see: Property C{lamVermeille}.
|
|
1138
|
+
'''
|
|
1139
|
+
return PhiLam2Tuple(radians(self.lat), self.lamVermeille, name=self.name)
|
|
1140
|
+
|
|
1141
|
+
def toCartesian(self, Cartesian=None, **Cartesian_kwds):
|
|
1142
|
+
'''Return the geocentric C{(x, y, z)} coordinates as an ellipsoidal or spherical
|
|
1143
|
+
C{Cartesian}.
|
|
1144
|
+
|
|
1145
|
+
@kwarg Cartesian: Optional class to return C{(x, y, z)} (L{ellipsoidalKarney.Cartesian},
|
|
1146
|
+
L{ellipsoidalNvector.Cartesian}, L{ellipsoidalVincenty.Cartesian},
|
|
1147
|
+
L{sphericalNvector.Cartesian} or L{sphericalTrigonometry.Cartesian})
|
|
1148
|
+
or C{None}.
|
|
1149
|
+
@kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword arguments, ignored
|
|
1150
|
+
if C{B{Cartesian} is None}.
|
|
1151
|
+
|
|
1152
|
+
@return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or
|
|
1153
|
+
a L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
|
|
1154
|
+
|
|
1155
|
+
@raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}.
|
|
1156
|
+
'''
|
|
1157
|
+
if Cartesian in (None, Vector4Tuple):
|
|
1158
|
+
r = self.xyzh
|
|
1159
|
+
elif Cartesian is Vector3Tuple:
|
|
1160
|
+
r = self.xyz
|
|
1161
|
+
else:
|
|
1162
|
+
_xsubclassof(self._CartesianBase, Cartesian=Cartesian)
|
|
1163
|
+
r = Cartesian(self, **_xkwds(Cartesian_kwds, name=self.name))
|
|
1164
|
+
return r
|
|
1165
|
+
|
|
1166
|
+
def toDatum(self, datum2):
|
|
1167
|
+
'''Convert this C{Ecef9Tuple} to an other datum.
|
|
1168
|
+
|
|
1169
|
+
@arg datum2: Datum to convert I{to} (L{Datum}).
|
|
1170
|
+
|
|
1171
|
+
@return: The converted 9-Tuple (C{Ecef9Tuple}).
|
|
1172
|
+
|
|
1173
|
+
@raise TypeError: The B{C{datum2}} is not a L{Datum}.
|
|
1174
|
+
'''
|
|
1175
|
+
if self.datum in (None, datum2): # PYCHOK _Names_
|
|
1176
|
+
r = self.copy()
|
|
1177
|
+
else:
|
|
1178
|
+
c = self._CartesianBase(self, datum=self.datum, name=self.name) # PYCHOK _Names_
|
|
1179
|
+
# c.toLatLon converts datum, x, y, z, lat, lon, etc.
|
|
1180
|
+
# and returns another Ecef9Tuple iff LatLon is None
|
|
1181
|
+
r = c.toLatLon(datum=datum2, LatLon=None)
|
|
1182
|
+
return r
|
|
1183
|
+
|
|
1184
|
+
def toLatLon(self, LatLon=None, **LatLon_kwds):
|
|
1185
|
+
'''Return the geodetic C{(lat, lon, height[, datum])} coordinates.
|
|
1186
|
+
|
|
1187
|
+
@kwarg LatLon: Optional class to return C{(lat, lon, height[, datum])}
|
|
1188
|
+
or C{None}.
|
|
1189
|
+
@kwarg LatLon_kwds: Optional B{C{height}}, B{C{datum}} and other
|
|
1190
|
+
B{C{LatLon}} keyword arguments.
|
|
1191
|
+
|
|
1192
|
+
@return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
|
|
1193
|
+
or if B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat, lon,
|
|
1194
|
+
height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
|
|
1195
|
+
datum)} depending on whether C{datum} is un-/specified.
|
|
1196
|
+
|
|
1197
|
+
@raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}.
|
|
1198
|
+
'''
|
|
1199
|
+
lat, lon, D = self.lat, self.lon, self.datum # PYCHOK Ecef9Tuple
|
|
1200
|
+
kwds = _xkwds(LatLon_kwds, height=self.height, datum=D, name=self.name) # PYCHOK Ecef9Tuple
|
|
1201
|
+
d = kwds.get(_datum_, LatLon)
|
|
1202
|
+
if LatLon is None:
|
|
1203
|
+
r = LatLon3Tuple(lat, lon, kwds[_height_], name=kwds[_name_])
|
|
1204
|
+
if d is not None:
|
|
1205
|
+
# assert d is not LatLon
|
|
1206
|
+
r = r.to4Tuple(d) # checks type(d)
|
|
1207
|
+
else:
|
|
1208
|
+
if d is None:
|
|
1209
|
+
_ = kwds.pop(_datum_) # remove None datum
|
|
1210
|
+
r = LatLon(lat, lon, **kwds)
|
|
1211
|
+
_xdatum(_xattr(r, datum=D), D)
|
|
1212
|
+
return r
|
|
1213
|
+
|
|
1214
|
+
def toLocal(self, ltp, Xyz=None, **Xyz_kwds):
|
|
1215
|
+
'''Convert this geocentric to I{local} C{x}, C{y} and C{z}.
|
|
1216
|
+
|
|
1217
|
+
@kwarg ltp: The I{local tangent plane} (LTP) to use (L{Ltp}).
|
|
1218
|
+
@kwarg Xyz: Optional class to return C{x}, C{y} and C{z}
|
|
1219
|
+
(L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.
|
|
1220
|
+
@kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
|
|
1221
|
+
arguments, ignored if C{B{Xyz} is None}.
|
|
1222
|
+
|
|
1223
|
+
@return: An B{C{Xyz}} instance or if C{B{Xyz} is None},
|
|
1224
|
+
a L{Local9Tuple}C{(x, y, z, lat, lon, height,
|
|
1225
|
+
ltp, ecef, M)} with C{M=None}, always.
|
|
1226
|
+
|
|
1227
|
+
@raise TypeError: Invalid B{C{ltp}}.
|
|
1228
|
+
'''
|
|
1229
|
+
return _MODS.ltp._xLtp(ltp)._ecef2local(self, Xyz, Xyz_kwds)
|
|
1230
|
+
|
|
1231
|
+
def toVector(self, Vector=None, **Vector_kwds):
|
|
1232
|
+
'''Return the geocentric C{(x, y, z)} coordinates as vector.
|
|
1233
|
+
|
|
1234
|
+
@kwarg Vector: Optional vector class to return C{(x, y, z)} or
|
|
1235
|
+
C{None}.
|
|
1236
|
+
@kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
|
|
1237
|
+
arguments, ignored if C{B{Vector} is None}.
|
|
1238
|
+
|
|
1239
|
+
@return: A C{Vector}C{(x, y, z, **Vector_kwds)} instance or a
|
|
1240
|
+
L{Vector3Tuple}C{(x, y, z)} if B{C{Vector}} is C{None}.
|
|
1241
|
+
|
|
1242
|
+
@see: Propertes C{xyz} and C{xyzh}
|
|
1243
|
+
'''
|
|
1244
|
+
return self.xyz if Vector is None else Vector(
|
|
1245
|
+
*self.xyz, **_xkwds(Vector_kwds, name=self.name)) # PYCHOK Ecef9Tuple
|
|
1246
|
+
|
|
1247
|
+
# def _T_x_M(self, T):
|
|
1248
|
+
# '''(INTERNAL) Update M{self.M = T.multiply(self.M)}.
|
|
1249
|
+
# '''
|
|
1250
|
+
# return self.dup(M=T.multiply(self.M))
|
|
1251
|
+
|
|
1252
|
+
@Property_RO
|
|
1253
|
+
def xyz(self):
|
|
1254
|
+
'''Get the geocentric C{(x, y, z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
|
|
1255
|
+
'''
|
|
1256
|
+
return Vector3Tuple(self.x, self.y, self.z, name=self.name)
|
|
1257
|
+
|
|
1258
|
+
@Property_RO
|
|
1259
|
+
def xyzh(self):
|
|
1260
|
+
'''Get the geocentric C{(x, y, z)} coordinates and C{height} (L{Vector4Tuple}C{(x, y, z, h)})
|
|
1261
|
+
'''
|
|
1262
|
+
return self.xyz.to4Tuple(self.height)
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
def _4Ecef(this, Ecef): # in .datums.Datum.ecef, .ellipsoids.Ellipsoid.ecef
|
|
1266
|
+
'''Return an ECEF converter for C{this} L{Datum} or L{Ellipsoid}.
|
|
1267
|
+
'''
|
|
1268
|
+
if Ecef is None:
|
|
1269
|
+
Ecef = EcefKarney
|
|
1270
|
+
else:
|
|
1271
|
+
_xinstanceof(*_Ecefs, Ecef=Ecef)
|
|
1272
|
+
return Ecef(this, name=this.name)
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
def _llhn4(latlonh, lon, height, suffix=NN, Error=EcefError, name=NN): # in .ltp
|
|
1276
|
+
'''(INTERNAL) Get a C{(lat, lon, h, name)} 4-tuple.
|
|
1277
|
+
'''
|
|
1278
|
+
try:
|
|
1279
|
+
lat, lon = latlonh.lat, latlonh.lon
|
|
1280
|
+
h = _xattr(latlonh, height=_xattr(latlonh, h=height))
|
|
1281
|
+
n = _xattr(latlonh, name=NN)
|
|
1282
|
+
except AttributeError:
|
|
1283
|
+
lat, h, n = latlonh, height, NN
|
|
1284
|
+
|
|
1285
|
+
try:
|
|
1286
|
+
llhn = Lat(lat), Lon(lon), Height(h), (name or n)
|
|
1287
|
+
except (TypeError, ValueError) as x:
|
|
1288
|
+
t = _lat_, _lon_, _height_
|
|
1289
|
+
if suffix:
|
|
1290
|
+
t = (_ + suffix for _ in t)
|
|
1291
|
+
d = dict(zip(t, (lat, lon, h)))
|
|
1292
|
+
raise Error(cause=x, **d)
|
|
1293
|
+
return llhn
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
def _xEcef(Ecef): # PYCHOK .latlonBase
|
|
1297
|
+
'''(INTERNAL) Validate B{C{Ecef}} I{class}.
|
|
1298
|
+
'''
|
|
1299
|
+
if issubclassof(Ecef, _EcefBase):
|
|
1300
|
+
return Ecef
|
|
1301
|
+
raise _TypesError(_Ecef_, Ecef, *_Ecefs)
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
# kwd lon00 unused but will throw a TypeError if misspelled, etc.
|
|
1305
|
+
def _xyzn4(xyz, y, z, Types, Error=EcefError, name=NN, # PYCHOK unused
|
|
1306
|
+
_xyz_y_z_names=_xyz_y_z, lon00=0): # in .ltp
|
|
1307
|
+
'''(INTERNAL) Get an C{(x, y, z, name)} 4-tuple.
|
|
1308
|
+
'''
|
|
1309
|
+
try:
|
|
1310
|
+
try:
|
|
1311
|
+
t = xyz.x, xyz.y, xyz.z, _xattr(xyz, name=name)
|
|
1312
|
+
if not isinstance(xyz, Types):
|
|
1313
|
+
raise _TypesError(_xyz_y_z_names[0], xyz, *Types)
|
|
1314
|
+
except AttributeError:
|
|
1315
|
+
t = map1(float, xyz, y, z) + (name,)
|
|
1316
|
+
|
|
1317
|
+
except (TypeError, ValueError) as x:
|
|
1318
|
+
d = dict(zip(_xyz_y_z_names, (xyz, y, z)))
|
|
1319
|
+
raise Error(cause=x, **d)
|
|
1320
|
+
return t
|
|
1321
|
+
# assert _xyz_y_z == _MODS.basics._args_kwds_names(_xyzn4)[:3]
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
_Ecefs = (EcefKarney, EcefSudano, EcefVeness, EcefYou,
|
|
1325
|
+
EcefFarrell21, EcefFarrell22)
|
|
1326
|
+
__all__ += _ALL_DOCS(_EcefBase)
|
|
1327
|
+
|
|
1328
|
+
# **) MIT License
|
|
1329
|
+
#
|
|
1330
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1331
|
+
#
|
|
1332
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1333
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1334
|
+
# to deal in the Software without restriction, including without limitation
|
|
1335
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1336
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1337
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1338
|
+
#
|
|
1339
|
+
# The above copyright notice and this permission notice shall be included
|
|
1340
|
+
# in all copies or substantial portions of the Software.
|
|
1341
|
+
#
|
|
1342
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1343
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1344
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1345
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1346
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1347
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1348
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|