Coverage for pygeodesy/ltpTuples.py: 95%
570 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-06 12:20 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-06 12:20 -0500
2# -*- coding: utf-8 -*-
4u'''Named, I{Local Tangent Plane} (LTP) tuples.
6Local coordinate classes L{XyzLocal}, L{Enu}, L{Ned} and L{Aer} and
7local coordinate tuples L{Local9Tuple}, L{Xyz4Tuple}, L{Enu4Tuple},
8L{Ned4Tuple}, L{Aer4Tuple}, L{ChLV9Tuple}, L{ChLVEN2Tuple},
9L{ChLVYX2Tuple}, L{ChLVyx2Tuple} and L{Footprint5Tuple}.
11@see: References in module L{ltp}.
12'''
14# from pygeodesy.basics import issubclassof # _MODS
15from pygeodesy.constants import _0_0, _1_0, _90_0, _N_90_0
16# from pygeodesy.dms import F_D, toDMS # _MODS
17from pygeodesy.errors import _TypeError, _TypesError, _xattr, _xkwds, \
18 _xkwds_item2
19from pygeodesy.fmath import fdot_, hypot, hypot_
20from pygeodesy.interns import NN, _4_, _azimuth_, _center_, _COMMASPACE_, \
21 _ecef_, _elevation_, _height_, _lat_, _lon_, \
22 _ltp_, _M_, _name_, _up_, _X_, _x_, _xyz_, \
23 _Y_, _y_, _z_
24from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
25# from pygeodesy.ltp import Attitude, ChLV, ChLVa, ChLVe, _xLtp # _MODS
26from pygeodesy.named import _name__, _name1__, _name2__, _NamedBase, \
27 _NamedTuple, _Pass, _xnamed
28from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
29from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
30 Property_RO, property_RO
31from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
32from pygeodesy.units import Azimuth, Bearing, Degrees, Degrees_, Height, \
33 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_
34from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians
35from pygeodesy.vector3d import Vector3d
37# from math import cos, radians # from .utily
39__all__ = _ALL_LAZY.ltpTuples
40__version__ = '24.12.06'
42_aer_ = 'aer'
43_alt_ = 'alt'
44_down_ = 'down'
45_east_ = 'east'
46_enu_ = 'enu'
47_h__ = 'h_'
48_ned_ = 'ned'
49_north_ = 'north'
50_local_ = 'local'
51_roll_ = 'roll'
52_slantrange_ = 'slantrange'
53_tilt_ = 'tilt'
54_uvw_ = 'uvw'
55_yaw_ = 'yaw'
58class _AbcBase(_NamedBase):
59 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
60 '''
61 _ltp = None # local tangent plane (C{Ltp}), origin
63 @Property_RO
64 def ltp(self):
65 '''Get the I{local tangent plane} (L{Ltp}).
66 '''
67 return self._ltp
69 def toAer(self, Aer=None, **name_Aer_kwds):
70 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
72 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
73 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
74 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
75 is None}.
77 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
78 slantrange, ltp)} if C{B{Aer} is None}.
80 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
81 '''
82 return self.xyz4._toXyz(Aer, name_Aer_kwds)
84 def toEnu(self, Enu=None, **name_Enu_kwds):
85 '''Get the I{local} I{East, North, Up} (ENU) components.
87 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
88 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
89 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
90 is None}.
92 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up,
93 ltp)} if C{B{Enu} is None}.
95 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
96 '''
97 return self.xyz4._toXyz(Enu, name_Enu_kwds)
99 def toNed(self, Ned=None, **name_Ned_kwds):
100 '''Get the I{local} I{North, East, Down} (NED) components.
102 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
103 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
104 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
105 is None}.
107 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
108 ltp)} if C{B{Ned} is None}.
110 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
111 '''
112 return self.xyz4._toXyz(Ned, name_Ned_kwds)
114 def toXyz(self, Xyz=None, **name_Xyz_kwds):
115 '''Get the local I{X, Y, Z} (XYZ) components.
117 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
118 or C{None}.
119 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
120 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
121 is None}.
123 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
124 C{B{Xyz} is None}.
126 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
127 '''
128 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
130 @Property_RO
131 def xyz(self):
132 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
133 '''
134 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
136 @property_RO
137 def xyz3(self):
138 '''Get the I{local} C{(X, Y, Z)} coordinates as C{3-tuple}.
139 '''
140 return tuple(self.xyz)
142 @property_RO
143 def xyz4(self): # PYCHOK no cover
144 '''I{Must be overloaded}.'''
145 self._notOverloaded()
147 @Property_RO
148 def xyzLocal(self):
149 '''Get this AER or NED as an L{XyzLocal}.
150 '''
151 return XyzLocal(self.xyz4, name=self.name)
154class _Abc4Tuple(_NamedTuple):
155 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple},
156 C{Ned4Tuple} and C{Xyz4Tuple}.
157 '''
158 def _2Cls(self, Abc, Cls, Cls_kwds):
159 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
160 '''
161 kwds = _name1__(Cls_kwds, _or_nameof=self)
162 _is = _MODS.basics.issubclassof
163 if Cls is None:
164 n, _ = _name2__(Cls_kwds)
165 r = self.copy(name=n) if n else self
166 elif _is(Cls, Abc):
167 r = Cls(*self, **kwds)
168 elif _is(Cls, Aer):
169 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls))
170 elif _is(Cls, Enu): # PYCHOK no cover
171 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls))
172 elif _is(Cls, Ned):
173 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls))
174 elif _is(Cls, XyzLocal): # PYCHOK no cover
175 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls))
176 elif Cls is Local9Tuple: # PYCHOK no cover
177 r = self.xyzLocal.toLocal9Tuple(**kwds)
178 else: # PYCHOK no cover
179 n = Abc.__name__[:3]
180 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
181 return r
183 @property_RO
184 def xyzLocal(self): # PYCHOK no cover
185 '''I{Must be overloaded}.'''
186 self._notOverloaded()
189class Aer(_AbcBase):
190 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
191 '''
192 _azimuth = _0_0 # bearing from North (C{degrees360})
193 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
194# _ltp = None # local tangent plane (C{Ltp}), origin
195 _slantrange = _0_0 # distance (C{Meter})
196 _toStr = _aer_
198 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name):
199 '''New L{Aer}.
201 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
202 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
203 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
204 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
205 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
206 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
207 only used with scalar B{C{azimuth_aer}}.
208 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
209 B{C{azimuth_aer}}.
210 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
211 L{LocalCartesian}).
212 @kwarg name: Optional C{B{name}=NN} (C{str}).
214 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
216 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
217 or B{C{slantrange}}.
218 '''
219 if _isDegrees(azimuth_aer):
220 aer = None
221 t = (Azimuth(azimuth_aer),
222 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0),
223 Meter_(slantrange=slantrange), ltp)
224 else: # PYCHOK no cover
225 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
226 aer = p.toAer() if p else azimuth_aer
227 t = aer.aer4
228 self._azimuth, self._elevation, self._slantrange, _ = t
229 _init(self, aer, ltp, name)
231 @Property_RO
232 def aer4(self):
233 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
234 '''
235 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
237 @Property_RO
238 def azimuth(self):
239 '''Get the Azimuth, bearing from North (C{degrees360}).
240 '''
241 return self._azimuth
243 @Property_RO
244 def down(self):
245 '''Get the Down component (C{meter}).
246 '''
247 return self.xyzLocal.down
249 @Property_RO
250 def east(self):
251 '''Get the East component (C{meter}).
252 '''
253 return self.xyzLocal.east
255 @Property_RO
256 def elevation(self):
257 '''Get the Elevation, tilt above horizon (C{degrees90}).
258 '''
259 return self._elevation
261 @Property_RO
262 def groundrange(self):
263 '''Get the I{ground range}, distance (C{meter}).
264 '''
265 return _er2gr(self._elevation, self._slantrange)
267 @Property_RO
268 def north(self):
269 '''Get the North component (C{meter}).
270 '''
271 return self.xyzLocal.north
273 @Property_RO
274 def slantrange(self):
275 '''Get the I{slant Range}, distance (C{meter}).
276 '''
277 return self._slantrange
279 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
280 '''Return a string representation of this AER as azimuth
281 (bearing), elevation and slant range.
283 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
284 @kwarg fmt: Enclosing backets format (C{str}).
285 @kwarg sep: Optional separator between AERs (C{str}).
287 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
288 '''
289 m = _MODS.dms
290 t = (m.toDMS(self.azimuth, form=m.F_D, prec=prec, ddd=0),
291 m.toDMS(self.elevation, form=m.F_D, prec=prec, ddd=0),
292 fstr( self.slantrange, prec=3 if prec is None else prec))
293 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
295 def toStr(self, **prec_fmt_sep): # PYCHOK expected
296 '''Return a string representation of this AER.
298 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
299 number of (decimal) digits, unstripped
300 (C{int}), C{B{fmt}='[]'} the enclosing
301 backets format (C{str}) and separator
302 C{B{sep}=", "} to join (C{str}).
304 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
305 '''
306 _, t = _toStr2(self, **prec_fmt_sep)
307 return t
309 @Property_RO
310 def up(self):
311 '''Get the Up component (C{meter}).
312 '''
313 return self.xyzLocal.up
315 @Property_RO
316 def x(self):
317 '''Get the X component (C{meter}).
318 '''
319 return self.xyz4.x
321 @Property_RO
322 def xyz4(self):
323 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
324 '''
325 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
326 R = self._slantrange
327 r = cE * R # ground range
328 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
330 @Property_RO
331 def y(self):
332 '''Get the Y component (C{meter}).
333 '''
334 return self.xyz4.y
336 @Property_RO
337 def z(self):
338 '''Get the Z component (C{meter}).
339 '''
340 return self.xyz4.z
343class Aer4Tuple(_Abc4Tuple):
344 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
345 all in C{meter} except C{ltp}.
346 '''
347 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
348 _Units_ = ( Meter, Meter, Meter, _Pass)
350 def _toAer(self, Cls, Cls_kwds):
351 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
352 '''
353 return self._2Cls(Aer, Cls, Cls_kwds)
355 @Property_RO
356 def groundrange(self):
357 '''Get the I{ground range}, distance (C{meter}).
358 '''
359 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
361 @Property_RO
362 def xyzLocal(self):
363 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
364 '''
365 return Aer(self).xyzLocal
368class Attitude4Tuple(_NamedTuple):
369 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
370 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
371 the attitude of a plane or camera.
372 '''
373 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
374 _Units_ = ( Meter, Degrees, Bearing, Degrees)
376 @Property_RO
377 def atyr(self):
378 '''Return this attitude (L{Attitude4Tuple}).
379 '''
380 return self
382 @Property_RO
383 def tyr3d(self):
384 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
385 '''
386 return _MODS.ltp.Attitude(self).tyr3d
389class Ned(_AbcBase):
390 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
392 @see: L{Enu} and L{Ltp}.
393 '''
394 _down = _0_0 # down, -XyzLocal.z (C{meter}).
395 _east = _0_0 # east, XyzLocal.y (C{meter}).
396# _ltp = None # local tangent plane (C{Ltp}), origin
397 _north = _0_0 # north, XyzLocal.x (C{meter})
398 _toStr = _ned_
400 def __init__(self, north_ned, east=0, down=0, ltp=None, **name):
401 '''New L{Ned} vector.
403 @arg north_ned: Scalar North component (C{meter}) or a previous
404 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
405 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
406 L{XyzLocal} or L{Xyz4Tuple}).
407 @kwarg east: Scalar East component (C{meter}), only used with
408 scalar B{C{north_ned}}.
409 @kwarg down: Scalar Down component, normal to I{inside} surface
410 of the ellipsoid or sphere (C{meter}), only used with
411 scalar B{C{north_ned}}.
412 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
413 L{LocalCartesian}).
414 @kwarg name: Optional C{B{name}=NN} (C{str}).
416 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
418 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
419 '''
420 if _isMeter(north_ned):
421 ned = None
422 t = (Meter(north=north_ned or _0_0),
423 Meter(east=east or _0_0),
424 Meter(down=down or _0_0), ltp)
425 else: # PYCHOK no cover
426 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
427 ned = p.toNed() if p else north_ned
428 t = ned.ned4
429 self._north, self._east, self._down, _ = t
430 _init(self, ned, ltp, name)
432 @Property_RO
433 def aer4(self):
434 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
435 '''
436 return _xyz2aer4(self)
438 @Property_RO
439 def azimuth(self):
440 '''Get the Azimuth, bearing from North (C{degrees360}).
441 '''
442 return self.aer4.azimuth
444 @deprecated_Property_RO
445 def bearing(self):
446 '''DEPRECATED, use C{azimuth}.'''
447 return self.azimuth
449 @Property_RO
450 def down(self):
451 '''Get the Down component (C{meter}).
452 '''
453 return self._down
455 @Property_RO
456 def east(self):
457 '''Get the East component (C{meter}).
458 '''
459 return self._east
461 @Property_RO
462 def elevation(self):
463 '''Get the Elevation, tilt above horizon (C{degrees90}).
464 '''
465 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
467 @Property_RO
468 def groundrange(self):
469 '''Get the I{ground range}, distance (C{meter}).
470 '''
471 return Meter(groundrange=hypot(self.north, self.east))
473 @deprecated_Property_RO
474 def length(self):
475 '''DEPRECATED, use C{slantrange}.'''
476 return self.slantrange
478 @deprecated_Property_RO
479 def ned(self):
480 '''DEPRECATED, use property C{ned4}.'''
481 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
483 @Property_RO
484 def ned4(self):
485 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
486 '''
487 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
489 @Property_RO
490 def north(self):
491 '''Get the North component (C{meter}).
492 '''
493 return self._north
495 @Property_RO
496 def slantrange(self):
497 '''Get the I{slant Range}, distance (C{meter}).
498 '''
499 return self.aer4.slantrange
501 @deprecated_method
502 def to3ned(self): # PYCHOK no cover
503 '''DEPRECATED, use property L{ned4}.'''
504 return self.ned # XXX deprecated too
506 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
507 '''Return a string representation of this NED.
509 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
510 @kwarg fmt: Enclosing backets format (C{str}).
511 @kwarg sep: Separator to join (C{str}).
513 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
514 '''
515 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
516 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
518 def toStr(self, **prec_fmt_sep): # PYCHOK expected
519 '''Return a string representation of this NED.
521 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
522 number of (decimal) digits, unstripped
523 (C{int}), C{B{fmt}='[]'} the enclosing
524 backets format (C{str}) and separator
525 C{B{sep}=", "} to join (C{str}).
527 @return: This NED as "[meter, meter, meter]" (C{str}).
528 '''
529 _, t = _toStr2(self, **prec_fmt_sep)
530 return t
532 @deprecated_method
533 def toVector3d(self):
534 '''DEPRECATED, use property L{xyz}.'''
535 return self.xyz
537 @Property_RO
538 def up(self):
539 '''Get the Up component (C{meter}).
540 '''
541 return Meter(up=-self._down) # negated
543 @Property_RO
544 def x(self):
545 '''Get the X component (C{meter}).
546 '''
547 return Meter(x=self._east) # 2nd arg, E
549 @Property_RO
550 def xyz4(self):
551 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
552 '''
553 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
555 @Property_RO
556 def y(self):
557 '''Get the Y component (C{meter}).
558 '''
559 return Meter(y=self._north) # 1st arg N
561 @Property_RO
562 def z(self):
563 '''Get the Z component (C{meter}).
564 '''
565 return Meter(z=-self._down) # negated
568class Ned4Tuple(_Abc4Tuple):
569 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
570 '''
571 _Names_ = (_north_, _east_, _down_, _ltp_)
572 _Units_ = ( Meter, Meter, Meter, _Pass)
574 def _toNed(self, Cls, Cls_kwds):
575 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
576 '''
577 return self._2Cls(Ned, Cls, Cls_kwds)
579 @Property_RO
580 def xyzLocal(self):
581 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
582 '''
583 return Ned(self).xyzLocal
586class _Vector3d(Vector3d):
588 _toStr = _xyz_
590 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
591 '''Return a string representation of this ENU/NED/XYZ.
593 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
594 @kwarg fmt: Enclosing backets format (C{str}).
595 @kwarg sep: Separator to join (C{str}).
597 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
598 "[N:meter, E:meter, D:meter]",
599 "[U:meter, V:meter, W:meter]" respectively
600 "[X:meter, Y:meter, Z:meter]" (C{str}).
601 '''
602 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
603 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
605 def toStr(self, **prec_fmt_sep): # PYCHOK expected
606 '''Return a string representation of this XYZ.
608 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
609 number of (decimal) digits, unstripped
610 (C{int}), C{B{fmt}='[]'} the enclosing
611 backets format (C{str}) and separator
612 C{B{sep}=", "} to join (C{str}).
614 @return: This XYZ as "[meter, meter, meter]" (C{str}).
615 '''
616 _, t = _toStr2(self, **prec_fmt_sep)
617 return t
620class XyzLocal(_Vector3d):
621 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
622 also base class for local L{Enu}.
623 '''
624 _ltp = None # local tangent plane (C{Ltp}), origin
626 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name):
627 '''New L{XyzLocal}.
629 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
630 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
631 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
632 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
633 @kwarg y: Scalar Y component (C{meter}), only used with scalar
634 B{C{x_xyz}}, C{positive north}.
635 @kwarg z: Scalar Z component, normal C{positive up} from the
636 surface of the ellipsoid or sphere (C{meter}), only
637 used with scalar B{C{x_xyz}}.
638 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
639 L{LocalCartesian}).
640 @kwarg name: Optional C{B{name}=NN} (C{str}).
642 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
644 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
645 '''
646 if _isMeter(x_xyz):
647 xyz = None
648 t = (Meter(x=x_xyz or _0_0),
649 Meter(y=y or _0_0),
650 Meter(z=z or _0_0), ltp)
651 else:
652 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
653 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp
654 self._x, self._y, self._z, _ = t
655 _init(self, xyz, ltp, name)
657 def __str__(self):
658 return self.toStr()
660 @Property_RO
661 def aer4(self):
662 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
663 '''
664 return _xyz2aer4(self)
666 @Property_RO
667 def azimuth(self):
668 '''Get the Azimuth, bearing from North (C{degrees360}).
670 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
671 Transformations_between_ECEF_and_ENU_coordinates>}.
672 '''
673 return self.aer4.azimuth
675 def classof(self, *args, **kwds): # PYCHOK no cover
676 '''Create another instance of this very class.
678 @arg args: Optional, positional arguments.
679 @kwarg kwds: Optional, keyword arguments.
681 @return: New instance (C{self.__class__}).
682 '''
683 kwds = _name1__(kwds, _or_nameof=self)
684 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp))
686 @Property_RO
687 def down(self):
688 '''Get the Down component (C{meter}).
689 '''
690 return Meter(down=-self.z)
692 @property_RO
693 def ecef(self):
694 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
695 '''
696 return self.ltp.ecef
698 @Property_RO
699 def east(self):
700 '''Get the East component (C{meter}).
701 '''
702 return Meter(east=self.x)
704 @Property_RO
705 def elevation(self):
706 '''Get the Elevation, tilt above horizon (C{degrees90}).
708 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
709 Transformations_between_ECEF_and_ENU_coordinates>}.
710 '''
711 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
713 @Property_RO
714 def enu4(self):
715 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
716 '''
717 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
719 @Property_RO
720 def groundrange(self):
721 '''Get the I{ground range}, distance (C{meter}).
722 '''
723 return Meter(groundrange=hypot(self.x, self.y))
725 @Property_RO
726 def ltp(self):
727 '''Get the I{local tangent plane} (L{Ltp}).
728 '''
729 return self._ltp
731 def _ltp_kwds_name3(self, ltp, kwds):
732 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}.
733 '''
734 ltp = _xLtp(ltp, self.ltp)
735 kwds = _name1__(kwds, _or_nameof=self)
736 kwds = _name1__(kwds, _or_nameof=ltp)
737 return ltp, kwds, kwds.get(_name_, NN)
739 @Property_RO
740 def ned4(self):
741 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
742 '''
743 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
745 @Property_RO
746 def north(self):
747 '''Get the North component (C{meter}).
748 '''
749 return Meter(north=self.y)
751 @Property_RO
752 def slantrange(self):
753 '''Get the I{slant Range}, distance (C{meter}).
754 '''
755 return self.aer4.slantrange
757 def toAer(self, Aer=None, **name_Aer_kwds):
758 '''Get the local I{Azimuth, Elevation, slant Range} components.
760 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
761 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
762 additional B{C{Aer}} keyword arguments, ignored if C{B{Aer}
763 is None}.
765 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
766 slantrange, ltp)} if C{B{Aer} is None}.
768 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item.
769 '''
770 return self.aer4._toAer(Aer, name_Aer_kwds)
772 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds): # PYCHOK signature
773 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
775 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
776 or C{None}.
777 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
778 this C{ltp}.
779 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
780 additional B{C{Cartesian}} keyword arguments, ignored if
781 C{B{Cartesian} is None}.
783 @return: A B{C{Cartesian}} instance or if C{B{Cartesian} is None}, an
784 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
785 C{M=None}, always.
787 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item.
788 '''
789 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds)
790 if Cartesian is None:
791 t = ltp._local2ecef(self, nine=True)
792 r = _xnamed(t, n) if n else t
793 else:
794 kwds = _xkwds(kwds, datum=ltp.datum, name=n)
795 xyz = ltp._local2ecef(self) # [:3]
796 r = Cartesian(*xyz, **kwds)
797 return r
799 def toEnu(self, Enu=None, **name_Enu_kwds):
800 '''Get the local I{East, North, Up} (ENU) components.
802 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
803 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
804 additional B{C{Enu}} keyword arguments, ignored if C{B{Enu}
805 is None}.
807 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up,
808 ltp)} if C{B{Enu} is None}.
810 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item.
811 '''
812 return self.enu4._toEnu(Enu, name_Enu_kwds)
814 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds):
815 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
817 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon}) or
818 C{None}.
819 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
820 this ENU/NED/AER/XYZ's LTP.
821 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
822 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
823 is None}.
825 @return: An B{C{LatLon}} instance or an L{Ecef9Tuple}C{(x, y, z, lat, lon,
826 height, C, M, datum)} if C{B{LatLon} is None}, with C{M=None}.
828 @raise TypeError: Invalid B{C{LatLon}}, B{C{ltp}} or B{C{name_LatLon_kwds}} item.
829 '''
830 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds)
831 t = ltp._local2ecef(self, nine=True)
832 if LatLon is None:
833 r = _xnamed(t, n) if n else t
834 else:
835 kwds = _xkwds(kwds, height=t.height, datum=t.datum)
836 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp?
837 return r
839 def toLocal9Tuple(self, M=False, **name):
840 '''Get this local as a C{Local9Tuple}.
842 @kwarg M: Optionally include the rotation matrix (C{bool}).
843 @kwarg name: Optional C{B{name}=NN} (C{str}).
845 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)}
846 with C{ltp} this C{Ltp}, C{ecef} an L{Ecef9Tuple} and C{M}
847 an L{EcefMatrix} or C{None}.
848 '''
849 ltp = self.ltp # see C{self.toLatLon}
850 t = ltp._local2ecef(self, nine=True, M=M)
851 return Local9Tuple(self.x, self.y, self.z,
852 t.lat, t.lon, t.height,
853 ltp, t, t.M, name=t._name__(name))
855 def toNed(self, Ned=None, **name_Ned_kwds):
856 '''Get the local I{North, East, Down} (Ned) components.
858 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
859 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
860 additional B{C{Ned}} keyword arguments, ignored if C{B{Ned}
861 is None}.
863 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
864 ltp)} if C{B{Ned} is None}.
866 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item.
867 '''
868 return self.ned4._toNed(Ned, name_Ned_kwds)
870 def toXyz(self, Xyz=None, **name_Xyz_kwds):
871 '''Get the local I{X, Y, Z} (XYZ) components.
873 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
874 or C{None}.
875 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
876 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
877 is None}.
879 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
880 C{B{Xyz} is None}.
882 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item.
883 '''
884 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
886 @Property_RO
887 def up(self):
888 '''Get the Up component (C{meter}).
889 '''
890 return Meter(up=self.z)
892# @Property_RO
893# def x(self): # see: Vector3d.x
894# '''Get the X component (C{meter}).
895# '''
896# return self._x
898# @Property_RO
899# def xyz(self): # see: Vector3d.xyz
900# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
901# '''
902# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
904 @Property_RO
905 def xyz4(self):
906 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
907 '''
908 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
910 @Property_RO
911 def xyzLocal(self):
912 '''Get this L{XyzLocal}.
913 '''
914 return self
916# @Property_RO
917# def y(self): # see: Vector3d.y
918# '''Get the Y component (C{meter}).
919# '''
920# return self._y
922# @Property_RO
923# def z(self): # see: Vector3d.z
924# '''Get the Z component (C{meter}).
925# '''
926# return self._z
929class Xyz4Tuple(_Abc4Tuple):
930 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
931 '''
932 _Names_ = (_x_, _y_, _z_, _ltp_)
933 _Units_ = ( Meter, Meter, Meter, _Pass)
935 def _toXyz(self, Cls, Cls_kwds):
936 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
937 '''
938 return self._2Cls(XyzLocal, Cls, Cls_kwds)
940 @property_RO
941 def xyz4(self):
942 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
943 '''
944 return self
946 @Property_RO
947 def xyzLocal(self):
948 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
949 '''
950 return XyzLocal(*self, name=self.name)
953class Enu(XyzLocal):
954 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
956 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
957 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
958 '''
959 _toStr = _enu_
961 def __init__(self, east_enu, north=0, up=0, ltp=None, **name):
962 '''New L{Enu}.
964 @arg east_enu: Scalar East component (C{meter}) or a previous
965 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
966 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
967 L{XyzLocal} or L{Xyz4Tuple}).
968 @kwarg north: Scalar North component (C{meter}) only used with
969 scalar B{C{east_enu}}.
970 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
971 normal from the surface of the ellipsoid or sphere (C{meter}).
972 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
973 L{LocalCartesian}).
974 @kwarg name: Optional C{B{name}=NN} (C{str}).
976 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
978 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
979 '''
980 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name)
982 def toUvw(self, location, Uvw=None, **name_Uvw_kwds):
983 '''Get the I{u, v, w} (UVW) components at a location.
985 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
986 L{Vector3d}) location, like a Point-Of-View.
987 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
988 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
989 additional B{L{Uvw}} keyword arguments, ignored if C{B{Uvw}
990 is None}.
992 @return: A B{C{Uvw}} instance or a L{Uvw3Tuple}C{(u, v, w)} if C{B{Uvw}
993 is None}.
995 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}} item.
997 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
998 '''
999 try:
1000 sa, ca, sb, cb = sincos2_(*location.philam)
1001 except Exception as x:
1002 raise _TypeError(location=location, cause=x)
1003 e, n, u, _ = self.enu4
1005 t = fdot_(ca, u, -sa, n)
1006 U = fdot_(cb, t, -sb, e)
1007 V = fdot_(cb, e, sb, t)
1008 W = fdot_(ca, n, sa, u)
1010 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self)
1011 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \
1012 Uvw(U, V, W, name=n, **kwds)
1014 @Property_RO
1015 def xyzLocal(self):
1016 '''Get this ENU as an L{XyzLocal}.
1017 '''
1018 return XyzLocal(*self.xyz4, name=self.name)
1021class Enu4Tuple(_Abc4Tuple):
1022 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1023 '''
1024 _Names_ = (_east_, _north_, _up_, _ltp_)
1025 _Units_ = ( Meter, Meter, Meter, _Pass)
1027 def _toEnu(self, Cls, Cls_kwds):
1028 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1029 '''
1030 return self._2Cls(Enu, Cls, Cls_kwds)
1032 @Property_RO
1033 def xyzLocal(self):
1034 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1035 '''
1036 return XyzLocal(*self, name=self.name)
1039class Local9Tuple(_NamedTuple):
1040 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1041 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1042 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1043 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1044 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1045 '''
1046 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1047 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1049 @Property_RO
1050 def azimuth(self):
1051 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1052 '''
1053 return self.xyzLocal.aer4.azimuth
1055 @Property_RO
1056 def down(self):
1057 '''Get the I{local} Down, C{-z} component (C{meter}).
1058 '''
1059 return -self.z
1061 @Property_RO
1062 def east(self):
1063 '''Get the I{local} East, C{x} component (C{meter}).
1064 '''
1065 return self.x
1067 @Property_RO
1068 def elevation(self):
1069 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1070 '''
1071 return self.xyzLocal.aer4.elevation
1073 @Property_RO
1074 def groundrange(self):
1075 '''Get the I{local} ground range, distance (C{meter}).
1076 '''
1077 return self.xyzLocal.aer4.groundrange
1079 @Property_RO
1080 def lam(self):
1081 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1082 '''
1083 return self.philam.lam
1085 @Property_RO
1086 def latlon(self):
1087 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1088 '''
1089 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1091 @Property_RO
1092 def latlonheight(self):
1093 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1094 '''
1095 return self.latlon.to3Tuple(self.height)
1097 @Property_RO
1098 def north(self):
1099 '''Get the I{local} North, C{y} component (C{meter}).
1100 '''
1101 return self.y
1103 @Property_RO
1104 def phi(self):
1105 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1106 '''
1107 return self.philam.phi
1109 @Property_RO
1110 def philam(self):
1111 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1112 '''
1113 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1115 @Property_RO
1116 def philamheight(self):
1117 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1118 '''
1119 return self.philam.to3Tuple(self.height)
1121 @Property_RO
1122 def slantrange(self):
1123 '''Get the I{local} slant Range, distance (C{meter}).
1124 '''
1125 return self.xyzLocal.aer4.slantrange
1127 def toAer(self, Aer=None, **name_Aer_kwds):
1128 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1130 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1131 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1132 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
1133 is None}.
1135 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
1136 slantrange, ltp)} if C{B{Aer} is None}.
1138 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item.
1139 '''
1140 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds)
1142 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds):
1143 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1145 @kwarg Cartesian: Optional I{geocentric} class to return C{(x, y, z)}
1146 (C{Cartesian}) or C{None}.
1147 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1148 additional B{C{Cartesian}} keyword arguments, ignored if
1149 C{B{Cartesian} is None}.
1151 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or a
1152 L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
1154 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item.
1155 '''
1156 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple
1158 def toEnu(self, Enu=None, **name_Enu_kwds):
1159 '''Get the I{local} I{East, North, Up} (ENU) components.
1161 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1162 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1163 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1164 is None}.
1166 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1167 L{Enu4Tuple}C{(east, north, up, ltp)}.
1169 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item.
1170 '''
1171 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds)
1173 def toLatLon(self, LatLon=None, **name_LatLon_kwds):
1174 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1176 @kwarg LatLon: Optional I{geodetic} class to return C{(lat, lon, height)}
1177 (C{LatLon}) or C{None}.
1178 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1179 additional B{C{LatLon}} keyword arguments, ignored if
1180 C{B{LatLon} is None}.
1182 @return: An C{B{LatLon}(lat, lon, **B{LatLon_kwds})} instance or if
1183 C{B{LatLon} is None}, a L{LatLon4Tuple}C{(lat, lon, height,
1184 datum)} or L{LatLon3Tuple}C{(lat, lon, height)} if C{datum}
1185 is specified or not.
1187 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1188 '''
1189 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple
1191 def toNed(self, Ned=None, **name_Ned_kwds):
1192 '''Get the I{local} I{North, East, Down} (NED) components.
1194 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1195 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1196 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
1197 is None}.
1199 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
1200 ltp)} if C{B{Ned} is None}.
1202 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item.
1203 '''
1204 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds)
1206 def toXyz(self, Xyz=None, **name_Xyz_kwds):
1207 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1209 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1210 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1211 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
1212 is None}.
1214 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
1215 C{B{Xyz} is None}.
1217 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item.
1218 '''
1219 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds)
1221 @Property_RO
1222 def up(self):
1223 '''Get the I{local} Up, C{z} component (C{meter}).
1224 '''
1225 return self.z
1227 @Property_RO
1228 def xyz(self):
1229 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1230 '''
1231 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1233 @Property_RO
1234 def xyzLocal(self):
1235 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1236 '''
1237 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1240_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1241_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1244class Uvw(_Vector3d):
1245 '''3-D C{u-v-w} (UVW) components.
1246 '''
1247 _toStr = _uvw_
1249 def __init__(self, u_uvw, v=0, w=0, **name):
1250 '''New L{Uvw}.
1252 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1253 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1254 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1255 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1256 @kwarg name: Optional C{B{name}=NN} (C{str}).
1258 @raise TypeError: Invalid B{C{east_enu}}.
1260 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1261 '''
1262 Vector3d.__init__(self, u_uvw, v, w, **name)
1264 def toEnu(self, location, Enu=Enu, **name_Enu_kwds):
1265 '''Get the I{East, North, Up} (ENU) components at a location.
1267 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1268 L{Vector3d}) location from where to cast the L{Los}.
1269 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1270 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1271 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1272 is None}.
1274 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up, ltp)}
1275 with C{ltp=None} if C{B{Enu} is None}.
1277 @raise TypeError: Invalid B{C{location}}, B{C{Enu}} or B{C{name_Enu_kwds}} item.
1279 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1280 '''
1281 try:
1282 sa, ca, sb, cb = sincos2_(*location.philam)
1283 except Exception as x:
1284 raise _TypeError(location=location, cause=x)
1285 u, v, w = self.uvw
1287 t = fdot_(cb, u, sb, v)
1288 E = fdot_(cb, v, -sb, u)
1289 N = fdot_(ca, w, -sa, t)
1290 U = fdot_(ca, t, sa, w)
1292 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self)
1293 return Enu4Tuple(E, N, U, name=n) if Enu is None else \
1294 Enu(E, N, U, name=n, **kwds)
1296 u = Vector3d.x
1298 @Property_RO
1299 def uvw(self):
1300 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1301 '''
1302 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1304 v = Vector3d.y
1305 w = Vector3d.z
1308class Uvw3Tuple(_NamedTuple):
1309 '''3-Tuple C{(u, v, w)}, in C{meter}.
1310 '''
1311 _Names_ = ('u', 'v', 'w')
1312 _Units_ = ( Meter, Meter, Meter)
1315class Los(Aer):
1316 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1317 '''
1319 def __init__(self, azimuth_aer, elevation=0, **name):
1320 '''New L{Los}.
1322 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1323 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1324 L{Enu4Tuple} or L{Los}).
1325 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1326 is 0, zenith +90, nadir -90), only used with scalar
1327 B{C{azimuth_aer}}.
1328 @kwarg name: Optional C{B{name}=NN} (C{str}).
1330 @raise TypeError: Invalid B{C{azimuth_aer}}.
1332 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1333 '''
1334 t = Aer(azimuth_aer, elevation)
1335 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name)
1337 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds):
1338 '''Get this LOS' I{target} (UVW) components from a location.
1340 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1341 L{Vector3d}) location from where to cast this LOS.
1343 @see: Method L{Enu.toUvw} for further details.
1344 '''
1345 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds)
1347 def toEnu(self, Enu=Enu, **name_Enu_kwds):
1348 '''Get this LOS as I{East, North, Up} (ENU) components.
1350 @see: Method L{Aer.toEnu} for further details.
1351 '''
1352 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds)
1355class ChLV9Tuple(Local9Tuple):
1356 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1357 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1358 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1359 otherwise like L{Local9Tuple}.
1360 '''
1361 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1363 @Property_RO
1364 def E_LV95(self):
1365 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1366 '''
1367 return self.EN2_LV95.E_LV95
1369 @Property_RO
1370 def EN2_LV95(self):
1371 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1372 '''
1373 return ChLVEN2Tuple(*_ChLV_false2(*self.YX, LV95=True), name=self.name)
1375 @Property_RO
1376 def h_LV03(self):
1377 '''Get the I{Swiss h_} height (C{meter}).
1378 '''
1379 return self.h_
1381 @Property_RO
1382 def h_LV95(self):
1383 '''Get the I{Swiss h_} height (C{meter}).
1384 '''
1385 return self.h_
1387 @property_RO
1388 def isChLV(self):
1389 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1390 '''
1391 return self.ltp.__class__ is _MODS.ltp.ChLV
1393 @property_RO
1394 def isChLVa(self):
1395 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1396 '''
1397 return self.ltp.__class__ is _MODS.ltp.ChLVa
1399 @property_RO
1400 def isChLVe(self):
1401 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1402 '''
1403 return self.ltp.__class__ is _MODS.ltp.ChLVe
1405 @Property_RO
1406 def N_LV95(self):
1407 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1408 '''
1409 return self.EN2_LV95.N_LV95
1411 @Property_RO
1412 def x(self):
1413 '''Get the I{local x, Swiss Y} easting (C{meter}).
1414 '''
1415 return self.Y
1417 @Property_RO
1418 def x_LV03(self):
1419 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1420 '''
1421 return self.yx2_LV03.x_LV03
1423 @Property_RO
1424 def y(self):
1425 '''Get the I{local y, Swiss X} northing (C{meter}).
1426 '''
1427 return self.X
1429 @Property_RO
1430 def y_LV03(self):
1431 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1432 '''
1433 return self.yx2_LV03.y_LV03
1435 @Property_RO
1436 def YX(self):
1437 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1438 '''
1439 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1441 @Property_RO
1442 def yx2_LV03(self):
1443 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1444 '''
1445 return ChLVyx2Tuple(*_ChLV_false2(*self.YX, LV95=False), name=self.name)
1447 @Property_RO
1448 def z(self):
1449 '''Get the I{local z, Swiss h_} height (C{meter}).
1450 '''
1451 return self.h_
1454class ChLVYX2Tuple(_NamedTuple):
1455 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1456 in C{meter}.
1457 '''
1458 _Names_ = (_Y_, _X_)
1459 _Units_ = ( Meter, Meter)
1461 def false2(self, LV95=True):
1462 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1464 @see: Function L{ChLV.false2} for more information.
1465 '''
1466 return _ChLV_false2(*self, LV95=LV95, name=self.name)
1469class ChLVEN2Tuple(_NamedTuple):
1470 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1471 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1472 '''
1473 _Names_ = ('E_LV95', 'N_LV95')
1474 _Units_ = ChLVYX2Tuple._Units_
1476 def unfalse2(self):
1477 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1479 @see: Function L{ChLV.unfalse2} for more information.
1480 '''
1481 return _ChLV_unfalse2(*self, LV95=True, name=self.name)
1484class ChLVyx2Tuple(_NamedTuple):
1485 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1486 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1487 '''
1488 _Names_ = ('y_LV03', 'x_LV03')
1489 _Units_ = ChLVYX2Tuple._Units_
1491 def unfalse2(self):
1492 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1494 @see: Function L{ChLV.unfalse2} for more information.
1495 '''
1496 return _ChLV_unfalse2(*self, LV95=False, name=self.name)
1499class Footprint5Tuple(_NamedTuple):
1500 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1501 with the C{center} and 4 corners of the I{local} projection of
1502 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1504 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1505 '''
1506 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1507 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1509 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds):
1510 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1511 C{LatLon(lat, lon, height)}s, C{LatLon3Tuple}s or C{LatLon4Tuple}s.
1513 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1514 footprint's C{center} or C{frustrum} C{ltp}.
1515 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1516 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1517 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
1518 is None}.
1520 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, **B{name_LatLon_kwds})}
1521 instances or if C{B{LatLon} is None}, 5 L{LatLon3Tuple}C{(lat, lon,
1522 height)}s respectively 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s
1523 depending on whether keyword argument C{datum} is un-/specified.
1525 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}} item.
1527 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1528 '''
1529 ltp = _xLtp(ltp, self.center.ltp) # PYCHOK .center
1530 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)
1531 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon)
1532 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1534 def xyzLocal5(self, ltp=None):
1535 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1537 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1538 the {center} and corner C{ltp}s.
1540 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1542 @raise TypeError: Invalid B{C{ltp}}.
1543 '''
1544 if ltp is None:
1545 p = self
1546 else:
1547 p = _xLtp(ltp)
1548 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1549 return Footprint5Tuple(t.xyzLocal for t in p)
1552def _ChLV_false2(Y, X, **LV95_name):
1553 '''(INTERNAL) Invoke static method C{ltp.ChLV.false2}.
1554 '''
1555 return _MODS.ltp.ChLV.false2(Y, X, **LV95_name)
1558def _ChLV_unfalse2(e, n, **LV95_name):
1559 '''(INTERNAL) Invoke static method C{ltp.ChLV.unfalse2}.
1560 '''
1561 return _MODS.ltp.ChLV.unfalse2(e, n, **LV95_name)
1564def _er2gr(e, r):
1565 '''(INTERNAL) Elevation and slant range to ground range.
1566 '''
1567 c = cos(radians(e))
1568 return Meter_(groundrange=r * c)
1571def _init(inst, abc, ltp, name):
1572 '''(INTERNAL) Complete C{__init__}.
1573 '''
1574 if abc is None:
1575 n = _name__(**name)
1576 else:
1577 n = abc._name__(name)
1578 ltp = _xattr(abc, ltp=ltp)
1579 if ltp:
1580 inst._ltp = _xLtp(ltp)
1581 if n:
1582 inst.name = n
1585def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
1586 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
1587 '''
1588 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
1589 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
1590 t = strs(t, prec=3 if prec is None else prec)
1591 if sep:
1592 t = sep.join(t)
1593 if fmt:
1594 t = fmt(t)
1595 return a, t
1598def _xLtp(ltp, *dflt):
1599 '''(INTERNAL) Invoke C{ltp._xLtp}.
1600 '''
1601 return _MODS.ltp._xLtp(ltp, *dflt)
1604def _xyz2aer4(inst):
1605 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
1606 '''
1607 x, y, z, _ = inst.xyz4
1608 A = Azimuth(atan2b(x, y))
1609 E = Degrees(elevation=atan2d(z, hypot(x, y)))
1610 R = Meter(slantrange=hypot_(x, y, z))
1611 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
1614def _xyzLocal(*Types, **name_inst):
1615 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
1616 '''
1617 n, inst = _xkwds_item2(name_inst)
1618 if isinstance(inst, Types):
1619 return None
1620 try:
1621 return inst.xyzLocal
1622 except (AttributeError, TypeError):
1623 raise _TypeError(n, inst, txt_not_=_local_)
1626__all__ += _ALL_DOCS(_AbcBase)
1628# **) MIT License
1629#
1630# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1631#
1632# Permission is hereby granted, free of charge, to any person obtaining a
1633# copy of this software and associated documentation files (the "Software"),
1634# to deal in the Software without restriction, including without limitation
1635# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1636# and/or sell copies of the Software, and to permit persons to whom the
1637# Software is furnished to do so, subject to the following conditions:
1638#
1639# The above copyright notice and this permission notice shall be included
1640# in all copies or substantial portions of the Software.
1641#
1642# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1643# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1644# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1645# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1646# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1647# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1648# OTHER DEALINGS IN THE SOFTWARE.