Coverage for pygeodesy/ltpTuples.py: 94%
570 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-01 11:43 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-01 11:43 -0400
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 # from .units
15from pygeodesy.constants import _0_0, _1_0, _90_0, _N_90_0
16from pygeodesy.dms import F_D, toDMS
17from pygeodesy.errors import _TypeError, _TypesError, _xattr, \
18 _xkwds, _xkwds_item2
19from pygeodesy.fmath import 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 Bearing, Degrees, Degrees_, Height, _isDegrees, \
32 _isMeter, Lat, Lon, Meter, Meter_, issubclassof
33from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_
34from pygeodesy.vector3d import Vector3d
36from math import cos, radians
38__all__ = _ALL_LAZY.ltpTuples
39__version__ = '24.05.31'
41_aer_ = 'aer'
42_alt_ = 'alt'
43_down_ = 'down'
44_east_ = 'east'
45_enu_ = 'enu'
46_h__ = 'h_'
47_ned_ = 'ned'
48_north_ = 'north'
49_local_ = 'local'
50_roll_ = 'roll'
51_slantrange_ = 'slantrange'
52_tilt_ = 'tilt'
53_uvw_ = 'uvw'
54_yaw_ = 'yaw'
57def _er2gr(e, r):
58 '''(INTERNAL) Elevation and slant range to ground range.
59 '''
60 c = cos(radians(e))
61 return Meter_(groundrange=r * c)
64def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
65 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
66 '''
67 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
68 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
69 t = strs(t, prec=3 if prec is None else prec)
70 if sep:
71 t = sep.join(t)
72 if fmt:
73 t = fmt(t)
74 return a, t
77def _4Tuple2Cls(inst, Cls, Cls_kwds):
78 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
79 '''
80 if Cls is None:
81 return inst
82 elif issubclassof(Cls, Aer):
83 return inst.xyzLocal.toAer(Aer=Cls, **Cls_kwds)
84 elif issubclassof(Cls, Enu): # PYCHOK no cover
85 return inst.xyzLocal.toEnu(Enu=Cls, **Cls_kwds)
86 elif issubclassof(Cls, Ned):
87 return inst.xyzLocal.toNed(Ned=Cls, **Cls_kwds)
88 elif issubclassof(Cls, XyzLocal): # PYCHOK no cover
89 return inst.xyzLocal.toXyz(Xyz=Cls, **Cls_kwds)
90 elif Cls is Local9Tuple: # PYCHOK no cover
91 return inst.xyzLocal.toLocal9Tuple(**Cls_kwds)
92 n = inst.__class__.__name__[:3] # PYCHOK no cover
93 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
96def _xyz2aer4(inst):
97 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
98 '''
99 x, y, z, _ = inst.xyz4
100 A = Bearing(azimuth=atan2b(x, y))
101 E = Degrees(elevation=atan2d(z, hypot(x, y)))
102 R = Meter(slantrange=hypot_(x, y, z))
103 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
106def _xyzLocal(*Types, **name_inst):
107 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
108 '''
109 n, inst = _xkwds_item2(name_inst)
110 if isinstance(inst, Types):
111 return None
112 try:
113 return inst.xyzLocal
114 except (AttributeError, TypeError):
115 raise _TypeError(n, inst, txt_not_=_local_)
118class _NamedAerNed(_NamedBase):
119 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
120 '''
121 _ltp = None # local tangent plane (C{Ltp}), origin
123 @Property_RO
124 def ltp(self):
125 '''Get the I{local tangent plane} (L{Ltp}).
126 '''
127 return self._ltp
129 def toAer(self, Aer=None, **name_Aer_kwds):
130 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
132 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
133 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and
134 optional, additional B{L{Aer}} keyword arguments,
135 ignored if B{C{Aer}} is C{None}.
137 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
138 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
140 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
141 '''
142 return self.xyz4._toXyz(Aer, name_Aer_kwds)
144 def toEnu(self, Enu=None, **name_Enu_kwds):
145 '''Get the I{local} I{East, North, Up} (ENU) components.
147 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
148 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and
149 optional, additional B{L{Enu}} keyword arguments,
150 ignored if C{B{Enu} is None}.
152 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
153 an L{Enu4Tuple}C{(east, north, up, ltp)}.
155 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
156 '''
157 return self.xyz4._toXyz(Enu, name_Enu_kwds)
159 def toNed(self, Ned=None, **name_Ned_kwds):
160 '''Get the I{local} I{North, East, Down} (NED) components.
162 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
163 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and
164 optional, additional B{L{Ned}} keyword arguments,
165 ignored if B{C{Ned}} is C{None}.
167 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
168 an L{Ned4Tuple}C{(north, east, down, ltp)}.
170 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
171 '''
172 return self.xyz4._toXyz(Ned, name_Ned_kwds)
174 def toXyz(self, Xyz=None, **name_Xyz_kwds):
175 '''Get the local I{X, Y, Z} (XYZ) components.
177 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
178 L{Ned}, L{Aer}) or C{None}.
179 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and
180 optional, additional B{C{Xyz}} keyword arguments,
181 ignored if C{B{Xyz} is None}.
183 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
184 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
186 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
187 '''
188 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
190 @Property_RO
191 def xyz(self):
192 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
193 '''
194 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
196 @property_RO
197 def xyz4(self): # PYCHOK no cover
198 '''I{Must be overloaded}.'''
199 self._notOverloaded()
201 @Property_RO
202 def xyzLocal(self):
203 '''Get this AER or NED as an L{XyzLocal}.
204 '''
205 return XyzLocal(self.xyz4, name=self.name)
208class Aer(_NamedAerNed):
209 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
210 '''
211 _azimuth = _0_0 # bearing from North (C{degrees360})
212 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
213# _ltp = None # local tangent plane (C{Ltp}), origin
214 _slantrange = _0_0 # distance (C{Meter})
215 _toStr = _aer_
217 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name):
218 '''New L{Aer}.
220 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
221 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
222 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
223 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
224 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
225 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
226 only used with scalar B{C{azimuth_aer}}.
227 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
228 B{C{azimuth_aer}}.
229 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
230 L{LocalCartesian}).
231 @kwarg name: Optional C{B{name}=NN} (C{str}).
233 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
235 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
236 or B{C{slantrange}}.
237 '''
238 if _isDegrees(azimuth_aer):
239 self._azimuth = Bearing(azimuth=azimuth_aer)
240 self._elevation = Degrees_(elevation=elevation, low=_N_90_0, high=_90_0)
241 self._slantrange = Meter_(slantrange=slantrange)
242 p, n = ltp, _name__(**name)
243 else: # PYCHOK no cover
244 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
245 aer = p.toAer() if p else azimuth_aer
246 self._azimuth, self._elevation, self._slantrange = \
247 aer.azimuth, aer.elevation, aer.slantrange
248 p = _xattr(aer, ltp=ltp)
249 n = aer._name__(name)
251 if p:
252 self._ltp = _MODS.ltp._xLtp(p)
253 if n:
254 self.name = n
256 @Property_RO
257 def aer4(self):
258 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
259 '''
260 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
262 @Property_RO
263 def azimuth(self):
264 '''Get the Azimuth, bearing from North (C{degrees360}).
265 '''
266 return self._azimuth
268 @Property_RO
269 def down(self):
270 '''Get the Down component (C{meter}).
271 '''
272 return self.xyzLocal.down
274 @Property_RO
275 def east(self):
276 '''Get the East component (C{meter}).
277 '''
278 return self.xyzLocal.east
280 @Property_RO
281 def elevation(self):
282 '''Get the Elevation, tilt above horizon (C{degrees90}).
283 '''
284 return self._elevation
286 @Property_RO
287 def groundrange(self):
288 '''Get the I{ground range}, distance (C{meter}).
289 '''
290 return _er2gr(self._elevation, self._slantrange)
292 @Property_RO
293 def north(self):
294 '''Get the North component (C{meter}).
295 '''
296 return self.xyzLocal.north
298 @Property_RO
299 def slantrange(self):
300 '''Get the I{slant Range}, distance (C{meter}).
301 '''
302 return self._slantrange
304 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
305 '''Return a string representation of this AER as azimuth
306 (bearing), elevation and slant range.
308 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
309 @kwarg fmt: Enclosing backets format (C{str}).
310 @kwarg sep: Optional separator between AERs (C{str}).
312 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
313 '''
314 t = (toDMS(self.azimuth, form=F_D, prec=prec, ddd=0),
315 toDMS(self.elevation, form=F_D, prec=prec, ddd=0),
316 fstr( self.slantrange, prec=3 if prec is None else prec))
317 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
319 def toStr(self, **prec_fmt_sep): # PYCHOK expected
320 '''Return a string representation of this AER.
322 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
323 number of (decimal) digits, unstripped
324 (C{int}), C{B{fmt}='[]'} the enclosing
325 backets format (C{str}) and separator
326 C{B{sep}=', '} to join (C{str}).
328 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
329 '''
330 _, t = _toStr2(self, **prec_fmt_sep)
331 return t
333 @Property_RO
334 def up(self):
335 '''Get the Up component (C{meter}).
336 '''
337 return self.xyzLocal.up
339 @Property_RO
340 def x(self):
341 '''Get the X component (C{meter}).
342 '''
343 return self.xyz4.x
345 @Property_RO
346 def xyz4(self):
347 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
348 '''
349 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
350 R = self._slantrange
351 r = cE * R # ground range
352 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
354 @Property_RO
355 def y(self):
356 '''Get the Y component (C{meter}).
357 '''
358 return self.xyz4.y
360 @Property_RO
361 def z(self):
362 '''Get the Z component (C{meter}).
363 '''
364 return self.xyz4.z
367class Aer4Tuple(_NamedTuple):
368 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
369 all in C{meter} except C{ltp}.
370 '''
371 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
372 _Units_ = ( Meter, Meter, Meter, _Pass)
374 def _toAer(self, Cls, Cls_kwds):
375 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
376 '''
377 if issubclassof(Cls, Aer):
378 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
379 else:
380 return _4Tuple2Cls(self, Cls, Cls_kwds)
382 @Property_RO
383 def groundrange(self):
384 '''Get the I{ground range}, distance (C{meter}).
385 '''
386 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
388 @Property_RO
389 def xyzLocal(self):
390 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
391 '''
392 return Aer(self).xyzLocal
395class Attitude4Tuple(_NamedTuple):
396 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
397 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
398 the attitude of a plane or camera.
399 '''
400 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
401 _Units_ = ( Meter, Bearing, Degrees, Degrees)
403 @Property_RO
404 def atyr(self):
405 '''Return this attitude (L{Attitude4Tuple}).
406 '''
407 return self
409 @Property_RO
410 def tyr3d(self):
411 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
412 '''
413 return _MODS.ltp.Attitude(self).tyr3d
416class Ned(_NamedAerNed):
417 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
419 @see: L{Enu} and L{Ltp}.
420 '''
421 _down = _0_0 # down, -XyzLocal.z (C{meter}).
422 _east = _0_0 # east, XyzLocal.y (C{meter}).
423# _ltp = None # local tangent plane (C{Ltp}), origin
424 _north = _0_0 # north, XyzLocal.x (C{meter})
425 _toStr = _ned_
427 def __init__(self, north_ned, east=0, down=0, ltp=None, **name):
428 '''New L{Ned} vector.
430 @arg north_ned: Scalar North component (C{meter}) or a previous
431 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
432 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
433 L{XyzLocal} or L{Xyz4Tuple}).
434 @kwarg east: Scalar East component (C{meter}), only used with
435 scalar B{C{north_ned}}.
436 @kwarg down: Scalar Down component, normal to I{inside} surface
437 of the ellipsoid or sphere (C{meter}), only used with
438 scalar B{C{north_ned}}.
439 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
440 L{LocalCartesian}).
441 @kwarg name: Optional C{B{name}=NN} (C{str}).
443 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
445 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
446 '''
447 if _isMeter(north_ned):
448 self._north = Meter(north=north_ned or _0_0)
449 self._east = Meter(east=east or _0_0)
450 self._down = Meter(down=down or _0_0)
451 p, n = ltp, _name__(**name)
452 else: # PYCHOK no cover
453 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
454 ned = p.toNed() if p else north_ned
455 self._north, self._east, self._down = ned.north, ned.east, ned.down
456 p = _xattr(ned, ltp=ltp)
457 n = ned._name__(name)
459 if p:
460 self._ltp = _MODS.ltp._xLtp(p)
461 if n:
462 self.name = n
464 @Property_RO
465 def aer4(self):
466 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
467 '''
468 return _xyz2aer4(self)
470 @Property_RO
471 def azimuth(self):
472 '''Get the Azimuth, bearing from North (C{degrees360}).
473 '''
474 return self.aer4.azimuth
476 @deprecated_Property_RO
477 def bearing(self):
478 '''DEPRECATED, use C{azimuth}.'''
479 return self.azimuth
481 @Property_RO
482 def down(self):
483 '''Get the Down component (C{meter}).
484 '''
485 return self._down
487 @Property_RO
488 def east(self):
489 '''Get the East component (C{meter}).
490 '''
491 return self._east
493 @Property_RO
494 def elevation(self):
495 '''Get the Elevation, tilt above horizon (C{degrees90}).
496 '''
497 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
499 @Property_RO
500 def groundrange(self):
501 '''Get the I{ground range}, distance (C{meter}).
502 '''
503 return Meter(groundrange=hypot(self.north, self.east))
505 @deprecated_Property_RO
506 def length(self):
507 '''DEPRECATED, use C{slantrange}.'''
508 return self.slantrange
510 @deprecated_Property_RO
511 def ned(self):
512 '''DEPRECATED, use property C{ned4}.'''
513 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
515 @Property_RO
516 def ned4(self):
517 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
518 '''
519 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
521 @Property_RO
522 def north(self):
523 '''Get the North component (C{meter}).
524 '''
525 return self._north
527 @Property_RO
528 def slantrange(self):
529 '''Get the I{slant Range}, distance (C{meter}).
530 '''
531 return self.aer4.slantrange
533 @deprecated_method
534 def to3ned(self): # PYCHOK no cover
535 '''DEPRECATED, use property L{ned4}.'''
536 return self.ned # XXX deprecated too
538 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
539 '''Return a string representation of this NED.
541 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
542 @kwarg fmt: Enclosing backets format (C{str}).
543 @kwarg sep: Separator to join (C{str}).
545 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
546 '''
547 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
548 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
550 def toStr(self, **prec_fmt_sep): # PYCHOK expected
551 '''Return a string representation of this NED.
553 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
554 number of (decimal) digits, unstripped
555 (C{int}), C{B{fmt}='[]'} the enclosing
556 backets format (C{str}) and separator
557 C{B{sep}=', '} to join (C{str}).
559 @return: This NED as "[meter, meter, meter]" (C{str}).
560 '''
561 _, t = _toStr2(self, **prec_fmt_sep)
562 return t
564 @deprecated_method
565 def toVector3d(self):
566 '''DEPRECATED, use property L{xyz}.'''
567 return self.xyz
569 @Property_RO
570 def up(self):
571 '''Get the Up component (C{meter}).
572 '''
573 return Meter(up=-self._down) # negated
575 @Property_RO
576 def x(self):
577 '''Get the X component (C{meter}).
578 '''
579 return Meter(x=self._east) # 2nd arg, E
581 @Property_RO
582 def xyz4(self):
583 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
584 '''
585 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
587 @Property_RO
588 def y(self):
589 '''Get the Y component (C{meter}).
590 '''
591 return Meter(y=self._north) # 1st arg N
593 @Property_RO
594 def z(self):
595 '''Get the Z component (C{meter}).
596 '''
597 return Meter(z=-self._down) # negated
600class Ned4Tuple(_NamedTuple):
601 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
602 '''
603 _Names_ = (_north_, _east_, _down_, _ltp_)
604 _Units_ = ( Meter, Meter, Meter, _Pass)
606 def _toNed(self, Cls, Cls_kwds):
607 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
608 '''
609 if issubclassof(Cls, Ned):
610 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
611 else:
612 return _4Tuple2Cls(self, Cls, Cls_kwds)
614 @Property_RO
615 def xyzLocal(self):
616 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
617 '''
618 return Ned(self).xyzLocal
621class _Vector3d(Vector3d):
623 _toStr = _xyz_
625 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
626 '''Return a string representation of this ENU/NED/XYZ.
628 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
629 @kwarg fmt: Enclosing backets format (C{str}).
630 @kwarg sep: Separator to join (C{str}).
632 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
633 "[N:meter, E:meter, D:meter]",
634 "[U:meter, V:meter, W:meter]" respectively
635 "[X:meter, Y:meter, Z:meter]" (C{str}).
636 '''
637 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
638 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
640 def toStr(self, **prec_fmt_sep): # PYCHOK expected
641 '''Return a string representation of this XYZ.
643 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
644 number of (decimal) digits, unstripped
645 (C{int}), C{B{fmt}='[]'} the enclosing
646 backets format (C{str}) and separator
647 C{B{sep}=', '} to join (C{str}).
649 @return: This XYZ as "[meter, meter, meter]" (C{str}).
650 '''
651 _, t = _toStr2(self, **prec_fmt_sep)
652 return t
655class XyzLocal(_Vector3d):
656 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
657 also base class for local L{Enu}.
658 '''
659 _ltp = None # local tangent plane (C{Ltp}), origin
661 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name):
662 '''New L{XyzLocal}.
664 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
665 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
666 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
667 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
668 @kwarg y: Scalar Y component (C{meter}), only used with scalar
669 B{C{x_xyz}}, C{positive north}.
670 @kwarg z: Scalar Z component, normal C{positive up} from the
671 surface of the ellipsoid or sphere (C{meter}), only
672 used with scalar B{C{x_xyz}}.
673 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
674 L{LocalCartesian}).
675 @kwarg name: Optional C{B{name}=NN} (C{str}).
677 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
679 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
680 '''
681 if _isMeter(x_xyz):
682 self._x = Meter(x=x_xyz or _0_0)
683 self._y = Meter(y=y or _0_0)
684 self._z = Meter(z=z or _0_0)
685 p, n = ltp, _name__(**name)
686 else:
687 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
688 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z
689 p = _xattr(xyz, ltp=ltp)
690 n = xyz._name__(name)
692 if p:
693 self._ltp = _MODS.ltp._xLtp(p)
694 if n:
695 self.name = n
697 def __str__(self):
698 return self.toStr()
700 @Property_RO
701 def aer4(self):
702 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
703 '''
704 return _xyz2aer4(self)
706 @Property_RO
707 def azimuth(self):
708 '''Get the Azimuth, bearing from North (C{degrees360}).
710 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
711 Transformations_between_ECEF_and_ENU_coordinates>}.
712 '''
713 return self.aer4.azimuth
715 def classof(self, *args, **kwds): # PYCHOK no cover
716 '''Create another instance of this very class.
718 @arg args: Optional, positional arguments.
719 @kwarg kwds: Optional, keyword arguments.
721 @return: New instance (C{self.__class__}).
722 '''
723 kwds = _name1__(kwds, _or_nameof=self)
724 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp))
726 @Property_RO
727 def down(self):
728 '''Get the Down component (C{meter}).
729 '''
730 return Meter(down=-self.z)
732 @property_RO
733 def ecef(self):
734 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
735 '''
736 return self.ltp.ecef
738 @Property_RO
739 def east(self):
740 '''Get the East component (C{meter}).
741 '''
742 return Meter(east=self.x)
744 @Property_RO
745 def elevation(self):
746 '''Get the Elevation, tilt above horizon (C{degrees90}).
748 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
749 Transformations_between_ECEF_and_ENU_coordinates>}.
750 '''
751 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
753 @Property_RO
754 def enu4(self):
755 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
756 '''
757 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
759 @Property_RO
760 def groundrange(self):
761 '''Get the I{ground range}, distance (C{meter}).
762 '''
763 return Meter(groundrange=hypot(self.x, self.y))
765 @Property_RO
766 def ltp(self):
767 '''Get the I{local tangent plane} (L{Ltp}).
768 '''
769 return self._ltp
771 def _ltp_kwds_name3(self, ltp, kwds):
772 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}.
773 '''
774 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
775 kwds = _name1__(kwds, _or_nameof=self)
776 kwds = _name1__(kwds, _or_nameof=ltp)
777 return ltp, kwds, kwds.get(_name_, NN)
779 @Property_RO
780 def ned4(self):
781 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
782 '''
783 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
785 @Property_RO
786 def north(self):
787 '''Get the North component (C{meter}).
788 '''
789 return Meter(north=self.y)
791 @Property_RO
792 def slantrange(self):
793 '''Get the I{slant Range}, distance (C{meter}).
794 '''
795 return self.aer4.slantrange
797 def toAer(self, Aer=None, **name_Aer_kwds):
798 '''Get the local I{Azimuth, Elevation, slantRange} components.
800 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
801 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and
802 optional, additional B{C{Aer}} keyword arguments,
803 ignored if C{B{Aer} is None}.
805 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
806 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
808 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
809 '''
810 return self.aer4._toAer(Aer, name_Aer_kwds)
812 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds):
813 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
815 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
816 or C{None}.
817 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
818 overriding this C{ltp}.
819 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and
820 optional, additional B{C{Cartesian}} keyword arguments,
821 ignored if C{B{Cartesian} is None}.
823 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an
824 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
825 with C{M=None}, always.
827 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or
828 B{C{name_Cartesian_kwds}}.
829 '''
830 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds)
831 if Cartesian is None:
832 t = ltp._local2ecef(self, nine=True)
833 r = _xnamed(t, n) if n else t
834 else:
835 kwds = _xkwds(kwds, datum=ltp.datum)
836 x, y, z = ltp._local2ecef(self)
837 r = Cartesian(x, y, z, **kwds)
838 return r
840 def toEnu(self, Enu=None, **name_Enu_kwds):
841 '''Get the local I{East, North, Up} (ENU) components.
843 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
844 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and
845 optional, additional B{C{Enu}} keyword arguments,
846 ignored if C{B{Enu} is None}.
848 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
849 an L{Enu4Tuple}C{(east, north, up, ltp)}.
851 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
852 '''
853 return self.enu4._toEnu(Enu, name_Enu_kwds)
855 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds):
856 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
858 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon})
859 or C{None}.
860 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
861 overriding this ENU/NED/AER/XYZ's LTP.
862 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and
863 optional, additional B{C{LatLon}} keyword arguments,
864 ignored if C{B{LatLon} is None}.
866 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an
867 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
868 datum)} with C{M=None}, always.
870 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or
871 B{C{name_LatLon_kwds}}.
872 '''
873 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds)
874 t = ltp._local2ecef(self, nine=True)
875 if LatLon is None:
876 r = _xnamed(t, n) if n else t
877 else:
878 kwds = _xkwds(kwds, height=t.height, datum=t.datum)
879 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp?
880 return r
882 def toLocal9Tuple(self, M=False, **name):
883 '''Get this local as a C{Local9Tuple}.
885 @kwarg M: Optionally include the rotation matrix (C{bool}).
886 @kwarg name: Optional C{B{name}=NN} (C{str}).
888 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp,
889 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an
890 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}.
891 '''
892 ltp = self.ltp # see C{self.toLatLon}
893 t = ltp._local2ecef(self, nine=True, M=M)
894 return Local9Tuple(self.x, self.y, self.z, t.lat, t.lon, t.height,
895 ltp, t, t.M, name=t._name__(name))
897 def toNed(self, Ned=None, **name_Ned_kwds):
898 '''Get the local I{North, East, Down} (Ned) components.
900 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
901 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and
902 optional, additional B{C{Ned}} keyword arguments,
903 ignored if C{B{Ned} is None}.
905 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
906 an L{Ned4Tuple}C{(north, east, down, ltp)}.
908 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
909 '''
910 return self.ned4._toNed(Ned, name_Ned_kwds)
912 def toXyz(self, Xyz=None, **name_Xyz_kwds):
913 '''Get the local I{X, Y, Z} (XYZ) components.
915 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
916 L{Ned}, L{Aer}) or C{None}.
917 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and
918 optional, additional B{C{Xyz}} keyword arguments,
919 ignored if C{B{Xyz} is None}.
921 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
922 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
924 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_EXyz_kwds}}.
925 '''
926 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
928 @Property_RO
929 def up(self):
930 '''Get the Up component (C{meter}).
931 '''
932 return Meter(up=self.z)
934# @Property_RO
935# def x(self): # see: Vector3d.x
936# '''Get the X component (C{meter}).
937# '''
938# return self._x
940# @Property_RO
941# def xyz(self): # see: Vector3d.xyz
942# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
943# '''
944# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
946 @Property_RO
947 def xyz4(self):
948 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
949 '''
950 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
952 @Property_RO
953 def xyzLocal(self):
954 '''Get this L{XyzLocal}.
955 '''
956 return self
958# @Property_RO
959# def y(self): # see: Vector3d.y
960# '''Get the Y component (C{meter}).
961# '''
962# return self._y
964# @Property_RO
965# def z(self): # see: Vector3d.z
966# '''Get the Z component (C{meter}).
967# '''
968# return self._z
971class Xyz4Tuple(_NamedTuple):
972 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
973 '''
974 _Names_ = (_x_, _y_, _z_, _ltp_)
975 _Units_ = ( Meter, Meter, Meter, _Pass)
977 def _toXyz(self, Cls, Cls_kwds):
978 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
979 '''
980 kwds = _name1__(Cls_kwds, _or_nameof=self)
981 if issubclassof(Cls, XyzLocal):
982 return Cls(*self, **kwds)
983 else:
984 return _4Tuple2Cls(self, Cls, kwds)
986 @Property_RO
987 def xyzLocal(self):
988 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
989 '''
990 return XyzLocal(*self, name=self.name)
993class Enu(XyzLocal):
994 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
996 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
997 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
998 '''
999 _toStr = _enu_
1001 def __init__(self, east_enu, north=0, up=0, ltp=None, **name):
1002 '''New L{Enu}.
1004 @arg east_enu: Scalar East component (C{meter}) or a previous
1005 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
1006 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
1007 L{XyzLocal} or L{Xyz4Tuple}).
1008 @kwarg north: Scalar North component (C{meter}) only used with
1009 scalar B{C{east_enu}}.
1010 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
1011 normal from the surface of the ellipsoid or sphere (C{meter}).
1012 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
1013 L{LocalCartesian}).
1014 @kwarg name: Optional C{B{name}=NN} (C{str}).
1016 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
1018 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
1019 '''
1020 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name)
1022 def toUvw(self, location, Uvw=None, **name_Uvw_kwds):
1023 '''Get the I{u, v, w} (UVW) components at a location.
1025 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1026 L{Vector3d}) location, like a Point-Of-View.
1027 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
1028 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1029 additional B{L{Uvw}} keyword arguments, ignored if
1030 C{B{Uvw} is None}.
1032 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
1033 L{Uvw3Tuple}C{(u, v, w)}.
1035 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}}.
1037 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1038 '''
1039 try:
1040 sa, ca, sb, cb = sincos2_(*location.philam)
1041 except Exception as x:
1042 raise _TypeError(location=location, cause=x)
1043 e, n, u, _ = self.enu4
1045 t = ca * u - sa * n
1046 U = cb * t - sb * e
1047 V = cb * e + sb * t
1048 W = ca * n + sa * u
1050 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self)
1051 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \
1052 Uvw( U, V, W, name=n, **kwds)
1054 @Property_RO
1055 def xyzLocal(self):
1056 '''Get this ENU as an L{XyzLocal}.
1057 '''
1058 return XyzLocal(*self.xyz4, name=self.name)
1061class Enu4Tuple(_NamedTuple):
1062 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1063 '''
1064 _Names_ = (_east_, _north_, _up_, _ltp_)
1065 _Units_ = ( Meter, Meter, Meter, _Pass)
1067 def _toEnu(self, Cls, Cls_kwds):
1068 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1069 '''
1070 kwds = _name1__(Cls_kwds, _or_nameof=self)
1071 if issubclassof(Cls, XyzLocal):
1072 return Cls(*self, **kwds)
1073 else:
1074 return _4Tuple2Cls(self, Cls, kwds)
1076 @Property_RO
1077 def xyzLocal(self):
1078 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1079 '''
1080 return XyzLocal(*self, name=self.name)
1083class Local9Tuple(_NamedTuple):
1084 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1085 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1086 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1087 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1088 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1089 '''
1090 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1091 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1093 @Property_RO
1094 def azimuth(self):
1095 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1096 '''
1097 return self.xyzLocal.aer4.azimuth
1099 @Property_RO
1100 def down(self):
1101 '''Get the I{local} Down, C{-z} component (C{meter}).
1102 '''
1103 return -self.z
1105 @Property_RO
1106 def east(self):
1107 '''Get the I{local} East, C{x} component (C{meter}).
1108 '''
1109 return self.x
1111 @Property_RO
1112 def elevation(self):
1113 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1114 '''
1115 return self.xyzLocal.aer4.elevation
1117 @Property_RO
1118 def groundrange(self):
1119 '''Get the I{local} ground range, distance (C{meter}).
1120 '''
1121 return self.xyzLocal.aer4.groundrange
1123 @Property_RO
1124 def lam(self):
1125 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1126 '''
1127 return self.philam.lam
1129 @Property_RO
1130 def latlon(self):
1131 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1132 '''
1133 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1135 @Property_RO
1136 def latlonheight(self):
1137 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1138 '''
1139 return self.latlon.to3Tuple(self.height)
1141 @Property_RO
1142 def north(self):
1143 '''Get the I{local} North, C{y} component (C{meter}).
1144 '''
1145 return self.y
1147 @Property_RO
1148 def phi(self):
1149 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1150 '''
1151 return self.philam.phi
1153 @Property_RO
1154 def philam(self):
1155 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1156 '''
1157 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1159 @Property_RO
1160 def philamheight(self):
1161 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1162 '''
1163 return self.philam.to3Tuple(self.height)
1165 @Property_RO
1166 def slantrange(self):
1167 '''Get the I{local} slant Range, distance (C{meter}).
1168 '''
1169 return self.xyzLocal.aer4.slantrange
1171 def toAer(self, Aer=None, **name_Aer_kwds):
1172 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1174 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1175 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1176 additional B{L{Aer}} keyword arguments, ignored if
1177 B{C{Aer}} is C{None}.
1179 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
1180 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1182 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
1183 '''
1184 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds)
1186 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds):
1187 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1189 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1190 or C{None}.
1191 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1192 additional B{C{Cartesian}} keyword arguments, ignored if
1193 C{B{Cartesian} is None}.
1195 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or
1196 if C{B{Cartesian} is None}, a L{Vector4Tuple}C{(x, y, z, h)} .
1198 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}}.
1199 '''
1200 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple
1202 def toEnu(self, Enu=None, **name_Enu_kwds):
1203 '''Get the I{local} I{East, North, Up} (ENU) components.
1205 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1206 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1207 additional B{L{Enu}} keyword arguments, ignored if
1208 C{B{Enu} is None}.
1210 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1211 L{Enu4Tuple}C{(east, north, up, ltp)}.
1213 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
1214 '''
1215 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds)
1217 def toLatLon(self, LatLon=None, **name_LatLon_kwds):
1218 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1220 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1221 (C{LatLon}) or C{None}.
1222 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1223 additional B{C{LatLon}} keyword arguments, ignored if
1224 C{B{LatLon} is None}.
1226 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
1227 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon,
1228 height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
1229 datum)} depending on whether C{datum} is un-/specified.
1231 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1232 '''
1233 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple
1235 def toNed(self, Ned=None, **name_Ned_kwds):
1236 '''Get the I{local} I{North, East, Down} (NED) components.
1238 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1239 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1240 additional B{L{Ned}} keyword arguments, ignored if
1241 B{C{Ned}} is C{None}.
1243 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an
1244 L{Ned4Tuple}C{(north, east, down, ltp)}.
1246 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
1247 '''
1248 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds)
1250 def toXyz(self, Xyz=None, **name_Xyz_kwds):
1251 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1253 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1254 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1255 additional B{C{Xyz}} keyword arguments, ignored if
1256 C{B{Xyz} is None}.
1258 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1259 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1261 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
1262 '''
1263 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds)
1265 @Property_RO
1266 def up(self):
1267 '''Get the I{local} Up, C{z} component (C{meter}).
1268 '''
1269 return self.z
1271 @Property_RO
1272 def xyz(self):
1273 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1274 '''
1275 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1277 @Property_RO
1278 def xyzLocal(self):
1279 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1280 '''
1281 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1284_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1285_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1288class Uvw(_Vector3d):
1289 '''3-D C{u-v-w} (UVW) components.
1290 '''
1291 _toStr = _uvw_
1293 def __init__(self, u_uvw, v=0, w=0, **name):
1294 '''New L{Uvw}.
1296 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1297 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1298 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1299 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1300 @kwarg name: Optional C{B{name}=NN} (C{str}).
1302 @raise TypeError: Invalid B{C{east_enu}}.
1304 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1305 '''
1306 Vector3d.__init__(self, u_uvw, v, w, **name)
1308 def toEnu(self, location, Enu=Enu, **name_Enu_kwds):
1309 '''Get the I{East, North, Up} (ENU) components at a location.
1311 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1312 L{Vector3d}) location from where to cast the L{Los}.
1313 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1314 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1315 additional B{L{Enu}} keyword arguments, ignored if
1316 C{B{Enu} is None}.
1318 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1319 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1321 @raise TypeError: Invalid B{C{location}} or B{C{name_Enu_kwds}}.
1323 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1324 '''
1325 try:
1326 sa, ca, sb, cb = sincos2_(*location.philam)
1327 except Exception as x:
1328 raise _TypeError(location=location, cause=x)
1329 u, v, w = self.uvw
1331 t = cb * u + sb * v
1332 E = cb * v - sb * u
1333 N = ca * w - sa * t
1334 U = ca * t + sa * w
1336 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self)
1337 return Enu4Tuple(E, N, U, name=n) if Enu is None else \
1338 Enu( E, N, U, name=n, **kwds)
1340 u = Vector3d.x
1342 @Property_RO
1343 def uvw(self):
1344 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1345 '''
1346 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1348 v = Vector3d.y
1349 w = Vector3d.z
1352class Uvw3Tuple(_NamedTuple):
1353 '''3-Tuple C{(u, v, w)}, in C{meter}.
1354 '''
1355 _Names_ = ('u', 'v', 'w')
1356 _Units_ = ( Meter, Meter, Meter)
1359class Los(Aer):
1360 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1361 '''
1363 def __init__(self, azimuth_aer, elevation=0, **name):
1364 '''New L{Los}.
1366 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1367 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1368 L{Enu4Tuple} or L{Los}).
1369 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1370 is 0, zenith +90, nadir -90), only used with scalar
1371 B{C{azimuth_aer}}.
1372 @kwarg name: Optional C{B{name}=NN} (C{str}).
1374 @raise TypeError: Invalid B{C{azimuth_aer}}.
1376 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1377 '''
1378 t = Aer(azimuth_aer, elevation)
1379 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name)
1381 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds):
1382 '''Get this LOS' I{target} (UVW) components from a location.
1384 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1385 L{Vector3d}) location from where to cast the L{Los}.
1387 @see: Method L{Enu.toUvw} for further details.
1388 '''
1389 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds)
1391 def toEnu(self, Enu=Enu, **name_Enu_kwds):
1392 '''Get this LOS as I{East, North, Up} (ENU) components.
1394 @see: Method L{Aer.toEnu} for further details.
1395 '''
1396 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds)
1399class ChLV9Tuple(Local9Tuple):
1400 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1401 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1402 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1403 otherwise like L{Local9Tuple}.
1404 '''
1405 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1407 @Property_RO
1408 def E_LV95(self):
1409 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1410 '''
1411 return self.EN2_LV95.E_LV95
1413 @Property_RO
1414 def EN2_LV95(self):
1415 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1416 '''
1417 return ChLVEN2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1419 @Property_RO
1420 def h_LV03(self):
1421 '''Get the I{Swiss h_} height (C{meter}).
1422 '''
1423 return self.h_
1425 @Property_RO
1426 def h_LV95(self):
1427 '''Get the I{Swiss h_} height (C{meter}).
1428 '''
1429 return self.h_
1431 @property_RO
1432 def isChLV(self):
1433 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1434 '''
1435 return self.ltp.__class__ is _MODS.ltp.ChLV
1437 @property_RO
1438 def isChLVa(self):
1439 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1440 '''
1441 return self.ltp.__class__ is _MODS.ltp.ChLVa
1443 @property_RO
1444 def isChLVe(self):
1445 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1446 '''
1447 return self.ltp.__class__ is _MODS.ltp.ChLVe
1449 @Property_RO
1450 def N_LV95(self):
1451 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1452 '''
1453 return self.EN2_LV95.N_LV95
1455 @Property_RO
1456 def x(self):
1457 '''Get the I{local x, Swiss Y} easting (C{meter}).
1458 '''
1459 return self.Y
1461 @Property_RO
1462 def x_LV03(self):
1463 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1464 '''
1465 return self.yx2_LV03.x_LV03
1467 @Property_RO
1468 def y(self):
1469 '''Get the I{local y, Swiss X} northing (C{meter}).
1470 '''
1471 return self.X
1473 @Property_RO
1474 def y_LV03(self):
1475 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1476 '''
1477 return self.yx2_LV03.y_LV03
1479 @Property_RO
1480 def YX(self):
1481 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1482 '''
1483 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1485 @Property_RO
1486 def yx2_LV03(self):
1487 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1488 '''
1489 return ChLVyx2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1491 @Property_RO
1492 def z(self):
1493 '''Get the I{local z, Swiss h_} height (C{meter}).
1494 '''
1495 return self.h_
1498class ChLVYX2Tuple(_NamedTuple):
1499 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1500 in C{meter}.
1501 '''
1502 _Names_ = (_Y_, _X_)
1503 _Units_ = ( Meter, Meter)
1505 def false2(self, LV95=True):
1506 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1508 @see: Function L{ChLV.false2} for more information.
1509 '''
1510 return _MODS.ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1513class ChLVEN2Tuple(_NamedTuple):
1514 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1515 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1516 '''
1517 _Names_ = ('E_LV95', 'N_LV95')
1518 _Units_ = ChLVYX2Tuple._Units_
1520 def unfalse2(self):
1521 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1523 @see: Function L{ChLV.unfalse2} for more information.
1524 '''
1525 return _MODS.ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1528class ChLVyx2Tuple(_NamedTuple):
1529 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1530 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1531 '''
1532 _Names_ = ('y_LV03', 'x_LV03')
1533 _Units_ = ChLVYX2Tuple._Units_
1535 def unfalse2(self):
1536 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1538 @see: Function L{ChLV.unfalse2} for more information.
1539 '''
1540 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1543class Footprint5Tuple(_NamedTuple):
1544 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1545 with the C{center} and 4 corners of the I{local} projection of
1546 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1548 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1549 '''
1550 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1551 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1553 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds):
1554 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1555 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1557 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1558 footprint's C{center} or C{frustrum} C{ltp}.
1559 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1560 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1561 additional B{C{LatLon}} keyword arguments, ignored if
1562 C{B{LatLon} is None}.
1564 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon,
1565 **B{name_LatLon_kwds})} instances or if C{B{LatLon} is None},
1566 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively
1567 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending
1568 on whether keyword argument C{datum} is un-/specified.
1570 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1572 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1573 '''
1574 ltp = _MODS.ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center
1575 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)
1576 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon)
1577 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1579 def xyzLocal5(self, ltp=None):
1580 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1582 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1583 the {center} and corner C{ltp}s.
1585 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1587 @raise TypeError: Invalid B{C{ltp}}.
1588 '''
1589 if ltp is None:
1590 p = self
1591 else:
1592 p = _MODS.ltp._xLtp(ltp)
1593 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1594 return Footprint5Tuple(t.xyzLocal for t in p)
1597__all__ += _ALL_DOCS(_NamedAerNed)
1599# **) MIT License
1600#
1601# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1602#
1603# Permission is hereby granted, free of charge, to any person obtaining a
1604# copy of this software and associated documentation files (the "Software"),
1605# to deal in the Software without restriction, including without limitation
1606# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1607# and/or sell copies of the Software, and to permit persons to whom the
1608# Software is furnished to do so, subject to the following conditions:
1609#
1610# The above copyright notice and this permission notice shall be included
1611# in all copies or substantial portions of the Software.
1612#
1613# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1614# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1615# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1616# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1617# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1618# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1619# OTHER DEALINGS IN THE SOFTWARE.