Coverage for pygeodesy/ltpTuples.py: 95%
565 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-12 16:17 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-12 16:17 -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}
7and local 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
25from pygeodesy.named import _name__, _name1__, _name2__, _NamedBase, \
26 _NamedTuple, _Pass, _xnamed
27from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
28from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
29 Property_RO, property_RO
30from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
31from pygeodesy.units import Azimuth, Bearing, Degrees, Degrees_, Height, \
32 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_
33from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians
34from pygeodesy.vector3d import Vector3d
36# from math import cos, radians # from .utily
38__all__ = _ALL_LAZY.ltpTuples
39__version__ = '24.11.07'
41_aer_ = 'aer'
42_alt_ = 'alt'
43_down_ = 'down'
44_east_ = 'east'
45_enu_ = 'enu'
46_h__ = 'h_'
47_ltp = _MODS.into(ltp=__name__)
48_ned_ = 'ned'
49_north_ = 'north'
50_local_ = 'local'
51_roll_ = 'roll'
52_slantrange_ = 'slantrange'
53_tilt_ = 'tilt'
54_uvw_ = 'uvw'
55_yaw_ = 'yaw'
58def _er2gr(e, r):
59 '''(INTERNAL) Elevation and slant range to ground range.
60 '''
61 c = cos(radians(e))
62 return Meter_(groundrange=r * c)
65def _init(inst, abc, ltp, name):
66 '''(INTERNAL) Complete C{__init__}.
67 '''
68 if abc is None:
69 n = _name__(**name)
70 else:
71 n = abc._name__(name)
72 ltp = _xattr(abc, ltp=ltp)
73 if ltp:
74 inst._ltp = _ltp._xLtp(ltp)
75 if n:
76 inst.name = n
79def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
80 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
81 '''
82 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
83 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
84 t = strs(t, prec=3 if prec is None else prec)
85 if sep:
86 t = sep.join(t)
87 if fmt:
88 t = fmt(t)
89 return a, t
92def _xyz2aer4(inst):
93 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
94 '''
95 x, y, z, _ = inst.xyz4
96 A = Azimuth(atan2b(x, y))
97 E = Degrees(elevation=atan2d(z, hypot(x, y)))
98 R = Meter(slantrange=hypot_(x, y, z))
99 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
102def _xyzLocal(*Types, **name_inst):
103 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
104 '''
105 n, inst = _xkwds_item2(name_inst)
106 if isinstance(inst, Types):
107 return None
108 try:
109 return inst.xyzLocal
110 except (AttributeError, TypeError):
111 raise _TypeError(n, inst, txt_not_=_local_)
114class _AbcBase(_NamedBase):
115 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
116 '''
117 _ltp = None # local tangent plane (C{Ltp}), origin
119 @Property_RO
120 def ltp(self):
121 '''Get the I{local tangent plane} (L{Ltp}).
122 '''
123 return self._ltp
125 def toAer(self, Aer=None, **name_Aer_kwds):
126 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
128 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
129 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
130 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
131 is None}.
133 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
134 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
136 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
137 '''
138 return self.xyz4._toXyz(Aer, name_Aer_kwds)
140 def toEnu(self, Enu=None, **name_Enu_kwds):
141 '''Get the I{local} I{East, North, Up} (ENU) components.
143 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
144 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
145 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
146 is None}.
148 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
149 L{Enu4Tuple}C{(east, north, up, ltp)}.
151 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
152 '''
153 return self.xyz4._toXyz(Enu, name_Enu_kwds)
155 def toNed(self, Ned=None, **name_Ned_kwds):
156 '''Get the I{local} I{North, East, Down} (NED) components.
158 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
159 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
160 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
161 is None}.
163 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an
164 L{Ned4Tuple}C{(north, east, down, ltp)}.
166 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
167 '''
168 return self.xyz4._toXyz(Ned, name_Ned_kwds)
170 def toXyz(self, Xyz=None, **name_Xyz_kwds):
171 '''Get the local I{X, Y, Z} (XYZ) components.
173 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
174 or C{None}.
175 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
176 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
177 is None}.
179 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None}, an
180 L{Xyz4Tuple}C{(x, y, z, ltp)}.
182 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
183 '''
184 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
186 @Property_RO
187 def xyz(self):
188 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
189 '''
190 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
192 @property_RO
193 def xyz3(self):
194 '''Get the I{local} C{(X, Y, Z)} coordinates as C{3-tuple}.
195 '''
196 return tuple(self.xyz)
198 @property_RO
199 def xyz4(self): # PYCHOK no cover
200 '''I{Must be overloaded}.'''
201 self._notOverloaded()
203 @Property_RO
204 def xyzLocal(self):
205 '''Get this AER or NED as an L{XyzLocal}.
206 '''
207 return XyzLocal(self.xyz4, name=self.name)
210class _Abc4Tuple(_NamedTuple):
211 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple},
212 C{Ned4Tuple} and C{Xyz4Tuple}.
213 '''
214 def _2Cls(self, Abc, Cls, Cls_kwds):
215 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
216 '''
217 kwds = _name1__(Cls_kwds, _or_nameof=self)
218 _is = _MODS.basics.issubclassof
219 if Cls is None:
220 n, _ = _name2__(Cls_kwds)
221 r = self.copy(name=n) if n else self
222 elif _is(Cls, Abc):
223 r = Cls(*self, **kwds)
224 elif _is(Cls, Aer):
225 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls))
226 elif _is(Cls, Enu): # PYCHOK no cover
227 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls))
228 elif _is(Cls, Ned):
229 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls))
230 elif _is(Cls, XyzLocal): # PYCHOK no cover
231 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls))
232 elif Cls is Local9Tuple: # PYCHOK no cover
233 r = self.xyzLocal.toLocal9Tuple(**kwds)
234 else: # PYCHOK no cover
235 n = Abc.__name__[:3]
236 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
237 return r
239 @property_RO
240 def xyzLocal(self): # PYCHOK no cover
241 '''I{Must be overloaded}.'''
242 self._notOverloaded()
245class Aer(_AbcBase):
246 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
247 '''
248 _azimuth = _0_0 # bearing from North (C{degrees360})
249 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
250# _ltp = None # local tangent plane (C{Ltp}), origin
251 _slantrange = _0_0 # distance (C{Meter})
252 _toStr = _aer_
254 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name):
255 '''New L{Aer}.
257 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
258 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
259 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
260 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
261 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
262 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
263 only used with scalar B{C{azimuth_aer}}.
264 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
265 B{C{azimuth_aer}}.
266 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
267 L{LocalCartesian}).
268 @kwarg name: Optional C{B{name}=NN} (C{str}).
270 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
272 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
273 or B{C{slantrange}}.
274 '''
275 if _isDegrees(azimuth_aer):
276 aer = None
277 t = (Azimuth(azimuth_aer),
278 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0),
279 Meter_(slantrange=slantrange), ltp)
280 else: # PYCHOK no cover
281 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
282 aer = p.toAer() if p else azimuth_aer
283 t = aer.aer4
284 self._azimuth, self._elevation, self._slantrange, _ = t
285 _init(self, aer, ltp, name)
287 @Property_RO
288 def aer4(self):
289 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
290 '''
291 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
293 @Property_RO
294 def azimuth(self):
295 '''Get the Azimuth, bearing from North (C{degrees360}).
296 '''
297 return self._azimuth
299 @Property_RO
300 def down(self):
301 '''Get the Down component (C{meter}).
302 '''
303 return self.xyzLocal.down
305 @Property_RO
306 def east(self):
307 '''Get the East component (C{meter}).
308 '''
309 return self.xyzLocal.east
311 @Property_RO
312 def elevation(self):
313 '''Get the Elevation, tilt above horizon (C{degrees90}).
314 '''
315 return self._elevation
317 @Property_RO
318 def groundrange(self):
319 '''Get the I{ground range}, distance (C{meter}).
320 '''
321 return _er2gr(self._elevation, self._slantrange)
323 @Property_RO
324 def north(self):
325 '''Get the North component (C{meter}).
326 '''
327 return self.xyzLocal.north
329 @Property_RO
330 def slantrange(self):
331 '''Get the I{slant Range}, distance (C{meter}).
332 '''
333 return self._slantrange
335 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
336 '''Return a string representation of this AER as azimuth
337 (bearing), elevation and slant range.
339 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
340 @kwarg fmt: Enclosing backets format (C{str}).
341 @kwarg sep: Optional separator between AERs (C{str}).
343 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
344 '''
345 m = _MODS.dms
346 t = (m.toDMS(self.azimuth, form=m.F_D, prec=prec, ddd=0),
347 m.toDMS(self.elevation, form=m.F_D, prec=prec, ddd=0),
348 fstr( self.slantrange, prec=3 if prec is None else prec))
349 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
351 def toStr(self, **prec_fmt_sep): # PYCHOK expected
352 '''Return a string representation of this AER.
354 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
355 number of (decimal) digits, unstripped
356 (C{int}), C{B{fmt}='[]'} the enclosing
357 backets format (C{str}) and separator
358 C{B{sep}=", "} to join (C{str}).
360 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
361 '''
362 _, t = _toStr2(self, **prec_fmt_sep)
363 return t
365 @Property_RO
366 def up(self):
367 '''Get the Up component (C{meter}).
368 '''
369 return self.xyzLocal.up
371 @Property_RO
372 def x(self):
373 '''Get the X component (C{meter}).
374 '''
375 return self.xyz4.x
377 @Property_RO
378 def xyz4(self):
379 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
380 '''
381 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
382 R = self._slantrange
383 r = cE * R # ground range
384 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
386 @Property_RO
387 def y(self):
388 '''Get the Y component (C{meter}).
389 '''
390 return self.xyz4.y
392 @Property_RO
393 def z(self):
394 '''Get the Z component (C{meter}).
395 '''
396 return self.xyz4.z
399class Aer4Tuple(_Abc4Tuple):
400 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
401 all in C{meter} except C{ltp}.
402 '''
403 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
404 _Units_ = ( Meter, Meter, Meter, _Pass)
406 def _toAer(self, Cls, Cls_kwds):
407 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
408 '''
409 return self._2Cls(Aer, Cls, Cls_kwds)
411 @Property_RO
412 def groundrange(self):
413 '''Get the I{ground range}, distance (C{meter}).
414 '''
415 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
417 @Property_RO
418 def xyzLocal(self):
419 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
420 '''
421 return Aer(self).xyzLocal
424class Attitude4Tuple(_NamedTuple):
425 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
426 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
427 the attitude of a plane or camera.
428 '''
429 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
430 _Units_ = ( Meter, Degrees, Bearing, Degrees)
432 @Property_RO
433 def atyr(self):
434 '''Return this attitude (L{Attitude4Tuple}).
435 '''
436 return self
438 @Property_RO
439 def tyr3d(self):
440 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
441 '''
442 return _ltp.Attitude(self).tyr3d
445class Ned(_AbcBase):
446 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
448 @see: L{Enu} and L{Ltp}.
449 '''
450 _down = _0_0 # down, -XyzLocal.z (C{meter}).
451 _east = _0_0 # east, XyzLocal.y (C{meter}).
452# _ltp = None # local tangent plane (C{Ltp}), origin
453 _north = _0_0 # north, XyzLocal.x (C{meter})
454 _toStr = _ned_
456 def __init__(self, north_ned, east=0, down=0, ltp=None, **name):
457 '''New L{Ned} vector.
459 @arg north_ned: Scalar North component (C{meter}) or a previous
460 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
461 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
462 L{XyzLocal} or L{Xyz4Tuple}).
463 @kwarg east: Scalar East component (C{meter}), only used with
464 scalar B{C{north_ned}}.
465 @kwarg down: Scalar Down component, normal to I{inside} surface
466 of the ellipsoid or sphere (C{meter}), only used with
467 scalar B{C{north_ned}}.
468 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
469 L{LocalCartesian}).
470 @kwarg name: Optional C{B{name}=NN} (C{str}).
472 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
474 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
475 '''
476 if _isMeter(north_ned):
477 ned = None
478 t = (Meter(north=north_ned or _0_0),
479 Meter(east=east or _0_0),
480 Meter(down=down or _0_0), ltp)
481 else: # PYCHOK no cover
482 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
483 ned = p.toNed() if p else north_ned
484 t = ned.ned4
485 self._north, self._east, self._down, _ = t
486 _init(self, ned, ltp, name)
488 @Property_RO
489 def aer4(self):
490 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
491 '''
492 return _xyz2aer4(self)
494 @Property_RO
495 def azimuth(self):
496 '''Get the Azimuth, bearing from North (C{degrees360}).
497 '''
498 return self.aer4.azimuth
500 @deprecated_Property_RO
501 def bearing(self):
502 '''DEPRECATED, use C{azimuth}.'''
503 return self.azimuth
505 @Property_RO
506 def down(self):
507 '''Get the Down component (C{meter}).
508 '''
509 return self._down
511 @Property_RO
512 def east(self):
513 '''Get the East component (C{meter}).
514 '''
515 return self._east
517 @Property_RO
518 def elevation(self):
519 '''Get the Elevation, tilt above horizon (C{degrees90}).
520 '''
521 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
523 @Property_RO
524 def groundrange(self):
525 '''Get the I{ground range}, distance (C{meter}).
526 '''
527 return Meter(groundrange=hypot(self.north, self.east))
529 @deprecated_Property_RO
530 def length(self):
531 '''DEPRECATED, use C{slantrange}.'''
532 return self.slantrange
534 @deprecated_Property_RO
535 def ned(self):
536 '''DEPRECATED, use property C{ned4}.'''
537 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
539 @Property_RO
540 def ned4(self):
541 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
542 '''
543 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
545 @Property_RO
546 def north(self):
547 '''Get the North component (C{meter}).
548 '''
549 return self._north
551 @Property_RO
552 def slantrange(self):
553 '''Get the I{slant Range}, distance (C{meter}).
554 '''
555 return self.aer4.slantrange
557 @deprecated_method
558 def to3ned(self): # PYCHOK no cover
559 '''DEPRECATED, use property L{ned4}.'''
560 return self.ned # XXX deprecated too
562 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
563 '''Return a string representation of this NED.
565 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
566 @kwarg fmt: Enclosing backets format (C{str}).
567 @kwarg sep: Separator to join (C{str}).
569 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
570 '''
571 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
572 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
574 def toStr(self, **prec_fmt_sep): # PYCHOK expected
575 '''Return a string representation of this NED.
577 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
578 number of (decimal) digits, unstripped
579 (C{int}), C{B{fmt}='[]'} the enclosing
580 backets format (C{str}) and separator
581 C{B{sep}=", "} to join (C{str}).
583 @return: This NED as "[meter, meter, meter]" (C{str}).
584 '''
585 _, t = _toStr2(self, **prec_fmt_sep)
586 return t
588 @deprecated_method
589 def toVector3d(self):
590 '''DEPRECATED, use property L{xyz}.'''
591 return self.xyz
593 @Property_RO
594 def up(self):
595 '''Get the Up component (C{meter}).
596 '''
597 return Meter(up=-self._down) # negated
599 @Property_RO
600 def x(self):
601 '''Get the X component (C{meter}).
602 '''
603 return Meter(x=self._east) # 2nd arg, E
605 @Property_RO
606 def xyz4(self):
607 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
608 '''
609 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
611 @Property_RO
612 def y(self):
613 '''Get the Y component (C{meter}).
614 '''
615 return Meter(y=self._north) # 1st arg N
617 @Property_RO
618 def z(self):
619 '''Get the Z component (C{meter}).
620 '''
621 return Meter(z=-self._down) # negated
624class Ned4Tuple(_Abc4Tuple):
625 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
626 '''
627 _Names_ = (_north_, _east_, _down_, _ltp_)
628 _Units_ = ( Meter, Meter, Meter, _Pass)
630 def _toNed(self, Cls, Cls_kwds):
631 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
632 '''
633 return self._2Cls(Ned, Cls, Cls_kwds)
635 @Property_RO
636 def xyzLocal(self):
637 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
638 '''
639 return Ned(self).xyzLocal
642class _Vector3d(Vector3d):
644 _toStr = _xyz_
646 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
647 '''Return a string representation of this ENU/NED/XYZ.
649 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
650 @kwarg fmt: Enclosing backets format (C{str}).
651 @kwarg sep: Separator to join (C{str}).
653 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
654 "[N:meter, E:meter, D:meter]",
655 "[U:meter, V:meter, W:meter]" respectively
656 "[X:meter, Y:meter, Z:meter]" (C{str}).
657 '''
658 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
659 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
661 def toStr(self, **prec_fmt_sep): # PYCHOK expected
662 '''Return a string representation of this XYZ.
664 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
665 number of (decimal) digits, unstripped
666 (C{int}), C{B{fmt}='[]'} the enclosing
667 backets format (C{str}) and separator
668 C{B{sep}=", "} to join (C{str}).
670 @return: This XYZ as "[meter, meter, meter]" (C{str}).
671 '''
672 _, t = _toStr2(self, **prec_fmt_sep)
673 return t
676class XyzLocal(_Vector3d):
677 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
678 also base class for local L{Enu}.
679 '''
680 _ltp = None # local tangent plane (C{Ltp}), origin
682 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name):
683 '''New L{XyzLocal}.
685 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
686 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
687 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
688 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
689 @kwarg y: Scalar Y component (C{meter}), only used with scalar
690 B{C{x_xyz}}, C{positive north}.
691 @kwarg z: Scalar Z component, normal C{positive up} from the
692 surface of the ellipsoid or sphere (C{meter}), only
693 used with scalar B{C{x_xyz}}.
694 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
695 L{LocalCartesian}).
696 @kwarg name: Optional C{B{name}=NN} (C{str}).
698 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
700 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
701 '''
702 if _isMeter(x_xyz):
703 xyz = None
704 t = (Meter(x=x_xyz or _0_0),
705 Meter(y=y or _0_0),
706 Meter(z=z or _0_0), ltp)
707 else:
708 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
709 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp
710 self._x, self._y, self._z, _ = t
711 _init(self, xyz, ltp, name)
713 def __str__(self):
714 return self.toStr()
716 @Property_RO
717 def aer4(self):
718 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
719 '''
720 return _xyz2aer4(self)
722 @Property_RO
723 def azimuth(self):
724 '''Get the Azimuth, bearing from North (C{degrees360}).
726 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
727 Transformations_between_ECEF_and_ENU_coordinates>}.
728 '''
729 return self.aer4.azimuth
731 def classof(self, *args, **kwds): # PYCHOK no cover
732 '''Create another instance of this very class.
734 @arg args: Optional, positional arguments.
735 @kwarg kwds: Optional, keyword arguments.
737 @return: New instance (C{self.__class__}).
738 '''
739 kwds = _name1__(kwds, _or_nameof=self)
740 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp))
742 @Property_RO
743 def down(self):
744 '''Get the Down component (C{meter}).
745 '''
746 return Meter(down=-self.z)
748 @property_RO
749 def ecef(self):
750 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
751 '''
752 return self.ltp.ecef
754 @Property_RO
755 def east(self):
756 '''Get the East component (C{meter}).
757 '''
758 return Meter(east=self.x)
760 @Property_RO
761 def elevation(self):
762 '''Get the Elevation, tilt above horizon (C{degrees90}).
764 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
765 Transformations_between_ECEF_and_ENU_coordinates>}.
766 '''
767 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
769 @Property_RO
770 def enu4(self):
771 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
772 '''
773 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
775 @Property_RO
776 def groundrange(self):
777 '''Get the I{ground range}, distance (C{meter}).
778 '''
779 return Meter(groundrange=hypot(self.x, self.y))
781 @Property_RO
782 def ltp(self):
783 '''Get the I{local tangent plane} (L{Ltp}).
784 '''
785 return self._ltp
787 def _ltp_kwds_name3(self, ltp, kwds):
788 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}.
789 '''
790 ltp = _ltp._xLtp(ltp, self.ltp)
791 kwds = _name1__(kwds, _or_nameof=self)
792 kwds = _name1__(kwds, _or_nameof=ltp)
793 return ltp, kwds, kwds.get(_name_, NN)
795 @Property_RO
796 def ned4(self):
797 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
798 '''
799 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
801 @Property_RO
802 def north(self):
803 '''Get the North component (C{meter}).
804 '''
805 return Meter(north=self.y)
807 @Property_RO
808 def slantrange(self):
809 '''Get the I{slant Range}, distance (C{meter}).
810 '''
811 return self.aer4.slantrange
813 def toAer(self, Aer=None, **name_Aer_kwds):
814 '''Get the local I{Azimuth, Elevation, slant Range} components.
816 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
817 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
818 additional B{C{Aer}} keyword arguments, ignored if C{B{Aer}
819 is None}.
821 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
822 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
824 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
825 '''
826 return self.aer4._toAer(Aer, name_Aer_kwds)
828 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds):
829 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
831 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
832 or C{None}.
833 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
834 this C{ltp}.
835 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
836 additional B{C{Cartesian}} keyword arguments, ignored if
837 C{B{Cartesian} is None}.
839 @return: A B{C{Cartesian}} instance or if C{B{Cartesian} is None}, an
840 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
841 C{M=None}, always.
843 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or B{C{name_Cartesian_kwds}}.
844 '''
845 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds)
846 if Cartesian is None:
847 t = ltp._local2ecef(self, nine=True)
848 r = _xnamed(t, n) if n else t
849 else:
850 kwds = _xkwds(kwds, datum=ltp.datum)
851 xyz = ltp._local2ecef(self) # [:3]
852 r = Cartesian(*xyz, **kwds)
853 return r
855 def toEnu(self, Enu=None, **name_Enu_kwds):
856 '''Get the local I{East, North, Up} (ENU) components.
858 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
859 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
860 additional B{C{Enu}} keyword arguments, ignored if C{B{Enu}
861 is None}.
863 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
864 L{Enu4Tuple}C{(east, north, up, ltp)}.
866 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
867 '''
868 return self.enu4._toEnu(Enu, name_Enu_kwds)
870 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds):
871 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
873 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon}) or
874 C{None}.
875 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
876 this ENU/NED/AER/XYZ's LTP.
877 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
878 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
879 is None}.
881 @return: An B{C{LatLon}} instance or if C{B{LatLon} is None}, an
882 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
883 C{M=None}, always.
885 @raise TypeError: Invalid B{C{LatLon}}, B{C{ltp}} or B{C{name_LatLon_kwds}}.
886 '''
887 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds)
888 t = ltp._local2ecef(self, nine=True)
889 if LatLon is None:
890 r = _xnamed(t, n) if n else t
891 else:
892 kwds = _xkwds(kwds, height=t.height, datum=t.datum)
893 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp?
894 return r
896 def toLocal9Tuple(self, M=False, **name):
897 '''Get this local as a C{Local9Tuple}.
899 @kwarg M: Optionally include the rotation matrix (C{bool}).
900 @kwarg name: Optional C{B{name}=NN} (C{str}).
902 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)}
903 with C{ltp} this C{Ltp}, C{ecef} an L{Ecef9Tuple} and C{M}
904 an L{EcefMatrix} or C{None}.
905 '''
906 ltp = self.ltp # see C{self.toLatLon}
907 t = ltp._local2ecef(self, nine=True, M=M)
908 return Local9Tuple(self.x, self.y, self.z,
909 t.lat, t.lon, t.height,
910 ltp, t, t.M, name=t._name__(name))
912 def toNed(self, Ned=None, **name_Ned_kwds):
913 '''Get the local I{North, East, Down} (Ned) components.
915 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
916 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
917 additional B{C{Ned}} keyword arguments, ignored if C{B{Ned}
918 is None}.
920 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an
921 L{Ned4Tuple}C{(north, east, down, ltp)}.
923 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
924 '''
925 return self.ned4._toNed(Ned, name_Ned_kwds)
927 def toXyz(self, Xyz=None, **name_Xyz_kwds):
928 '''Get the local I{X, Y, Z} (XYZ) components.
930 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
931 or C{None}.
932 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
933 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
934 is None}.
936 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None}, an
937 L{Xyz4Tuple}C{(x, y, z, ltp)}.
939 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_EXyz_kwds}}.
940 '''
941 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
943 @Property_RO
944 def up(self):
945 '''Get the Up component (C{meter}).
946 '''
947 return Meter(up=self.z)
949# @Property_RO
950# def x(self): # see: Vector3d.x
951# '''Get the X component (C{meter}).
952# '''
953# return self._x
955# @Property_RO
956# def xyz(self): # see: Vector3d.xyz
957# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
958# '''
959# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
961 @Property_RO
962 def xyz4(self):
963 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
964 '''
965 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
967 @Property_RO
968 def xyzLocal(self):
969 '''Get this L{XyzLocal}.
970 '''
971 return self
973# @Property_RO
974# def y(self): # see: Vector3d.y
975# '''Get the Y component (C{meter}).
976# '''
977# return self._y
979# @Property_RO
980# def z(self): # see: Vector3d.z
981# '''Get the Z component (C{meter}).
982# '''
983# return self._z
986class Xyz4Tuple(_Abc4Tuple):
987 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
988 '''
989 _Names_ = (_x_, _y_, _z_, _ltp_)
990 _Units_ = ( Meter, Meter, Meter, _Pass)
992 def _toXyz(self, Cls, Cls_kwds):
993 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
994 '''
995 return self._2Cls(XyzLocal, Cls, Cls_kwds)
997 @property_RO
998 def xyz4(self):
999 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
1000 '''
1001 return self
1003 @Property_RO
1004 def xyzLocal(self):
1005 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
1006 '''
1007 return XyzLocal(*self, name=self.name)
1010class Enu(XyzLocal):
1011 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
1013 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
1014 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
1015 '''
1016 _toStr = _enu_
1018 def __init__(self, east_enu, north=0, up=0, ltp=None, **name):
1019 '''New L{Enu}.
1021 @arg east_enu: Scalar East component (C{meter}) or a previous
1022 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
1023 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
1024 L{XyzLocal} or L{Xyz4Tuple}).
1025 @kwarg north: Scalar North component (C{meter}) only used with
1026 scalar B{C{east_enu}}.
1027 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
1028 normal from the surface of the ellipsoid or sphere (C{meter}).
1029 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
1030 L{LocalCartesian}).
1031 @kwarg name: Optional C{B{name}=NN} (C{str}).
1033 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
1035 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
1036 '''
1037 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name)
1039 def toUvw(self, location, Uvw=None, **name_Uvw_kwds):
1040 '''Get the I{u, v, w} (UVW) components at a location.
1042 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1043 L{Vector3d}) location, like a Point-Of-View.
1044 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
1045 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1046 additional B{L{Uvw}} keyword arguments, ignored if C{B{Uvw}
1047 is None}.
1049 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
1050 L{Uvw3Tuple}C{(u, v, w)}.
1052 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}}.
1054 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1055 '''
1056 try:
1057 sa, ca, sb, cb = sincos2_(*location.philam)
1058 except Exception as x:
1059 raise _TypeError(location=location, cause=x)
1060 e, n, u, _ = self.enu4
1062 t = fdot_(ca, u, -sa, n)
1063 U = fdot_(cb, t, -sb, e)
1064 V = fdot_(cb, e, sb, t)
1065 W = fdot_(ca, n, sa, u)
1067 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self)
1068 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \
1069 Uvw(U, V, W, name=n, **kwds)
1071 @Property_RO
1072 def xyzLocal(self):
1073 '''Get this ENU as an L{XyzLocal}.
1074 '''
1075 return XyzLocal(*self.xyz4, name=self.name)
1078class Enu4Tuple(_Abc4Tuple):
1079 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1080 '''
1081 _Names_ = (_east_, _north_, _up_, _ltp_)
1082 _Units_ = ( Meter, Meter, Meter, _Pass)
1084 def _toEnu(self, Cls, Cls_kwds):
1085 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1086 '''
1087 return self._2Cls(Enu, Cls, Cls_kwds)
1089 @Property_RO
1090 def xyzLocal(self):
1091 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1092 '''
1093 return XyzLocal(*self, name=self.name)
1096class Local9Tuple(_NamedTuple):
1097 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1098 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1099 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1100 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1101 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1102 '''
1103 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1104 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1106 @Property_RO
1107 def azimuth(self):
1108 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1109 '''
1110 return self.xyzLocal.aer4.azimuth
1112 @Property_RO
1113 def down(self):
1114 '''Get the I{local} Down, C{-z} component (C{meter}).
1115 '''
1116 return -self.z
1118 @Property_RO
1119 def east(self):
1120 '''Get the I{local} East, C{x} component (C{meter}).
1121 '''
1122 return self.x
1124 @Property_RO
1125 def elevation(self):
1126 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1127 '''
1128 return self.xyzLocal.aer4.elevation
1130 @Property_RO
1131 def groundrange(self):
1132 '''Get the I{local} ground range, distance (C{meter}).
1133 '''
1134 return self.xyzLocal.aer4.groundrange
1136 @Property_RO
1137 def lam(self):
1138 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1139 '''
1140 return self.philam.lam
1142 @Property_RO
1143 def latlon(self):
1144 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1145 '''
1146 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1148 @Property_RO
1149 def latlonheight(self):
1150 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1151 '''
1152 return self.latlon.to3Tuple(self.height)
1154 @Property_RO
1155 def north(self):
1156 '''Get the I{local} North, C{y} component (C{meter}).
1157 '''
1158 return self.y
1160 @Property_RO
1161 def phi(self):
1162 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1163 '''
1164 return self.philam.phi
1166 @Property_RO
1167 def philam(self):
1168 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1169 '''
1170 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1172 @Property_RO
1173 def philamheight(self):
1174 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1175 '''
1176 return self.philam.to3Tuple(self.height)
1178 @Property_RO
1179 def slantrange(self):
1180 '''Get the I{local} slant Range, distance (C{meter}).
1181 '''
1182 return self.xyzLocal.aer4.slantrange
1184 def toAer(self, Aer=None, **name_Aer_kwds):
1185 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1187 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1188 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1189 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
1190 is None}.
1192 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
1193 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1195 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
1196 '''
1197 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds)
1199 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds):
1200 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1202 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1203 or C{None}.
1204 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1205 additional B{C{Cartesian}} keyword arguments, ignored if
1206 C{B{Cartesian} is None}.
1208 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or
1209 if C{B{Cartesian} is None}, a L{Vector4Tuple}C{(x, y, z, h)} .
1211 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}}.
1212 '''
1213 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple
1215 def toEnu(self, Enu=None, **name_Enu_kwds):
1216 '''Get the I{local} I{East, North, Up} (ENU) components.
1218 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1219 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1220 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1221 is None}.
1223 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1224 L{Enu4Tuple}C{(east, north, up, ltp)}.
1226 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
1227 '''
1228 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds)
1230 def toLatLon(self, LatLon=None, **name_LatLon_kwds):
1231 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1233 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1234 (C{LatLon}) or C{None}.
1235 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1236 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
1237 is None}.
1239 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})} or if
1240 C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon, height)}
1241 respectively L{LatLon4Tuple}C{(lat, lon, height, datum)}
1242 depending on whether C{datum} is un-/specified.
1244 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1245 '''
1246 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple
1248 def toNed(self, Ned=None, **name_Ned_kwds):
1249 '''Get the I{local} I{North, East, Down} (NED) components.
1251 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1252 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1253 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
1254 is None}.
1256 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an
1257 L{Ned4Tuple}C{(north, east, down, ltp)}.
1259 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
1260 '''
1261 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds)
1263 def toXyz(self, Xyz=None, **name_Xyz_kwds):
1264 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1266 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1267 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1268 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
1269 is None}.
1271 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1272 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1274 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
1275 '''
1276 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds)
1278 @Property_RO
1279 def up(self):
1280 '''Get the I{local} Up, C{z} component (C{meter}).
1281 '''
1282 return self.z
1284 @Property_RO
1285 def xyz(self):
1286 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1287 '''
1288 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1290 @Property_RO
1291 def xyzLocal(self):
1292 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1293 '''
1294 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1297_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1298_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1301class Uvw(_Vector3d):
1302 '''3-D C{u-v-w} (UVW) components.
1303 '''
1304 _toStr = _uvw_
1306 def __init__(self, u_uvw, v=0, w=0, **name):
1307 '''New L{Uvw}.
1309 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1310 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1311 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1312 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1313 @kwarg name: Optional C{B{name}=NN} (C{str}).
1315 @raise TypeError: Invalid B{C{east_enu}}.
1317 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1318 '''
1319 Vector3d.__init__(self, u_uvw, v, w, **name)
1321 def toEnu(self, location, Enu=Enu, **name_Enu_kwds):
1322 '''Get the I{East, North, Up} (ENU) components at a location.
1324 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1325 L{Vector3d}) location from where to cast the L{Los}.
1326 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1327 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1328 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1329 is None}.
1331 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1332 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1334 @raise TypeError: Invalid B{C{location}} or B{C{name_Enu_kwds}}.
1336 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1337 '''
1338 try:
1339 sa, ca, sb, cb = sincos2_(*location.philam)
1340 except Exception as x:
1341 raise _TypeError(location=location, cause=x)
1342 u, v, w = self.uvw
1344 t = fdot_(cb, u, sb, v)
1345 E = fdot_(cb, v, -sb, u)
1346 N = fdot_(ca, w, -sa, t)
1347 U = fdot_(ca, t, sa, w)
1349 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self)
1350 return Enu4Tuple(E, N, U, name=n) if Enu is None else \
1351 Enu(E, N, U, name=n, **kwds)
1353 u = Vector3d.x
1355 @Property_RO
1356 def uvw(self):
1357 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1358 '''
1359 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1361 v = Vector3d.y
1362 w = Vector3d.z
1365class Uvw3Tuple(_NamedTuple):
1366 '''3-Tuple C{(u, v, w)}, in C{meter}.
1367 '''
1368 _Names_ = ('u', 'v', 'w')
1369 _Units_ = ( Meter, Meter, Meter)
1372class Los(Aer):
1373 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1374 '''
1376 def __init__(self, azimuth_aer, elevation=0, **name):
1377 '''New L{Los}.
1379 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1380 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1381 L{Enu4Tuple} or L{Los}).
1382 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1383 is 0, zenith +90, nadir -90), only used with scalar
1384 B{C{azimuth_aer}}.
1385 @kwarg name: Optional C{B{name}=NN} (C{str}).
1387 @raise TypeError: Invalid B{C{azimuth_aer}}.
1389 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1390 '''
1391 t = Aer(azimuth_aer, elevation)
1392 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name)
1394 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds):
1395 '''Get this LOS' I{target} (UVW) components from a location.
1397 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1398 L{Vector3d}) location from where to cast this LOS.
1400 @see: Method L{Enu.toUvw} for further details.
1401 '''
1402 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds)
1404 def toEnu(self, Enu=Enu, **name_Enu_kwds):
1405 '''Get this LOS as I{East, North, Up} (ENU) components.
1407 @see: Method L{Aer.toEnu} for further details.
1408 '''
1409 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds)
1412class ChLV9Tuple(Local9Tuple):
1413 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1414 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1415 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1416 otherwise like L{Local9Tuple}.
1417 '''
1418 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1420 @Property_RO
1421 def E_LV95(self):
1422 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1423 '''
1424 return self.EN2_LV95.E_LV95
1426 @Property_RO
1427 def EN2_LV95(self):
1428 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1429 '''
1430 return ChLVEN2Tuple(*_ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1432 @Property_RO
1433 def h_LV03(self):
1434 '''Get the I{Swiss h_} height (C{meter}).
1435 '''
1436 return self.h_
1438 @Property_RO
1439 def h_LV95(self):
1440 '''Get the I{Swiss h_} height (C{meter}).
1441 '''
1442 return self.h_
1444 @property_RO
1445 def isChLV(self):
1446 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1447 '''
1448 return self.ltp.__class__ is _ltp.ChLV
1450 @property_RO
1451 def isChLVa(self):
1452 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1453 '''
1454 return self.ltp.__class__ is _ltp.ChLVa
1456 @property_RO
1457 def isChLVe(self):
1458 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1459 '''
1460 return self.ltp.__class__ is _ltp.ChLVe
1462 @Property_RO
1463 def N_LV95(self):
1464 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1465 '''
1466 return self.EN2_LV95.N_LV95
1468 @Property_RO
1469 def x(self):
1470 '''Get the I{local x, Swiss Y} easting (C{meter}).
1471 '''
1472 return self.Y
1474 @Property_RO
1475 def x_LV03(self):
1476 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1477 '''
1478 return self.yx2_LV03.x_LV03
1480 @Property_RO
1481 def y(self):
1482 '''Get the I{local y, Swiss X} northing (C{meter}).
1483 '''
1484 return self.X
1486 @Property_RO
1487 def y_LV03(self):
1488 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1489 '''
1490 return self.yx2_LV03.y_LV03
1492 @Property_RO
1493 def YX(self):
1494 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1495 '''
1496 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1498 @Property_RO
1499 def yx2_LV03(self):
1500 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1501 '''
1502 return ChLVyx2Tuple(*_ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1504 @Property_RO
1505 def z(self):
1506 '''Get the I{local z, Swiss h_} height (C{meter}).
1507 '''
1508 return self.h_
1511class ChLVYX2Tuple(_NamedTuple):
1512 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1513 in C{meter}.
1514 '''
1515 _Names_ = (_Y_, _X_)
1516 _Units_ = ( Meter, Meter)
1518 def false2(self, LV95=True):
1519 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1521 @see: Function L{ChLV.false2} for more information.
1522 '''
1523 return _ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1526class ChLVEN2Tuple(_NamedTuple):
1527 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1528 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1529 '''
1530 _Names_ = ('E_LV95', 'N_LV95')
1531 _Units_ = ChLVYX2Tuple._Units_
1533 def unfalse2(self):
1534 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1536 @see: Function L{ChLV.unfalse2} for more information.
1537 '''
1538 return _ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1541class ChLVyx2Tuple(_NamedTuple):
1542 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1543 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1544 '''
1545 _Names_ = ('y_LV03', 'x_LV03')
1546 _Units_ = ChLVYX2Tuple._Units_
1548 def unfalse2(self):
1549 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1551 @see: Function L{ChLV.unfalse2} for more information.
1552 '''
1553 return _ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1556class Footprint5Tuple(_NamedTuple):
1557 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1558 with the C{center} and 4 corners of the I{local} projection of
1559 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1561 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1562 '''
1563 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1564 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1566 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds):
1567 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1568 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1570 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1571 footprint's C{center} or C{frustrum} C{ltp}.
1572 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1573 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1574 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
1575 is None}.
1577 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, **B{name_LatLon_kwds})}
1578 instances or if C{B{LatLon} is None}, 5 L{LatLon3Tuple}C{(lat, lon,
1579 height)}s respectively 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s
1580 depending on whether keyword argument C{datum} is un-/specified.
1582 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1584 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1585 '''
1586 ltp = _ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center
1587 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)
1588 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon)
1589 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1591 def xyzLocal5(self, ltp=None):
1592 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1594 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1595 the {center} and corner C{ltp}s.
1597 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1599 @raise TypeError: Invalid B{C{ltp}}.
1600 '''
1601 if ltp is None:
1602 p = self
1603 else:
1604 p = _ltp._xLtp(ltp)
1605 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1606 return Footprint5Tuple(t.xyzLocal for t in p)
1609__all__ += _ALL_DOCS(_AbcBase)
1611# **) MIT License
1612#
1613# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1614#
1615# Permission is hereby granted, free of charge, to any person obtaining a
1616# copy of this software and associated documentation files (the "Software"),
1617# to deal in the Software without restriction, including without limitation
1618# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1619# and/or sell copies of the Software, and to permit persons to whom the
1620# Software is furnished to do so, subject to the following conditions:
1621#
1622# The above copyright notice and this permission notice shall be included
1623# in all copies or substantial portions of the Software.
1624#
1625# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1626# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1627# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1628# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1629# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1630# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1631# OTHER DEALINGS IN THE SOFTWARE.