Coverage for pygeodesy/cartesianBase.py: 95%
205 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-10-04 12:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-10-04 12:08 -0400
2# -*- coding: utf-8 -*-
4u'''(INTERNAL) Private base classes for elliposiodal, spherical and N-/vectorial
5C{Cartesian}s.
7After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**,
8see U{https://www.Movable-Type.co.UK/scripts/latlong.html},
9U{https://www.Movable-Type.co.UK/scripts/latlong-vectors.html} and
10U{https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html}..
11'''
13# from pygeodesy.basics import _xinstanceof # from .datums
14from pygeodesy.constants import EPS, EPS0, isnear0, _1_0, _N_1_0, \
15 _2_0, _4_0, _6_0
16from pygeodesy.datums import Datum, _spherical_datum, _WGS84, _xinstanceof
17from pygeodesy.errors import _IsnotError, _ValueError, _xdatum, _xkwds
18from pygeodesy.fmath import cbrt, hypot_, hypot2, sqrt # hypot
19from pygeodesy.fsums import Fmt, fsumf_
20from pygeodesy.interns import NN, _COMMASPACE_, _height_, _not_
21from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!
22from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
23from pygeodesy.namedTuples import LatLon4Tuple, Vector4Tuple, \
24 Bearing2Tuple # PYCHOK .sphericalBase
25from pygeodesy.props import deprecated_method, Property, Property_RO, \
26 property_doc_, _update_all
27# from pygeodesy.resections impoty cassini, collins5, pierlot, tienstra7
28# from pygeodesy.streprs import Fmt # from .fsums
29from pygeodesy.units import Height, _heigHt
30from pygeodesy.vector3d import Vector3d, _xyzhdn3
32# from math import sqrt # from .fmath
34__all__ = _ALL_LAZY.cartesianBase
35__version__ = '23.10.04'
38class CartesianBase(Vector3d):
39 '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.
40 '''
41 _datum = None # L{Datum}, to be overriden
42 _height = None # height (L{Height}), set or approximated
44 def __init__(self, x_xyz, y=None, z=None, datum=None, ll=None, name=NN):
45 '''New C{Cartesian...}.
47 @arg x_xyz: Cartesian X coordinate (C{scalar}) or a C{Cartesian},
48 L{Ecef9Tuple}, L{Vector3Tuple} or L{Vector4Tuple}.
49 @kwarg y: Cartesian Y coordinate (C{scalar}), ignored if B{C{x_xyz}}
50 is not C{scalar}, otherwise same units as B{C{x_xyz}}.
51 @kwarg z: Cartesian Z coordinate (C{scalar}), ignored if B{C{x_xyz}}
52 is not C{scalar}, otherwise same units as B{C{x_xyz}}.
53 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
54 or L{a_f2Tuple}).
55 @kwarg ll: Optional, original latlon (C{LatLon}).
56 @kwarg name: Optional name (C{str}).
58 @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}
59 coordinate or B{C{x_xyz}} not an L{Ecef9Tuple},
60 L{Vector3Tuple} or L{Vector4Tuple}.
61 '''
62 h, d, n = _xyzhdn3(x_xyz, None, datum, ll)
63 Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=name or n)
64 if h is not None:
65 self._height = Height(h)
66 if d is not None:
67 self.datum = d
69# def __matmul__(self, other): # PYCHOK Python 3.5+
70# '''Return C{NotImplemented} for C{c_ = c @ datum} and C{c_ = c @ transform}.
71# '''
72# return NotImplemented if isinstance(other, (Datum, Transform)) else \
73# _NotImplemented(self, other)
75 def cassini(self, pointB, pointC, alpha, beta, useZ=False):
76 '''3-Point resection between this and 2 other points using U{Cassini
77 <https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.
79 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
80 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
81 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
82 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
83 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
84 B{C{pointC}} (C{degrees}, non-negative).
85 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
86 B{C{pointC}} (C{degrees}, non-negative).
87 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
88 force C{z=INT0} (C{bool}).
90 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
92 @return: The survey point, an instance of this (sub-)class.
94 @raise ResectionError: Near-coincident, -colinear or -concyclic points
95 or negative or invalid B{C{alpha}} or B{C{beta}}.
97 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.
99 @see: Function L{pygeodesy.cassini} for references and more details.
100 '''
101 return _MODS.resections.cassini(self, pointB, pointC, alpha, beta,
102 useZ=useZ, datum=self.datum)
104 @deprecated_method
105 def collins(self, pointB, pointC, alpha, beta, useZ=False):
106 '''DEPRECATED, use method L{collins5}.'''
107 return self.collins5(pointB, pointC, alpha, beta, useZ=useZ)
109 def collins5(self, pointB, pointC, alpha, beta, useZ=False):
110 '''3-Point resection between this and 2 other points using U{Collins<https://Dokumen.tips/
111 documents/three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.
113 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
114 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
115 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
116 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
117 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
118 B{C{pointC}} (C{degrees}, non-negative).
119 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
120 B{C{pointC}} (C{degrees}, non-negative).
121 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
122 force C{z=INT0} (C{bool}).
124 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
126 @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},
127 auxiliary C{pointH}, each an instance of this (sub-)class and
128 triangle sides C{a}, C{b} and C{c}.
130 @raise ResectionError: Near-coincident, -colinear or -concyclic points
131 or negative or invalid B{C{alpha}} or B{C{beta}}.
133 @raise TypeError: Invalid B{C{pointB}} or B{C{pointM}}.
135 @see: Function L{pygeodesy.collins5} for references and more details.
136 '''
137 return _MODS.resections.collins5(self, pointB, pointC, alpha, beta,
138 useZ=useZ, datum=self.datum)
140 @property_doc_(''' this cartesian's datum (L{Datum}).''')
141 def datum(self):
142 '''Get this cartesian's datum (L{Datum}).
143 '''
144 return self._datum
146 @datum.setter # PYCHOK setter!
147 def datum(self, datum):
148 '''Set this cartesian's C{datum} I{without conversion}
149 (L{Datum}), ellipsoidal or spherical.
151 @raise TypeError: The B{C{datum}} is not a L{Datum}.
152 '''
153 d = _spherical_datum(datum, name=self.name)
154 if self._datum: # is not None
155 if self._datum.isEllipsoidal and not d.isEllipsoidal:
156 raise _IsnotError(_ellipsoidal_, datum=datum)
157 elif self._datum.isSpherical and not d.isSpherical:
158 raise _IsnotError(_spherical_, datum=datum)
159 if self._datum != d:
160 _update_all(self)
161 self._datum = d
163 def destinationXyz(self, delta, Cartesian=None, **Cartesian_kwds):
164 '''Calculate the destination using a I{local} delta from this cartesian.
166 @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu},
167 L{Ned} or L{Local9Tuple}).
168 @kwarg Cartesian: Optional (geocentric) class to return the
169 destination or C{None}.
170 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
171 arguments, ignored if C{B{Cartesian} is None}.
173 @return: Destination as a C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})}
174 instance or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,
175 z, lat, lon, height, C, M, datum)} with C{M=None} always.
177 @raise TypeError: Invalid B{C{delta}}, B{C{Cartesian}} or
178 B{C{Cartesian_kwds}}.
179 '''
180 if Cartesian is None:
181 r = self._ltp._local2ecef(delta, nine=True)
182 else:
183 r = self._ltp._local2ecef(delta, nine=False)
184 r = Cartesian(*r, **_xkwds(Cartesian_kwds, datum=self.datum))
185 return r._xnamed(r) if self.name else r
187 @Property_RO
188 def Ecef(self):
189 '''Get the ECEF I{class} (L{EcefKarney}), I{lazily}.
190 '''
191 return _MODS.ecef.EcefKarney # default
193 @Property_RO
194 def _ecef9(self):
195 '''(INTERNAL) Helper for L{toEcef}, L{toLocal} and L{toLtp} (L{Ecef9Tuple}).
196 '''
197 return self.Ecef(self.datum, name=self.name).reverse(self, M=True)
199 def hartzell(self, los=None, earth=None):
200 '''Compute the intersection of a Line-Of-Sight (los) from this certesian
201 Point-Of-View (pov) with this cartesian's ellipsoid surface.
203 @kwarg los: Line-Of-Sight, I{direction} to earth (L{Los}, L{Vector3d})
204 or C{None} to point to the ellipsoid's center.
205 @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2},
206 L{a_f2Tuple} or C{scalar} radius in C{meter}) overriding
207 this cartesian's C{datum} ellipsoid.
209 @return: The ellipsoid intersection (C{Cartesian}) with C{.height} set
210 to the distance to this C{pov}.
212 @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov}
213 is inside the ellipsoid or B{C{los}} points
214 points outside or away from the ellipsoid.
216 @raise TypeError: Invalid B{C{los}} or no B{C{datum}}.
218 @see: Function C{hartzell} for further details.
219 '''
220 return _MODS.formy._hartzell(self, los, earth)
222 @Property
223 def height(self):
224 '''Get the height (C{meter}).
225 '''
226 return self._height4.h if self._height is None else self._height
228 @height.setter # PYCHOK setter!
229 def height(self, height):
230 '''Set the height (C{meter}).
232 @raise TypeError: Invalid B{C{height}} C{type}.
234 @raise ValueError: Invalid B{C{height}}.
235 '''
236 h = Height(height)
237 if self._height != h:
238 _update_all(self)
239 self._height = h
241 @Property_RO
242 def _height4(self):
243 '''(INTERNAL) Get this C{height4}-tuple.
244 '''
245 try:
246 r = self.datum.ellipsoid.height4(self, normal=True)
247 except (AttributeError, ValueError): # no datum, null cartesian,
248 r = Vector4Tuple(self.x, self.y, self.z, 0, name=self.height4.__name__)
249 return r
251 def height4(self, earth=None, normal=True, Cartesian=None, **Cartesian_kwds):
252 '''Compute the height of this cartesian above or below and the projection
253 on this datum's ellipsoid surface.
255 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius
256 I{overriding} this datum (L{Datum}, L{Ellipsoid},
257 L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},
258 L{JacobiConformal} or C{meter}, conventionally).
259 @kwarg normal: If C{True} the projection is the nearest point on the
260 ellipsoid's surface, otherwise the intersection of the
261 radial line to the center and the ellipsoid's surface.
262 @kwarg Cartesian: Optional class to return the height and projection
263 (C{Cartesian}) or C{None}.
264 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
265 arguments, ignored if C{B{Cartesian} is None}.
267 @note: Use keyword argument C{height=0} to override C{B{Cartesian}.height}
268 to {0} or any other C{scalar}, conventionally in C{meter}.
270 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None}, a
271 L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}
272 and C{z} coordinates and height C{h} in C{meter}, conventionally.
274 @raise TriaxialError: No convergence in triaxial root finding.
276 @raise TypeError: Invalid B{C{earth}}.
278 @see: L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.
279 '''
280 d = self.datum if earth is None else earth
281 if normal and d == self.datum:
282 r = self._height4
283 elif isinstance(d, _MODS.triaxials.Triaxial_):
284 r = d.height4(self, normal=normal)
285 else:
286 r = _spherical_datum(d).ellipsoid.height4(self, normal=normal)
287 if Cartesian is not None:
288 kwds = Cartesian_kwds.copy()
289 h = kwds.pop(_height_, None)
290 r = Cartesian(r, **kwds)
291 if h is not None:
292 r.height = Height(height=h)
293 return r
295 @Property_RO
296 def isEllipsoidal(self):
297 '''Check whether this cartesian is ellipsoidal (C{bool} or C{None} if unknown).
298 '''
299 return self.datum.isEllipsoidal if self._datum else None
301 @Property_RO
302 def isSpherical(self):
303 '''Check whether this cartesian is spherical (C{bool} or C{None} if unknown).
304 '''
305 return self.datum.isSpherical if self._datum else None
307 @Property_RO
308 def latlon(self):
309 '''Get this cartesian's (geodetic) lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
310 '''
311 return self.toEcef().latlon
313 @Property_RO
314 def latlonheight(self):
315 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height (L{LatLon3Tuple}C{(lat, lon, height)}).
316 '''
317 return self.toEcef().latlonheight
319 @Property_RO
320 def latlonheightdatum(self):
321 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
322 '''
323 return self.toEcef().latlonheightdatum
325 @Property_RO
326 def _ltp(self):
327 '''(INTERNAL) Cache for L{toLtp}.
328 '''
329 return _MODS.ltp.Ltp(self._ecef9, ecef=self.Ecef(self.datum), name=self.name)
331 @Property_RO
332 def _N_vector(self):
333 '''(INTERNAL) Get the (C{nvectorBase._N_vector_}).
334 '''
335 x, y, z, h = self._n_xyzh4(self.datum)
336 return _MODS.nvectorBase._N_vector_(x, y, z, h=h, name=self.name)
338 def _n_xyzh4(self, datum):
339 '''(INTERNAL) Get the n-vector components as L{Vector4Tuple}.
340 '''
341 def _ErrorEPS0(x):
342 return _ValueError(origin=self, txt=Fmt.PARENTSPACED(EPS0=x))
344 _xinstanceof(Datum, datum=datum)
345 # <https://www.Movable-Type.co.UK/scripts/geodesy/docs/
346 # latlon-nvector-ellipsoidal.js.html#line309>,
347 # <https://GitHub.com/pbrod/nvector>/src/nvector/core.py>
348 # _equation23 and <https://www.NavLab.net/nvector>
349 E = datum.ellipsoid
350 x, y, z = self.xyz
352 # Kenneth Gade eqn 23
353 p = hypot2(x, y) * E.a2_
354 q = z**2 * E.e21 * E.a2_
355 r = fsumf_(p, q, -E.e4) / _6_0
356 s = (p * q * E.e4) / (_4_0 * r**3)
357 t = cbrt(fsumf_(_1_0, s, sqrt(s * (_2_0 + s))))
358 if isnear0(t):
359 raise _ErrorEPS0(t)
360 u = fsumf_(_1_0, t, _1_0 / t) * r
361 v = sqrt(u**2 + E.e4 * q)
362 t = v * _2_0
363 if t < EPS0: # isnear0
364 raise _ErrorEPS0(t)
365 w = fsumf_(u, v, -q) * E.e2 / t
366 k = sqrt(fsumf_(u, v, w**2)) - w
367 if isnear0(k):
368 raise _ErrorEPS0(k)
369 t = k + E.e2
370 if isnear0(t):
371 raise _ErrorEPS0(t)
372 e = k / t
373# d = e * hypot(x, y)
374# tmp = 1 / hypot(d, z) == 1 / hypot(e * hypot(x, y), z)
375 t = hypot_(x * e, y * e, z) # == 1 / tmp
376 if t < EPS0: # isnear0
377 raise _ErrorEPS0(t)
378 h = fsumf_(k, E.e2, _N_1_0) / k * t
379 s = e / t # == e * tmp
380 return Vector4Tuple(x * s, y * s, z / t, h, name=self.name)
382 @Property_RO
383 def philam(self):
384 '''Get this cartesian's (geodetic) lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
385 '''
386 return self.toEcef().philam
388 @Property_RO
389 def philamheight(self):
390 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height (L{PhiLam3Tuple}C{(phi, lam, height)}).
391 '''
392 return self.toEcef().philamheight
394 @Property_RO
395 def philamheightdatum(self):
396 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
397 '''
398 return self.toEcef().philamheightdatum
400 def pierlot(self, point2, point3, alpha12, alpha23, useZ=False, eps=EPS):
401 '''3-Point resection between this and two other points using U{Pierlot
402 <http://www.Telecom.ULg.ac.Be/triangulation>}'s method C{ToTal} with
403 I{approximate} limits for the (pseudo-)singularities.
405 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
406 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
407 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
408 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
409 @arg alpha12: Angle subtended from this point to B{C{point2}} or
410 B{C{alpha2 - alpha}} (C{degrees}).
411 @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or
412 B{C{alpha3 - alpha2}} (C{degrees}).
413 @kwarg useZ: If C{True}, interpolate the Z component, otherwise use C{z=INT0}
414 (C{bool}).
415 @kwarg eps: Tolerance for C{cot} (pseudo-)singularities (C{float}).
417 @note: This point, B{C{point2}} and B{C{point3}} are ordered counter-clockwise.
419 @return: The survey (or robot) point, an instance of this (sub-)class.
421 @raise ResectionError: Near-coincident, -colinear or -concyclic points
422 or invalid B{C{alpha12}} or B{C{alpha23}}.
424 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
426 @see: Function L{pygeodesy.pierlot} for references and more details.
427 '''
428 return _MODS.resections.pierlot(self, point2, point3, alpha12, alpha23,
429 useZ=useZ, eps=eps, datum=self.datum)
431 def pierlotx(self, point2, point3, alpha1, alpha2, alpha3, useZ=False):
432 '''3-Point resection between this and two other points using U{Pierlot
433 <http://www.Telecom.ULg.ac.Be/publi/publications/pierlot/Pierlot2014ANewThree>}'s
434 method C{ToTal} with I{exact} limits for the (pseudo-)singularities.
436 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
437 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
438 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
439 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
440 @arg alpha1: Angle at B{C{point1}} (C{degrees}).
441 @arg alpha2: Angle at B{C{point2}} (C{degrees}).
442 @arg alpha3: Angle at B{C{point3}} (C{degrees}).
443 @kwarg useZ: If C{True}, interpolate the survey point's Z component,
444 otherwise use C{z=INT0} (C{bool}).
446 @return: The survey (or robot) point, an instance of this (sub-)class.
448 @raise ResectionError: Near-coincident, -colinear or -concyclic points or
449 invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.
451 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
453 @see: Function L{pygeodesy.pierlotx} for references and more details.
454 '''
455 return _MODS.resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3,
456 useZ=useZ, datum=self.datum)
458 @deprecated_method
459 def tienstra(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
460 '''DEPRECATED, use method L{tienstra7}.'''
461 return self.tienstra7(pointB, pointC, alpha, beta=beta, gamma=gamma, useZ=useZ)
463 def tienstra7(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
464 '''3-Point resection between this and two other points using U{Tienstra
465 <https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.
467 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
468 C{Vector2Tuple} if C{B{useZ}=False}).
469 @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
470 C{Vector2Tuple} if C{B{useZ}=False}).
471 @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}} (C{degrees},
472 non-negative).
473 @kwarg beta: Angle subtended by triangle side C{b} from this to B{C{pointC}} (C{degrees},
474 non-negative) or C{None} if C{B{gamma} is not None}.
475 @kwarg gamma: Angle subtended by triangle side C{c} from this to B{C{pointB}} (C{degrees},
476 non-negative) or C{None} if C{B{beta} is not None}.
477 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}
478 (C{bool}).
480 @note: This point, B{C{pointB}} and B{C{pointC}} are ordered clockwise.
482 @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP},
483 an instance of this (sub-)class and triangle angle C{A} at this point,
484 C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees} and
485 triangle sides C{a}, C{b} and C{c}.
487 @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of
488 B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or
489 negative B{C{alpha}}, B{C{beta}} or B{C{gamma}}.
491 @raise TypeError: Invalid B{C{pointB}} or B{C{pointC}}.
493 @see: Function L{pygeodesy.tienstra7} for references and more details.
494 '''
495 return _MODS.resections.tienstra7(self, pointB, pointC, alpha, beta, gamma,
496 useZ=useZ, datum=self.datum)
498 @deprecated_method
499 def to2ab(self): # PYCHOK no cover
500 '''DEPRECATED, use property C{philam}.
502 @return: A L{PhiLam2Tuple}C{(phi, lam)}.
503 '''
504 return self.philam
506 @deprecated_method
507 def to2ll(self): # PYCHOK no cover
508 '''DEPRECATED, use property C{latlon}.
510 @return: A L{LatLon2Tuple}C{(lat, lon)}.
511 '''
512 return self.latlon
514 @deprecated_method
515 def to3llh(self, datum=None): # PYCHOK no cover
516 '''DEPRECATED, use property L{latlonheightdatum} or L{latlonheight}.
518 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
520 @note: This method returns a B{C{-4Tuple}} I{and not a} C{-3Tuple}
521 as its name may suggest.
522 '''
523 t = self.toLatLon(datum=datum, LatLon=None)
524 return LatLon4Tuple(t.lat, t.lon, t.height, t.datum, name=self.name)
526# def _to3LLh(self, datum, LL, **pairs): # OBSOLETE
527# '''(INTERNAL) Helper for C{subclass.toLatLon} and C{.to3llh}.
528# '''
529# r = self.to3llh(datum) # LatLon3Tuple
530# if LL is not None:
531# r = LL(r.lat, r.lon, height=r.height, datum=datum, name=self.name)
532# for n, v in pairs.items():
533# setattr(r, n, v)
534# return r
536 def toDatum(self, datum2, datum=None):
537 '''Convert this cartesian from one datum to an other.
539 @arg datum2: Datum to convert I{to} (L{Datum}).
540 @kwarg datum: Datum to convert I{from} (L{Datum}).
542 @return: The converted point (C{Cartesian}).
544 @raise TypeError: B{C{datum2}} or B{C{datum}}
545 invalid.
546 '''
547 _xinstanceof(Datum, datum2=datum2)
549 c = self if datum in (None, self.datum) else \
550 self.toDatum(datum)
552 i, d = False, c.datum
553 if d == datum2:
554 return c.copy() if c is self else c
556 elif d == _WGS84:
557 d = datum2 # convert from WGS84 to datum2
559 elif datum2 == _WGS84:
560 i = True # convert to WGS84 by inverse transformation
562 else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first
563 c = c.toTransform(d.transform, inverse=True, datum=_WGS84)
564 d = datum2
566 return c.toTransform(d.transform, inverse=i, datum=datum2)
568 convertDatum = toDatum # for backward compatibility
570 def toEcef(self):
571 '''Convert this cartesian to I{geodetic} (lat-/longitude) coordinates.
573 @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
574 C, M, datum)} with C{C} and C{M} if available.
576 @raise EcefError: A C{.datum} or an ECEF issue.
577 '''
578 return self._ecef9
580 def toLatLon(self, datum=None, height=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.toDatum
581 '''Convert this cartesian to a geodetic (lat-/longitude) point.
583 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
584 or L{a_f2Tuple}).
585 @kwarg height: Optional height, overriding the converted height
586 (C{meter}), iff B{C{LatLon}} is not C{None}.
587 @kwarg LatLon: Optional class to return the geodetic point
588 (C{LatLon}) or C{None}.
589 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
590 arguments, ignored if C{B{LatLon} is None}.
592 @return: The geodetic point (B{C{LatLon}}) or if B{C{LatLon}}
593 is C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon,
594 height, C, M, datum)} with C{C} and C{M} if available.
596 @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}.
597 '''
598 d = _spherical_datum(datum or self.datum, name=self.name)
599 if d == self.datum:
600 r = self.toEcef()
601 else:
602 c = self.toDatum(d)
603 r = c.Ecef(d, name=self.name).reverse(c, M=LatLon is None)
605 if LatLon: # class or .classof
606 h = _heigHt(r, height)
607 r = LatLon(r.lat, r.lon, datum=r.datum, height=h,
608 **_xkwds(LatLon_kwds, name=r.name))
609 _xdatum(r.datum, d)
610 return r
612 def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):
613 '''Convert this I{geocentric} cartesian to I{local} C{X}, C{Y} and C{Z}.
615 @kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z}
616 (L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.
617 @kwarg ltp: The I{local tangent plane} (LTP) to use,
618 overriding this cartesian's LTP (L{Ltp}).
619 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
620 arguments, ignored if C{B{Xyz} is None}.
622 @return: An B{C{Xyz}} instance or if C{B{Xyz} is None},
623 a L{Local9Tuple}C{(x, y, z, lat, lon, height,
624 ltp, ecef, M)} with C{M=None} always.
626 @raise TypeError: Invalid B{C{ltp}}.
627 '''
628 p = _MODS.ltp._xLtp(ltp, self._ltp)
629 return p._ecef2local(self._ecef9, Xyz, Xyz_kwds)
631 def toLtp(self, Ecef=None):
632 '''Return the I{local tangent plane} (LTP) for this cartesian.
634 @kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ...
635 L{EcefYou}), overriding this cartesian's C{Ecef}.
636 '''
637 return self._ltp if Ecef in (None, self.Ecef) else _MODS.ltp.Ltp(
638 self._ecef9, ecef=Ecef(self.datum), name=self.name)
640 def toNvector(self, Nvector=None, datum=None, **Nvector_kwds):
641 '''Convert this cartesian to C{n-vector} components.
643 @kwarg Nvector: Optional class to return the C{n-vector}
644 components (C{Nvector}) or C{None}.
645 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
646 or L{a_f2Tuple}) overriding this cartesian's datum.
647 @kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword
648 arguments, ignored if C{B{Nvector} is None}.
650 @return: The C{unit, n-vector} components (B{C{Nvector}}) or a
651 L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector}} is C{None}.
653 @raise TypeError: Invalid B{C{datum}}.
655 @raise ValueError: The B{C{Cartesian}} at origin.
657 @example:
659 >>> c = Cartesian(3980581, 97, 4966825)
660 >>> n = c.toNvector() # (x=0.622818, y=0.00002, z=0.782367, h=0.242887)
661 '''
662 d = _spherical_datum(datum or self.datum, name=self.name)
663 r = self._N_vector.xyzh if self.datum == d else self._n_xyzh4(d)
665 if Nvector is not None:
666 kwds = _xkwds(Nvector_kwds, h=r.h, datum=d)
667 r = self._xnamed(Nvector(r.x, r.y, r.z, **kwds))
668 return r
670 def toStr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_): # PYCHOK expected
671 '''Return the string representation of this cartesian.
673 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
674 @kwarg fmt: Enclosing backets format (string).
675 @kwarg sep: Separator to join (string).
677 @return: Cartesian represented as "[x, y, z]" (string).
678 '''
679 return Vector3d.toStr(self, prec=prec, fmt=fmt, sep=sep)
681 def toTransform(self, transform, inverse=False, datum=None):
682 '''Return a new cartesian by applying a Helmert transform
683 to this cartesian.
685 @arg transform: Transform to apply (L{Transform}).
686 @kwarg inverse: Apply the inverse of the Helmert
687 transform (C{bool}).
688 @kwarg datum: Datum for the transformed cartesian (L{Datum}),
689 overriding this cartesian's datum.
691 @return: The transformed cartesian (C{Cartesian}).
693 @raise Valuerror: If C{B{inverse}=True} and B{C{datum}}
694 is not L{Datums}C{.WGS84}.
695 '''
696 d = datum or self.datum
697 if inverse and d != _WGS84:
698 raise _ValueError(inverse=inverse, datum=d,
699 txt=_not_(_WGS84.name))
701 xyz = transform.transform(*self.xyz, inverse=inverse)
702 return self.classof(xyz, datum=d)
704 def toVector(self, Vector=None, **Vector_kwds):
705 '''Return this cartesian's components as vector.
707 @kwarg Vector: Optional class to return the C{n-vector}
708 components (L{Vector3d}) or C{None}.
709 @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
710 arguments, ignored if C{B{Vector} is None}.
712 @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)} if
713 B{C{Vector}} is C{None}.
715 @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}}.
716 '''
717 return self.xyz if Vector is None else self._xnamed(
718 Vector(self.x, self.y, self.z, **Vector_kwds))
721__all__ += _ALL_DOCS(CartesianBase)
723# **) MIT License
724#
725# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
726#
727# Permission is hereby granted, free of charge, to any person obtaining a
728# copy of this software and associated documentation files (the "Software"),
729# to deal in the Software without restriction, including without limitation
730# the rights to use, copy, modify, merge, publish, distribute, sublicense,
731# and/or sell copies of the Software, and to permit persons to whom the
732# Software is furnished to do so, subject to the following conditions:
733#
734# The above copyright notice and this permission notice shall be included
735# in all copies or substantial portions of the Software.
736#
737# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
738# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
739# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
740# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
741# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
742# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
743# OTHER DEALINGS IN THE SOFTWARE.