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.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. 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.