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
@@ -0,0 +1,1062 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) Private C{CartesianBase} class for elliposiodal, spherical and N-/vectorial
5
+ C{Cartesian}s and public functions L{rtp2xyz}, L{rtp2xyz_}, L{xyz2rtp} and L{xyz2rtp_}.
6
+
7
+ After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**, see
8
+ U{https://www.Movable-Type.co.UK/scripts/latlong.html},
9
+ U{https://www.Movable-Type.co.UK/scripts/latlong-vectors.html} and
10
+ U{https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html}.
11
+ '''
12
+
13
+ # from pygeodesy.basics import _xinstanceof # from .datums
14
+ from pygeodesy.constants import EPS, EPS0, INT0, PI2, _isfinite, isnear0, \
15
+ _0_0, _1_0, _N_1_0, _2_0, _4_0, _6_0
16
+ from pygeodesy.datums import Datum, _earth_ellipsoid, _spherical_datum, \
17
+ Transform, _WGS84, _xinstanceof
18
+ # from pygeodesy.ecef import EcefKarney # _MODS
19
+ from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, \
20
+ _xdatum, _xkwds
21
+ from pygeodesy.fmath import cbrt, hypot, hypot_, hypot2, fabs, sqrt # hypot
22
+ # from pygeodesy.formy import _hartzell # _MODS
23
+ from pygeodesy.fsums import fsumf_, Fmt
24
+ from pygeodesy.interns import NN, _COMMASPACE_, _phi_ # _not_
25
+ from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!
26
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
27
+ from pygeodesy.named import _NamedTuple, _Pass
28
+ from pygeodesy.namedTuples import LatLon4Tuple, Vector3Tuple, Vector4Tuple, \
29
+ Bearing2Tuple # PYCHOK .sphericalBase
30
+ # from pygeodesy.nvectorBase import _N_vector # _MODS
31
+ from pygeodesy.props import deprecated_method, Property, Property_RO, \
32
+ property_doc_, property_RO, _update_all
33
+ # from pygeodesy.resections import cassini, collins5, pierlot, tienstra7
34
+ # from pygeodesy.streprs import Fmt # from .fsums
35
+ # from pygeodesy.triaxials import Triaxial_ # _MODS
36
+ from pygeodesy.units import Degrees, Height, _heigHt, _isMeter, Meter, \
37
+ Radians, _toDegrees, _toRadians
38
+ from pygeodesy.utily import acos1, sincos2d, sincos2_, atan2, degrees, radians
39
+ from pygeodesy.vector3d import Vector3d, _xyzhdn3
40
+ # from pygeodesy.vector3dBase import _xyz3 # _MODS
41
+ # from pygeodesy import ltp, resections # _MODS
42
+
43
+ # from math import atan2, degrees, fabs, radians, sqrt # from .fmath, .utily
44
+
45
+ __all__ = _ALL_LAZY.cartesianBase
46
+ __version__ = '24.02.22'
47
+
48
+ _r_ = 'r'
49
+ _theta_ = 'theta'
50
+
51
+
52
+ class CartesianBase(Vector3d):
53
+ '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.
54
+ '''
55
+ _datum = None # L{Datum}, to be overriden
56
+ _height = None # height (L{Height}), set or approximated
57
+
58
+ def __init__(self, x_xyz, y=None, z=None, datum=None, ll=None, name=NN):
59
+ '''New C{Cartesian...}.
60
+
61
+ @arg x_xyz: Cartesian X coordinate (C{scalar}) or a C{Cartesian},
62
+ L{Ecef9Tuple}, L{Vector3Tuple} or L{Vector4Tuple}.
63
+ @kwarg y: Cartesian Y coordinate (C{scalar}), ignored if B{C{x_xyz}}
64
+ is not C{scalar}, otherwise same units as B{C{x_xyz}}.
65
+ @kwarg z: Cartesian Z coordinate (C{scalar}), ignored if B{C{x_xyz}}
66
+ is not C{scalar}, otherwise same units as B{C{x_xyz}}.
67
+ @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
68
+ or L{a_f2Tuple}).
69
+ @kwarg ll: Optional, original latlon (C{LatLon}).
70
+ @kwarg name: Optional name (C{str}).
71
+
72
+ @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}} coordinate
73
+ or B{C{x_xyz}} not a C{Cartesian}, L{Ecef9Tuple},
74
+ L{Vector3Tuple} or L{Vector4Tuple} or B{C{datum}} is
75
+ not a L{Datum}.
76
+ '''
77
+ h, d, n = _xyzhdn3(x_xyz, None, datum, ll)
78
+ Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=name or n)
79
+ if h is not None:
80
+ self._height = Height(h)
81
+ if d is not None:
82
+ self.datum = d
83
+
84
+ # def __matmul__(self, other): # PYCHOK Python 3.5+
85
+ # '''Return C{NotImplemented} for C{c_ = c @ datum} and C{c_ = c @ transform}.
86
+ # '''
87
+ # return NotImplemented if isinstance(other, (Datum, Transform)) else \
88
+ # _NotImplemented(self, other)
89
+
90
+ def cassini(self, pointB, pointC, alpha, beta, useZ=False):
91
+ '''3-Point resection between this and 2 other points using U{Cassini
92
+ <https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.
93
+
94
+ @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
95
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
96
+ @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
97
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
98
+ @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
99
+ B{C{pointC}} (C{degrees}, non-negative).
100
+ @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
101
+ B{C{pointC}} (C{degrees}, non-negative).
102
+ @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
103
+ force C{z=INT0} (C{bool}).
104
+
105
+ @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
106
+
107
+ @return: The survey point, an instance of this (sub-)class.
108
+
109
+ @raise ResectionError: Near-coincident, -colinear or -concyclic points
110
+ or negative or invalid B{C{alpha}} or B{C{beta}}.
111
+
112
+ @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.
113
+
114
+ @see: Function L{pygeodesy.cassini} for references and more details.
115
+ '''
116
+ return self._resections.cassini(self, pointB, pointC, alpha, beta,
117
+ useZ=useZ, datum=self.datum)
118
+
119
+ @deprecated_method
120
+ def collins(self, pointB, pointC, alpha, beta, useZ=False):
121
+ '''DEPRECATED, use method L{collins5}.'''
122
+ return self.collins5(pointB, pointC, alpha, beta, useZ=useZ)
123
+
124
+ def collins5(self, pointB, pointC, alpha, beta, useZ=False):
125
+ '''3-Point resection between this and 2 other points using U{Collins<https://Dokumen.tips/
126
+ documents/three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.
127
+
128
+ @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
129
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
130
+ @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
131
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
132
+ @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
133
+ B{C{pointC}} (C{degrees}, non-negative).
134
+ @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
135
+ B{C{pointC}} (C{degrees}, non-negative).
136
+ @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
137
+ force C{z=INT0} (C{bool}).
138
+
139
+ @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
140
+
141
+ @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},
142
+ auxiliary C{pointH}, each an instance of this (sub-)class and
143
+ triangle sides C{a}, C{b} and C{c}.
144
+
145
+ @raise ResectionError: Near-coincident, -colinear or -concyclic points
146
+ or negative or invalid B{C{alpha}} or B{C{beta}}.
147
+
148
+ @raise TypeError: Invalid B{C{pointB}} or B{C{pointM}}.
149
+
150
+ @see: Function L{pygeodesy.collins5} for references and more details.
151
+ '''
152
+ return self._resections.collins5(self, pointB, pointC, alpha, beta,
153
+ useZ=useZ, datum=self.datum)
154
+
155
+ @deprecated_method
156
+ def convertDatum(self, datum2, **datum):
157
+ '''DEPRECATED, use method L{toDatum}.'''
158
+ return self.toDatum(datum2, **datum)
159
+
160
+ @property_doc_(''' this cartesian's datum (L{Datum}).''')
161
+ def datum(self):
162
+ '''Get this cartesian's datum (L{Datum}).
163
+ '''
164
+ return self._datum
165
+
166
+ @datum.setter # PYCHOK setter!
167
+ def datum(self, datum):
168
+ '''Set this cartesian's C{datum} I{without conversion}
169
+ (L{Datum}), ellipsoidal or spherical.
170
+
171
+ @raise TypeError: The B{C{datum}} is not a L{Datum}.
172
+ '''
173
+ d = _spherical_datum(datum, name=self.name)
174
+ if self._datum: # is not None
175
+ if d.isEllipsoidal and not self._datum.isEllipsoidal:
176
+ raise _IsnotError(_ellipsoidal_, datum=datum)
177
+ elif d.isSpherical and not self._datum.isSpherical:
178
+ raise _IsnotError(_spherical_, datum=datum)
179
+ if self._datum != d:
180
+ _update_all(self)
181
+ self._datum = d
182
+
183
+ def destinationXyz(self, delta, Cartesian=None, **Cartesian_kwds):
184
+ '''Calculate the destination using a I{local} delta from this cartesian.
185
+
186
+ @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu},
187
+ L{Ned} or L{Local9Tuple}).
188
+ @kwarg Cartesian: Optional (geocentric) class to return the
189
+ destination or C{None}.
190
+ @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
191
+ arguments, ignored if C{B{Cartesian} is None}.
192
+
193
+ @return: Destination as a C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})}
194
+ instance or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,
195
+ z, lat, lon, height, C, M, datum)} with C{M=None} always.
196
+
197
+ @raise TypeError: Invalid B{C{delta}}, B{C{Cartesian}} or
198
+ B{C{Cartesian_kwds}}.
199
+ '''
200
+ if Cartesian is None:
201
+ r = self._Ltp._local2ecef(delta, nine=True)
202
+ else:
203
+ r = self._Ltp._local2ecef(delta, nine=False)
204
+ r = Cartesian(*r, **_xkwds(Cartesian_kwds, datum=self.datum))
205
+ return r._xnamed(r) if self.name else r
206
+
207
+ @property_RO
208
+ def Ecef(self):
209
+ '''Get the ECEF I{class} (L{EcefKarney}), I{once}.
210
+ '''
211
+ CartesianBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO
212
+ return E
213
+
214
+ @Property_RO
215
+ def _ecef9(self):
216
+ '''(INTERNAL) Helper for L{toEcef}, L{toLocal} and L{toLtp} (L{Ecef9Tuple}).
217
+ '''
218
+ return self.Ecef(self.datum, name=self.name).reverse(self, M=True)
219
+
220
+ @property_RO
221
+ def ellipsoidalCartesian(self):
222
+ '''Get the C{Cartesian type} iff ellipsoidal, overloaded in L{CartesianEllipsoidalBase}.
223
+ '''
224
+ return False
225
+
226
+ def hartzell(self, los=False, earth=None):
227
+ '''Compute the intersection of a Line-Of-Sight from this cartesian Point-Of-View
228
+ (pov) and this cartesian's ellipsoid surface.
229
+
230
+ @kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}),
231
+ C{True} for the I{normal, plumb} onto the surface or I{False} or
232
+ C{None} to point to the center of the ellipsoid.
233
+ @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}
234
+ or C{scalar} radius in C{meter}), overriding this cartesian's
235
+ C{datum} ellipsoid.
236
+
237
+ @return: The intersection (C{Cartesian}) with C{.height} set to the distance to
238
+ this C{pov}.
239
+
240
+ @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside
241
+ the ellipsoid or B{C{los}} points outside or away from
242
+ the ellipsoid.
243
+
244
+ @raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}.
245
+
246
+ @see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details.
247
+ '''
248
+ return _MODS.formy._hartzell(self, los, earth)
249
+
250
+ @Property
251
+ def height(self):
252
+ '''Get the height (C{meter}).
253
+ '''
254
+ return self._height4.h if self._height is None else self._height
255
+
256
+ @height.setter # PYCHOK setter!
257
+ def height(self, height):
258
+ '''Set the height (C{meter}).
259
+
260
+ @raise TypeError: Invalid B{C{height}} C{type}.
261
+
262
+ @raise ValueError: Invalid B{C{height}}.
263
+ '''
264
+ h = Height(height)
265
+ if self._height != h:
266
+ _update_all(self)
267
+ self._height = h
268
+
269
+ def _height2C(self, r, Cartesian=None, datum=None, height=INT0, **kwds):
270
+ '''(INTERNAL) Helper for methods C{.height3} and C{.height4}.
271
+ '''
272
+ if Cartesian is not None:
273
+ r = Cartesian(r, **kwds)
274
+ if datum is not None:
275
+ r.datum = datum
276
+ if height is not None:
277
+ r.height = height # Height(height)
278
+ return r
279
+
280
+ def height3(self, earth=None, height=None, **Cartesian_and_kwds):
281
+ '''Compute the cartesian at a height above or below this certesian's ellipsoid.
282
+
283
+ @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
284
+ I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},
285
+ L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).
286
+ @kwarg height: The height (C{meter}, conventionally), overriding this
287
+ cartesian's height.
288
+ @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return
289
+ the cartesian I{at height} and additional B{C{Cartesian}}
290
+ keyword arguments.
291
+
292
+ @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None},
293
+ a L{Vector3Tuple}C{(x, y, z)} with the C{x}, C{y} and C{z}
294
+ coordinates I{at height} in C{meter}, conventionally.
295
+
296
+ @note: This cartesian's coordinates are returned if B{C{earth}} and this
297
+ datum or B{C{heigth}} and/or this height are C{None} or undefined.
298
+
299
+ @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}
300
+ does not accept a B{C{datum}} keyword agument.
301
+
302
+ @raise TriaxialError: No convergence in triaxial root finding.
303
+
304
+ @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
305
+ '''
306
+ n = self.height3.__name__
307
+ d = self.datum if earth is None else _spherical_datum(earth, name=n)
308
+ c, h = self, _heigHt(self, height)
309
+ if h and d:
310
+ R, r = self.Roc2(earth=d)
311
+ if R > EPS0:
312
+ R = (R + h) / R
313
+ r = ((r + h) / r) if r > EPS0 else _1_0
314
+ c = c.times_(R, R, r)
315
+
316
+ r = Vector3Tuple(c.x, c.y, c.z, name=n)
317
+ if Cartesian_and_kwds:
318
+ r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))
319
+ return r
320
+
321
+ @Property_RO
322
+ def _height4(self):
323
+ '''(INTERNAL) Get this C{height4}-tuple.
324
+ '''
325
+ try:
326
+ r = self.datum.ellipsoid.height4(self, normal=True)
327
+ except (AttributeError, ValueError): # no datum, null cartesian,
328
+ r = Vector4Tuple(self.x, self.y, self.z, 0, name=self.height4.__name__)
329
+ return r
330
+
331
+ def height4(self, earth=None, normal=True, **Cartesian_and_kwds):
332
+ '''Compute the projection of this point on and the height above or below
333
+ this datum's ellipsoid surface.
334
+
335
+ @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
336
+ I{overriding} this datum (L{Datum}, L{Ellipsoid},
337
+ L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},
338
+ L{JacobiConformal} or C{meter}, conventionally).
339
+ @kwarg normal: If C{True} the projection is the nearest point on the
340
+ ellipsoid's surface, otherwise the intersection of the
341
+ radial line to the ellipsoid's center and the surface.
342
+ @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return
343
+ the I{projection} and additional B{C{Cartesian}} keyword
344
+ arguments.
345
+
346
+ @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None}, a
347
+ L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}
348
+ and C{z} coordinates and height C{h} in C{meter}, conventionally.
349
+
350
+ @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}
351
+ does not accept a B{C{datum}} keyword agument.
352
+
353
+ @raise TriaxialError: No convergence in triaxial root finding.
354
+
355
+ @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
356
+
357
+ @see: Methods L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.
358
+ '''
359
+ n = self.height4.__name__
360
+ d = self.datum if earth is None else earth
361
+ if normal and d is self.datum:
362
+ r = self._height4
363
+ elif isinstance(d, _MODS.triaxials.Triaxial_):
364
+ r = d.height4(self, normal=normal)
365
+ try:
366
+ d = d.toEllipsoid(name=n)
367
+ except (TypeError, ValueError): # TriaxialError
368
+ d = None
369
+ else:
370
+ r = _earth_ellipsoid(d).height4(self, normal=normal)
371
+
372
+ if Cartesian_and_kwds:
373
+ if d and not isinstance(d, Datum):
374
+ d = _spherical_datum(d, name=n)
375
+ r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))
376
+ return r
377
+
378
+ @Property_RO
379
+ def isEllipsoidal(self):
380
+ '''Check whether this cartesian is ellipsoidal (C{bool} or C{None} if unknown).
381
+ '''
382
+ return self.datum.isEllipsoidal if self._datum else None
383
+
384
+ @Property_RO
385
+ def isSpherical(self):
386
+ '''Check whether this cartesian is spherical (C{bool} or C{None} if unknown).
387
+ '''
388
+ return self.datum.isSpherical if self._datum else None
389
+
390
+ @Property_RO
391
+ def latlon(self):
392
+ '''Get this cartesian's (geodetic) lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
393
+ '''
394
+ return self.toEcef().latlon
395
+
396
+ @Property_RO
397
+ def latlonheight(self):
398
+ '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height (L{LatLon3Tuple}C{(lat, lon, height)}).
399
+ '''
400
+ return self.toEcef().latlonheight
401
+
402
+ @Property_RO
403
+ def latlonheightdatum(self):
404
+ '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
405
+ '''
406
+ return self.toEcef().latlonheightdatum
407
+
408
+ @property_RO
409
+ def _ltp(self):
410
+ '''(INTERNAL) Get module C{.ltp}, I{once}.
411
+ '''
412
+ CartesianBase._ltp = m = _MODS.ltp # overwrite property_RO
413
+ return m
414
+
415
+ @Property_RO
416
+ def _Ltp(self):
417
+ '''(INTERNAL) Cache for L{toLtp}.
418
+ '''
419
+ return self._ltp.Ltp(self._ecef9, ecef=self.Ecef(self.datum), name=self.name)
420
+
421
+ @Property_RO
422
+ def _N_vector(self):
423
+ '''(INTERNAL) Get the (C{nvectorBase._N_vector_}).
424
+ '''
425
+ m = _MODS.nvectorBase
426
+ x, y, z, h = self._n_xyzh4(self.datum)
427
+ return m._N_vector_(x, y, z, h=h, name=self.name)
428
+
429
+ def _n_xyzh4(self, datum):
430
+ '''(INTERNAL) Get the n-vector components as L{Vector4Tuple}.
431
+ '''
432
+ def _ErrorEPS0(x):
433
+ return _ValueError(origin=self, txt=Fmt.PARENSPACED(EPS0=x))
434
+
435
+ _xinstanceof(Datum, datum=datum)
436
+ # <https://www.Movable-Type.co.UK/scripts/geodesy/docs/
437
+ # latlon-nvector-ellipsoidal.js.html#line309>,
438
+ # <https://GitHub.com/pbrod/nvector>/src/nvector/core.py>
439
+ # _equation23 and <https://www.NavLab.net/nvector>
440
+ E = datum.ellipsoid
441
+ x, y, z = self.xyz
442
+
443
+ # Kenneth Gade eqn 23
444
+ p = hypot2(x, y) * E.a2_
445
+ q = z**2 * E.e21 * E.a2_
446
+ r = fsumf_(p, q, -E.e4) / _6_0
447
+ s = (p * q * E.e4) / (_4_0 * r**3)
448
+ t = cbrt(fsumf_(_1_0, s, sqrt(s * (_2_0 + s))))
449
+ if isnear0(t):
450
+ raise _ErrorEPS0(t)
451
+ u = fsumf_(_1_0, t, _1_0 / t) * r
452
+ v = sqrt(u**2 + E.e4 * q)
453
+ t = v * _2_0
454
+ if t < EPS0: # isnear0
455
+ raise _ErrorEPS0(t)
456
+ w = fsumf_(u, v, -q) * E.e2 / t
457
+ k = sqrt(fsumf_(u, v, w**2)) - w
458
+ if isnear0(k):
459
+ raise _ErrorEPS0(k)
460
+ t = k + E.e2
461
+ if isnear0(t):
462
+ raise _ErrorEPS0(t)
463
+ e = k / t
464
+ # d = e * hypot(x, y)
465
+ # tmp = 1 / hypot(d, z) == 1 / hypot(e * hypot(x, y), z)
466
+ t = hypot_(x * e, y * e, z) # == 1 / tmp
467
+ if t < EPS0: # isnear0
468
+ raise _ErrorEPS0(t)
469
+ h = fsumf_(k, E.e2, _N_1_0) / k * t
470
+ s = e / t # == e * tmp
471
+ return Vector4Tuple(x * s, y * s, z / t, h, name=self.name)
472
+
473
+ @Property_RO
474
+ def philam(self):
475
+ '''Get this cartesian's (geodetic) lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
476
+ '''
477
+ return self.toEcef().philam
478
+
479
+ @Property_RO
480
+ def philamheight(self):
481
+ '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height (L{PhiLam3Tuple}C{(phi, lam, height)}).
482
+ '''
483
+ return self.toEcef().philamheight
484
+
485
+ @Property_RO
486
+ def philamheightdatum(self):
487
+ '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
488
+ '''
489
+ return self.toEcef().philamheightdatum
490
+
491
+ def pierlot(self, point2, point3, alpha12, alpha23, useZ=False, eps=EPS):
492
+ '''3-Point resection between this and two other points using U{Pierlot
493
+ <http://www.Telecom.ULg.ac.Be/triangulation>}'s method C{ToTal} with
494
+ I{approximate} limits for the (pseudo-)singularities.
495
+
496
+ @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
497
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
498
+ @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
499
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
500
+ @arg alpha12: Angle subtended from this point to B{C{point2}} or
501
+ B{C{alpha2 - alpha}} (C{degrees}).
502
+ @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or
503
+ B{C{alpha3 - alpha2}} (C{degrees}).
504
+ @kwarg useZ: If C{True}, interpolate the Z component, otherwise use C{z=INT0}
505
+ (C{bool}).
506
+ @kwarg eps: Tolerance for C{cot} (pseudo-)singularities (C{float}).
507
+
508
+ @note: This point, B{C{point2}} and B{C{point3}} are ordered counter-clockwise.
509
+
510
+ @return: The survey (or robot) point, an instance of this (sub-)class.
511
+
512
+ @raise ResectionError: Near-coincident, -colinear or -concyclic points
513
+ or invalid B{C{alpha12}} or B{C{alpha23}}.
514
+
515
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
516
+
517
+ @see: Function L{pygeodesy.pierlot} for references and more details.
518
+ '''
519
+ return self._resections.pierlot(self, point2, point3, alpha12, alpha23,
520
+ useZ=useZ, eps=eps, datum=self.datum)
521
+
522
+ def pierlotx(self, point2, point3, alpha1, alpha2, alpha3, useZ=False):
523
+ '''3-Point resection between this and two other points using U{Pierlot
524
+ <http://www.Telecom.ULg.ac.Be/publi/publications/pierlot/Pierlot2014ANewThree>}'s
525
+ method C{ToTal} with I{exact} limits for the (pseudo-)singularities.
526
+
527
+ @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
528
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
529
+ @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
530
+ C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
531
+ @arg alpha1: Angle at B{C{point1}} (C{degrees}).
532
+ @arg alpha2: Angle at B{C{point2}} (C{degrees}).
533
+ @arg alpha3: Angle at B{C{point3}} (C{degrees}).
534
+ @kwarg useZ: If C{True}, interpolate the survey point's Z component,
535
+ otherwise use C{z=INT0} (C{bool}).
536
+
537
+ @return: The survey (or robot) point, an instance of this (sub-)class.
538
+
539
+ @raise ResectionError: Near-coincident, -colinear or -concyclic points or
540
+ invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.
541
+
542
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
543
+
544
+ @see: Function L{pygeodesy.pierlotx} for references and more details.
545
+ '''
546
+ return self._resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3,
547
+ useZ=useZ, datum=self.datum)
548
+
549
+ @property_RO
550
+ def _resections(self):
551
+ '''(INTERNAL) Import the C{.resections} module, I{once}.
552
+ '''
553
+ CartesianBase._resections = m = _MODS.resections # overwrite property_RO
554
+ return m
555
+
556
+ def Roc2(self, earth=None):
557
+ '''Compute this cartesian's I{normal} and I{pseudo, z-based} radius of curvature.
558
+
559
+ @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
560
+ I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},
561
+ L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).
562
+
563
+ @return: 2-Tuple C{(R, r)} with the I{normal} and I{pseudo, z-based} radius of
564
+ curvature C{R} respectively C{r}, both in C{meter} conventionally.
565
+
566
+ @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
567
+ '''
568
+ r = z = fabs( self.z)
569
+ R, _0 = hypot(self.x, self.y), EPS0
570
+ if R < _0: # polar
571
+ R = z
572
+ elif z > _0: # non-equatorial
573
+ d = self.datum if earth is None else _spherical_datum(earth)
574
+ e = self.toLatLon(datum=d, height=0, LatLon=None) # Ecef9Tuple
575
+ M = e.M # EcefMatrix
576
+ sa, ca = map(fabs, (M._2_2_, M._2_1_) if M else sincos2d(e.lat))
577
+ if ca < _0: # polar
578
+ R = z
579
+ else: # prime-vertical, normal roc R
580
+ R = R / ca # /= chokes PyChecker
581
+ r = R if sa < _0 else (r / sa) # non-/equatorial
582
+ return R, r
583
+
584
+ @property_RO
585
+ def sphericalCartesian(self):
586
+ '''Get the C{Cartesian type} iff spherical, overloaded in L{CartesianSphericalBase}.
587
+ '''
588
+ return False
589
+
590
+ @deprecated_method
591
+ def tienstra(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
592
+ '''DEPRECATED, use method L{tienstra7}.'''
593
+ return self.tienstra7(pointB, pointC, alpha, beta=beta, gamma=gamma, useZ=useZ)
594
+
595
+ def tienstra7(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
596
+ '''3-Point resection between this and two other points using U{Tienstra
597
+ <https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.
598
+
599
+ @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
600
+ C{Vector2Tuple} if C{B{useZ}=False}).
601
+ @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
602
+ C{Vector2Tuple} if C{B{useZ}=False}).
603
+ @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}} (C{degrees},
604
+ non-negative).
605
+ @kwarg beta: Angle subtended by triangle side C{b} from this to B{C{pointC}} (C{degrees},
606
+ non-negative) or C{None} if C{B{gamma} is not None}.
607
+ @kwarg gamma: Angle subtended by triangle side C{c} from this to B{C{pointB}} (C{degrees},
608
+ non-negative) or C{None} if C{B{beta} is not None}.
609
+ @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}
610
+ (C{bool}).
611
+
612
+ @note: This point, B{C{pointB}} and B{C{pointC}} are ordered clockwise.
613
+
614
+ @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP},
615
+ an instance of this (sub-)class and triangle angle C{A} at this point,
616
+ C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees} and
617
+ triangle sides C{a}, C{b} and C{c}.
618
+
619
+ @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of
620
+ B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or
621
+ negative B{C{alpha}}, B{C{beta}} or B{C{gamma}}.
622
+
623
+ @raise TypeError: Invalid B{C{pointB}} or B{C{pointC}}.
624
+
625
+ @see: Function L{pygeodesy.tienstra7} for references and more details.
626
+ '''
627
+ return self._resections.tienstra7(self, pointB, pointC, alpha, beta, gamma,
628
+ useZ=useZ, datum=self.datum)
629
+
630
+ @deprecated_method
631
+ def to2ab(self): # PYCHOK no cover
632
+ '''DEPRECATED, use property C{philam}.
633
+
634
+ @return: A L{PhiLam2Tuple}C{(phi, lam)}.
635
+ '''
636
+ return self.philam
637
+
638
+ @deprecated_method
639
+ def to2ll(self): # PYCHOK no cover
640
+ '''DEPRECATED, use property C{latlon}.
641
+
642
+ @return: A L{LatLon2Tuple}C{(lat, lon)}.
643
+ '''
644
+ return self.latlon
645
+
646
+ @deprecated_method
647
+ def to3llh(self, datum=None): # PYCHOK no cover
648
+ '''DEPRECATED, use property L{latlonheight} or L{latlonheightdatum}.
649
+
650
+ @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
651
+
652
+ @note: This method returns a B{C{-4Tuple}} I{and not a} C{-3Tuple}
653
+ as its name may suggest.
654
+ '''
655
+ t = self.toLatLon(datum=datum, LatLon=None)
656
+ return LatLon4Tuple(t.lat, t.lon, t.height, t.datum, name=self.name)
657
+
658
+ # def _to3LLh(self, datum, LL, **pairs): # OBSOLETE
659
+ # '''(INTERNAL) Helper for C{subclass.toLatLon} and C{.to3llh}.
660
+ # '''
661
+ # r = self.to3llh(datum) # LatLon3Tuple
662
+ # if LL is not None:
663
+ # r = LL(r.lat, r.lon, height=r.height, datum=datum, name=self.name)
664
+ # for n, v in pairs.items():
665
+ # setattr(r, n, v)
666
+ # return r
667
+
668
+ def toDatum(self, datum2, datum=None):
669
+ '''Convert this cartesian from one datum to an other.
670
+
671
+ @arg datum2: Datum to convert I{to} (L{Datum}).
672
+ @kwarg datum: Datum to convert I{from} (L{Datum}).
673
+
674
+ @return: The converted point (C{Cartesian}).
675
+
676
+ @raise TypeError: B{C{datum2}} or B{C{datum}}
677
+ invalid.
678
+ '''
679
+ _xinstanceof(Datum, datum2=datum2)
680
+
681
+ c = self if datum in (None, self.datum) else \
682
+ self.toDatum(datum)
683
+
684
+ i, d = False, c.datum
685
+ if d == datum2:
686
+ return c.copy() if c is self else c
687
+
688
+ elif datum2.transform.isunity and \
689
+ d.transform.isunity:
690
+ return c.dup(datum=datum2)
691
+
692
+ elif d == _WGS84:
693
+ d = datum2 # convert from WGS84 to datum2
694
+
695
+ elif datum2 == _WGS84:
696
+ i = True # convert to WGS84 by inverse transformation
697
+
698
+ else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first
699
+ c = c.toTransform(d.transform, inverse=True, datum=_WGS84)
700
+ d = datum2
701
+
702
+ return c.toTransform(d.transform, inverse=i, datum=datum2)
703
+
704
+ def toEcef(self):
705
+ '''Convert this cartesian to I{geodetic} (lat-/longitude) coordinates.
706
+
707
+ @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
708
+ C, M, datum)} with C{C} and C{M} if available.
709
+
710
+ @raise EcefError: A C{.datum} or an ECEF issue.
711
+ '''
712
+ return self._ecef9
713
+
714
+ def toLatLon(self, datum=None, height=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.toDatum
715
+ '''Convert this cartesian to a I{geodetic} (lat-/longitude) point.
716
+
717
+ @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
718
+ or L{a_f2Tuple}).
719
+ @kwarg height: Optional height, overriding the converted height
720
+ (C{meter}), iff B{C{LatLon}} is not C{None}.
721
+ @kwarg LatLon: Optional class to return the geodetic point
722
+ (C{LatLon}) or C{None}.
723
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
724
+ arguments, ignored if C{B{LatLon} is None}.
725
+
726
+ @return: The geodetic point (B{C{LatLon}}) or if B{C{LatLon}}
727
+ is C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon,
728
+ height, C, M, datum)} with C{C} and C{M} if available.
729
+
730
+ @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}.
731
+ '''
732
+ d = _spherical_datum(datum or self.datum, name=self.name)
733
+ if d == self.datum:
734
+ r = self.toEcef()
735
+ else:
736
+ c = self.toDatum(d)
737
+ r = c.Ecef(d, name=self.name).reverse(c, M=LatLon is None)
738
+
739
+ if LatLon: # class or .classof
740
+ h = _heigHt(r, height)
741
+ r = LatLon(r.lat, r.lon, datum=r.datum, height=h,
742
+ **_xkwds(LatLon_kwds, name=r.name))
743
+ _xdatum(r.datum, d)
744
+ return r
745
+
746
+ def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):
747
+ '''Convert this I{geocentric} cartesian to I{local} C{X}, C{Y} and C{Z}.
748
+
749
+ @kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z}
750
+ (L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.
751
+ @kwarg ltp: The I{local tangent plane} (LTP) to use,
752
+ overriding this cartesian's LTP (L{Ltp}).
753
+ @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
754
+ arguments, ignored if C{B{Xyz} is None}.
755
+
756
+ @return: An B{C{Xyz}} instance or if C{B{Xyz} is None},
757
+ a L{Local9Tuple}C{(x, y, z, lat, lon, height,
758
+ ltp, ecef, M)} with C{M=None} always.
759
+
760
+ @raise TypeError: Invalid B{C{ltp}}.
761
+ '''
762
+ p = self._ltp._xLtp(ltp, self._Ltp)
763
+ return p._ecef2local(self._ecef9, Xyz, Xyz_kwds)
764
+
765
+ def toLtp(self, Ecef=None):
766
+ '''Return the I{local tangent plane} (LTP) for this cartesian.
767
+
768
+ @kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ...
769
+ L{EcefYou}), overriding this cartesian's C{Ecef}.
770
+ '''
771
+ return self._Ltp if Ecef in (None, self.Ecef) else self._ltp.Ltp(
772
+ self._ecef9, ecef=Ecef(self.datum), name=self.name)
773
+
774
+ def toNvector(self, Nvector=None, datum=None, **Nvector_kwds):
775
+ '''Convert this cartesian to C{n-vector} components, I{including height}.
776
+
777
+ @kwarg Nvector: Optional class to return the C{n-vector} components
778
+ (C{Nvector}) or C{None}.
779
+ @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
780
+ or L{a_f2Tuple}) overriding this cartesian's datum.
781
+ @kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword
782
+ arguments, ignored if C{B{Nvector} is None}.
783
+
784
+ @return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if
785
+ B{C{Nvector}} is C{None}.
786
+
787
+ @raise TypeError: Invalid B{C{Nvector}}, B{C{Nvector_kwds}} or
788
+ B{C{datum}}.
789
+
790
+ @raise ValueError: B{C{Cartesian}} at origin.
791
+ '''
792
+ r, d = self._N_vector.xyzh, self.datum
793
+ if datum is not None:
794
+ d = _spherical_datum(datum, name=self.name)
795
+ if d != self.datum:
796
+ r = self._n_xyzh4(d)
797
+
798
+ if Nvector is not None:
799
+ kwds = _xkwds(Nvector_kwds, h=r.h, datum=d)
800
+ r = self._xnamed(Nvector(r.x, r.y, r.z, **kwds))
801
+ return r
802
+
803
+ def toRtp(self):
804
+ '''Convert this cartesian to I{spherical, polar} coordinates.
805
+
806
+ @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
807
+ and C{phi}, both in L{Degrees}.
808
+
809
+ @see: Function L{xyz2rtp_} and class L{RadiusThetaPhi3Tuple}.
810
+ '''
811
+ return _rtp3(self.toRtp, Degrees, self, name=self.name)
812
+
813
+ def toStr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_): # PYCHOK expected
814
+ '''Return the string representation of this cartesian.
815
+
816
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
817
+ @kwarg fmt: Enclosing backets format (C{letter}).
818
+ @kwarg sep: Separator to join (C{str}).
819
+
820
+ @return: Cartesian represented as "[x, y, z]" (C{str}).
821
+ '''
822
+ return Vector3d.toStr(self, prec=prec, fmt=fmt, sep=sep)
823
+
824
+ def toTransform(self, transform, inverse=False, datum=None):
825
+ '''Apply a Helmert transform to this cartesian.
826
+
827
+ @arg transform: Transform to apply (L{Transform} or L{TransformXform}).
828
+ @kwarg inverse: Apply the inverse of the C{B{transform}} (C{bool}).
829
+ @kwarg datum: Datum for the transformed cartesian (L{Datum}), overriding
830
+ this cartesian's datum but I{not} taken it into account.
831
+
832
+ @return: A transformed cartesian (C{Cartesian}) or a copy of this
833
+ cartesian if C{B{transform}.isunity}.
834
+
835
+ @raise TypeError: Invalid B{C{transform}}.
836
+ '''
837
+ _xinstanceof(Transform, transform=transform)
838
+ if transform.isunity:
839
+ c = self.dup(datum=datum or self.datum)
840
+ else:
841
+ # if inverse and d != _WGS84:
842
+ # raise _ValueError(inverse=inverse, datum=d,
843
+ # txt=_not_(_WGS84.name))
844
+ xyz = transform.transform(*self.xyz, inverse=inverse)
845
+ c = self.dup(xyz=xyz, datum=datum or self.datum)
846
+ return c
847
+
848
+ def toVector(self, Vector=None, **Vector_kwds):
849
+ '''Return this cartesian's I{geocentric} components as vector.
850
+
851
+ @kwarg Vector: Optional class to return the I{geocentric}
852
+ components (L{Vector3d}) or C{None}.
853
+ @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
854
+ arguments, ignored if C{B{Vector} is None}.
855
+
856
+ @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)} if
857
+ B{C{Vector}} is C{None}.
858
+
859
+ @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}}.
860
+ '''
861
+ return self.xyz if Vector is None else self._xnamed(
862
+ Vector(self.x, self.y, self.z, **Vector_kwds))
863
+
864
+
865
+ class RadiusThetaPhi3Tuple(_NamedTuple):
866
+ '''3-Tuple C{(r, theta, phi)} with radial distance C{r} in C{meter}, inclination
867
+ C{theta} (with respect to the positive z-axis) and azimuthal angle C{phi} in
868
+ L{Degrees} I{or} L{Radians} representing a U{spherical, polar position
869
+ <https://WikiPedia.org/wiki/Spherical_coordinate_system>}.
870
+ '''
871
+ _Names_ = (_r_, _theta_, _phi_)
872
+ _Units_ = ( Meter, _Pass, _Pass)
873
+
874
+ def toCartesian(self, name=NN, **Cartesian_and_kwds):
875
+ '''Convert this L{RadiusThetaPhi3Tuple} to a cartesian C{(x, y, z)} vector.
876
+
877
+ @kwarg name: Optional name (C{str}), overriding this name.
878
+ @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class and additional
879
+ C{B{Cartesian}} keyword arguments.
880
+
881
+ @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword
882
+ argument is given, a L{Vector3Tuple}C{(x, y, z)} with C{x}, C{y}
883
+ and C{z} in the same units as radius C{r}, C{meter} conventionally.
884
+
885
+ @see: Function L{rtp2xyz_}.
886
+ '''
887
+ r, t, p = self
888
+ t, p, _ = _toRadians(self, t, p)
889
+ return rtp2xyz_(r, t, p, name=name or self.name, **Cartesian_and_kwds)
890
+
891
+ def toDegrees(self, name=NN):
892
+ '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Degrees}.
893
+
894
+ @kwarg name: Optional name (C{str}), overriding this name.
895
+
896
+ @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
897
+ and C{phi} both in L{Degrees}.
898
+ '''
899
+ r, t, p = self
900
+ t, p, _ = _toDegrees(self, t, p)
901
+ return _ or self.classof(r, Degrees(theta=t), Degrees(phi=p),
902
+ name=name or self.name)
903
+
904
+ def toRadians(self, name=NN):
905
+ '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Radians}.
906
+
907
+ @kwarg name: Optional name (C{str}), overriding this name.
908
+
909
+ @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
910
+ and C{phi} both in L{Radians}.
911
+ '''
912
+ r, t, p = self
913
+ t, p, _ = _toRadians(self, t, p)
914
+ return _ or self.classof(r, Radians(theta=t), Radians(phi=p),
915
+ name=name or self.name)
916
+
917
+
918
+ def rtp2xyz(r_rtp, *theta_phi, **name_Cartesian_and_kwds):
919
+ '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.
920
+
921
+ @arg r_rtp: Radial distance (C{scalar}, conventially C{meter}) or a previous
922
+ L{RadiusThetaPhi3Tuple} instance.
923
+ @arg theta_phi: Inclination B{C{theta}} (C{degrees} with respect to the positive z-axis)
924
+ and azimuthal angle B{C{phi}} (C{degrees}), ignored if C{B{r_rtp}} is a
925
+ L{RadiusThetaPhi3Tuple}.
926
+ @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN} (C{str}), a C{B{Cartesian}=None}
927
+ class to return the coordinates and additional C{B{Cartesian}}
928
+ keyword arguments.
929
+
930
+ @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword argument
931
+ is given a L{Vector3Tuple}C{(x, y, z)}, with C{x}, C{y} and C{z} in the same
932
+ units as radius C{r}, C{meter} conventionally.
933
+
934
+ @raise TypeError: Invalid B{C{r_rtp}}.
935
+
936
+ @see: Functions L{rtp2xyz_} and L{xyz2rtp}.
937
+ '''
938
+ if isinstance(r_rtp, RadiusThetaPhi3Tuple):
939
+ c = r_rtp.toCartesian(**name_Cartesian_and_kwds)
940
+ else:
941
+ c = rtp2xyz_(r_rtp, *map(radians, theta_phi), **name_Cartesian_and_kwds)
942
+ return c
943
+
944
+
945
+ def rtp2xyz_(r_rtp, *theta_phi, **name_Cartesian_and_kwds):
946
+ '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.
947
+
948
+ @arg r_rtp: Radial distance (C{scalar}, conventially C{meter}) or a previous
949
+ L{RadiusThetaPhi3Tuple} instance.
950
+ @arg theta_phi: Inclination B{C{theta}} (C{radians} with respect to the positive z-axis)
951
+ and azimuthal angle B{C{phi}} (C{degrees}), ignored is C{B{r_rtp}} is a
952
+ L{RadiusThetaPhi3Tuple}.
953
+ @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN} (C{str}), a C{B{Cartesian}=None}
954
+ class to return the coordinates and additional C{B{Cartesian}}
955
+ keyword arguments.
956
+
957
+ @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword argument
958
+ is given a L{Vector3Tuple}C{(x, y, z)}, with C{x}, C{y} and C{z} in the same
959
+ units as radius C{r}, C{meter} conventionally.
960
+
961
+ @raise TypeError: Invalid B{C{r_rtp}} or B{C{theta_phi}}.
962
+
963
+ @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}
964
+ (ISO 80000-2:2019).
965
+ '''
966
+ if _isMeter(r_rtp) and len(theta_phi) == 2:
967
+ r = r_rtp
968
+ if r and _isfinite(r):
969
+ s, z, y, x = sincos2_(*theta_phi)
970
+ s *= r
971
+ z *= r
972
+ y *= s
973
+ x *= s
974
+ else:
975
+ x = y = z = r
976
+
977
+ def _n_C_kwds3(name=NN, Cartesian=None, **kwds):
978
+ return name, Cartesian, kwds
979
+
980
+ n, C, kwds = _n_C_kwds3(**name_Cartesian_and_kwds)
981
+ c = Vector3Tuple(x, y, z, name=n) if C is None else \
982
+ C(x, y, z, name=n, **kwds)
983
+
984
+ elif isinstance(r_rtp, RadiusThetaPhi3Tuple):
985
+ c = r_rtp.toCartesian(**name_Cartesian_and_kwds)
986
+ else:
987
+ raise _TypeError(r_rtp=r_rtp, theta_phi=theta_phi)
988
+ return c
989
+
990
+
991
+ def _rtp3(where, Unit, *x_y_z, **name):
992
+ '''(INTERNAL) Helper for C{.toRtp}, C{xyz2rtp} and C{xyz2rtp_}.
993
+ '''
994
+ x, y, z = _MODS.vector3dBase._xyz3(where, *x_y_z)
995
+ r = hypot_(x, y, z)
996
+ if r > 0:
997
+ t = acos1(z / r)
998
+ p = atan2(y, x)
999
+ while p < 0:
1000
+ p += PI2
1001
+ if Unit is Degrees:
1002
+ t = degrees(t)
1003
+ p = degrees(p)
1004
+ else:
1005
+ t = p = _0_0
1006
+ return RadiusThetaPhi3Tuple(r, Unit(theta=t), Unit(phi=p), **name)
1007
+
1008
+
1009
+ def xyz2rtp(x_xyz, *y_z, **name):
1010
+ '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.
1011
+
1012
+ @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} and C{phi},
1013
+ both in L{Degrees}.
1014
+
1015
+ @see: Function L{xyz2rtp_} and class L{RadiusThetaPhi3Tuple}.
1016
+ '''
1017
+ return _rtp3(xyz2rtp, Degrees, x_xyz, *y_z, **name)
1018
+
1019
+
1020
+ def xyz2rtp_(x_xyz, *y_z, **name):
1021
+ '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.
1022
+
1023
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
1024
+ C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} or a
1025
+ C{tuple} or C{list} of 3+ C{scalar} items) if no C{y_z} specified.
1026
+ @arg y_z: Y and Z component (C{scalar}s), ignored if C{x_xyz} is not a C{scalar}.
1027
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
1028
+
1029
+ @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with radial distance C{r}
1030
+ (C{meter}, same units as C{x}, C{y} and C{z}), inclination C{theta}
1031
+ (with respect to the positive z-axis) and azimuthal angle C{phi},
1032
+ both in L{Radians}.
1033
+
1034
+ @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}
1035
+ (ISO 80000-2:2019).
1036
+ '''
1037
+ return _rtp3(xyz2rtp_, Radians, x_xyz, *y_z, **name)
1038
+
1039
+
1040
+ __all__ += _ALL_DOCS(CartesianBase)
1041
+
1042
+ # **) MIT License
1043
+ #
1044
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1045
+ #
1046
+ # Permission is hereby granted, free of charge, to any person obtaining a
1047
+ # copy of this software and associated documentation files (the "Software"),
1048
+ # to deal in the Software without restriction, including without limitation
1049
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1050
+ # and/or sell copies of the Software, and to permit persons to whom the
1051
+ # Software is furnished to do so, subject to the following conditions:
1052
+ #
1053
+ # The above copyright notice and this permission notice shall be included
1054
+ # in all copies or substantial portions of the Software.
1055
+ #
1056
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1057
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1058
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1059
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1060
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1061
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1062
+ # OTHER DEALINGS IN THE SOFTWARE.