Coverage for pygeodesy/ltpTuples.py: 94%
555 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-04-04 14:33 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-04-04 14:33 -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 _down_, _east_, _ecef_, _elevation_, _height_, \
22 _lat_, _lon_, _ltp_, _M_, _north_, _not_, _up_, \
23 _X_, _x_, _xyz_, _Y_, _y_, _z_
24from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
25from pygeodesy.named import _NamedBase, _NamedTuple, notOverloaded, \
26 _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.03.15'
41_aer_ = 'aer'
42_alt_ = 'alt'
43_enu_ = 'enu'
44_h__ = 'h_'
45_ned_ = 'ned'
46_local_ = 'local'
47_roll_ = 'roll'
48_slantrange_ = 'slantrange'
49_tilt_ = 'tilt'
50_uvw_ = 'uvw'
51_yaw_ = 'yaw'
54def _er2gr(e, r):
55 '''(INTERNAL) Elevation and slant range to ground range.
56 '''
57 c = cos(radians(e))
58 return Meter_(groundrange=r * c)
61def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
62 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
63 '''
64 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
65 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
66 t = strs(t, prec=3 if prec is None else prec)
67 if sep:
68 t = sep.join(t)
69 if fmt:
70 t = fmt(t)
71 return a, t
74def _4Tuple2Cls(inst, Cls, Cls_kwds):
75 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
76 '''
77 if Cls is None:
78 return inst
79 elif issubclassof(Cls, Aer):
80 return inst.xyzLocal.toAer(Aer=Cls, **Cls_kwds)
81 elif issubclassof(Cls, Enu): # PYCHOK no cover
82 return inst.xyzLocal.toEnu(Enu=Cls, **Cls_kwds)
83 elif issubclassof(Cls, Ned):
84 return inst.xyzLocal.toNed(Ned=Cls, **Cls_kwds)
85 elif issubclassof(Cls, XyzLocal): # PYCHOK no cover
86 return inst.xyzLocal.toXyz(Xyz=Cls, **Cls_kwds)
87 elif Cls is Local9Tuple: # PYCHOK no cover
88 return inst.xyzLocal.toLocal9Tuple(**Cls_kwds)
89 n = inst.__class__.__name__[:3] # PYCHOK no cover
90 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
93def _xyz2aer4(inst):
94 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
95 '''
96 x, y, z, _ = inst.xyz4
97 A = Bearing(azimuth=atan2b(x, y))
98 E = Degrees(elevation=atan2d(z, hypot(x, y)))
99 R = Meter(slantrange=hypot_(x, y, z))
100 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
103def _xyzLocal(*Types, **name_inst):
104 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
105 '''
106 n, inst = _xkwds_item2(name_inst)
107 if isinstance(inst, Types):
108 return None
109 try:
110 return inst.xyzLocal
111 except (AttributeError, TypeError):
112 raise _TypeError(n, inst, txt=_not_(_local_))
115class _NamedAerNed(_NamedBase):
116 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
117 '''
118 _ltp = None # local tangent plane (C{Ltp}), origin
120 @Property_RO
121 def ltp(self):
122 '''Get the I{local tangent plane} (L{Ltp}).
123 '''
124 return self._ltp
126 def toAer(self, Aer=None, **Aer_kwds):
127 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
129 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
130 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
131 arguments, ignored if B{C{Aer}} is C{None}.
133 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
134 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
135 '''
136 return self.xyz4._toXyz(Aer, Aer_kwds)
138 def toEnu(self, Enu=None, **Enu_kwds):
139 '''Get the I{local} I{East, North, Up} (ENU) components.
141 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
142 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
143 arguments, ignored if C{B{Enu} is None}.
145 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
146 an L{Enu4Tuple}C{(east, north, up, ltp)}.
147 '''
148 return self.xyz4._toXyz(Enu, Enu_kwds)
150 def toNed(self, Ned=None, **Ned_kwds):
151 '''Get the I{local} I{North, East, Down} (NED) components.
153 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
154 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
155 arguments, ignored if B{C{Ned}} is C{None}.
157 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
158 an L{Ned4Tuple}C{(north, east, down, ltp)}.
159 '''
160 return self.xyz4._toXyz(Ned, Ned_kwds)
162 def toXyz(self, Xyz=None, **Xyz_kwds):
163 '''Get the local I{X, Y, Z} (XYZ) components.
165 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
166 L{Ned}, L{Aer}) or C{None}.
167 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
168 arguments, ignored if C{B{Xyz} is None}.
170 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
171 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
173 @raise TypeError: Invalid B{C{Xyz}}.
174 '''
175 return self.xyz4._toXyz(Xyz, Xyz_kwds)
177 @Property_RO
178 def xyz(self):
179 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
180 '''
181 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
183 @property_RO
184 def xyz4(self): # PYCHOK no cover
185 '''I{Must be overloaded}.'''
186 notOverloaded(self)
188 @Property_RO
189 def xyzLocal(self):
190 '''Get this AER or NED as an L{XyzLocal}.
191 '''
192 return XyzLocal(self.xyz4, name=self.name)
195class Aer(_NamedAerNed):
196 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
197 '''
198 _azimuth = _0_0 # bearing from North (C{degrees360})
199 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
200# _ltp = None # local tangent plane (C{Ltp}), origin
201 _slantrange = _0_0 # distance (C{Meter})
202 _toStr = _aer_
204 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, name=NN):
205 '''New L{Aer}.
207 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
208 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
209 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
210 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
211 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
212 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
213 only used with scalar B{C{azimuth_aer}}.
214 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
215 B{C{azimuth_aer}}.
216 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
217 L{LocalCartesian}).
218 @kwarg name: Optional name (C{str}).
220 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
222 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
223 or B{C{slantrange}}.
224 '''
225 if _isDegrees(azimuth_aer):
226 self._azimuth = Bearing(azimuth=azimuth_aer)
227 self._elevation = Degrees_(elevation=elevation, low=_N_90_0, high=_90_0)
228 self._slantrange = Meter_(slantrange=slantrange)
229 p, n = ltp, name
230 else: # PYCHOK no cover
231 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
232 aer = p.toAer() if p else azimuth_aer
233 self._azimuth, self._elevation, self._slantrange = \
234 aer.azimuth, aer.elevation, aer.slantrange
235 p = _xattr(aer, ltp=ltp)
236 n = name or _xattr(aer, name=name)
238 if p:
239 self._ltp = _MODS.ltp._xLtp(p)
240 if name:
241 self.name = n
243 @Property_RO
244 def aer4(self):
245 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
246 '''
247 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
249 @Property_RO
250 def azimuth(self):
251 '''Get the Azimuth, bearing from North (C{degrees360}).
252 '''
253 return self._azimuth
255 @Property_RO
256 def down(self):
257 '''Get the Down component (C{meter}).
258 '''
259 return self.xyzLocal.down
261 @Property_RO
262 def east(self):
263 '''Get the East component (C{meter}).
264 '''
265 return self.xyzLocal.east
267 @Property_RO
268 def elevation(self):
269 '''Get the Elevation, tilt above horizon (C{degrees90}).
270 '''
271 return self._elevation
273 @Property_RO
274 def groundrange(self):
275 '''Get the I{ground range}, distance (C{meter}).
276 '''
277 return _er2gr(self._elevation, self._slantrange)
279 @Property_RO
280 def north(self):
281 '''Get the North component (C{meter}).
282 '''
283 return self.xyzLocal.north
285 @Property_RO
286 def slantrange(self):
287 '''Get the I{slant Range}, distance (C{meter}).
288 '''
289 return self._slantrange
291 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
292 '''Return a string representation of this AER as azimuth
293 (bearing), elevation and slant range.
295 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
296 @kwarg fmt: Enclosing backets format (C{str}).
297 @kwarg sep: Optional separator between AERs (C{str}).
299 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
300 '''
301 t = (toDMS(self.azimuth, form=F_D, prec=prec, ddd=0),
302 toDMS(self.elevation, form=F_D, prec=prec, ddd=0),
303 fstr( self.slantrange, prec=3 if prec is None else prec))
304 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
306 def toStr(self, **prec_fmt_sep): # PYCHOK expected
307 '''Return a string representation of this AER.
309 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
310 number of (decimal) digits, unstripped
311 (C{int}), C{B{fmt}='[]'} the enclosing
312 backets format (C{str}) and separator
313 C{B{sep}=', '} to join (C{str}).
315 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
316 '''
317 _, t = _toStr2(self, **prec_fmt_sep)
318 return t
320 @Property_RO
321 def up(self):
322 '''Get the Up component (C{meter}).
323 '''
324 return self.xyzLocal.up
326 @Property_RO
327 def x(self):
328 '''Get the X component (C{meter}).
329 '''
330 return self.xyz4.x
332 @Property_RO
333 def xyz4(self):
334 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
335 '''
336 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
337 R = self._slantrange
338 r = cE * R # ground range
339 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
341 @Property_RO
342 def y(self):
343 '''Get the Y component (C{meter}).
344 '''
345 return self.xyz4.y
347 @Property_RO
348 def z(self):
349 '''Get the Z component (C{meter}).
350 '''
351 return self.xyz4.z
354class Aer4Tuple(_NamedTuple):
355 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
356 all in C{meter} except C{ltp}.
357 '''
358 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
359 _Units_ = ( Meter, Meter, Meter, _Pass)
361 def _toAer(self, Cls, Cls_kwds):
362 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
363 '''
364 if issubclassof(Cls, Aer):
365 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
366 else:
367 return _4Tuple2Cls(self, Cls, Cls_kwds)
369 @Property_RO
370 def groundrange(self):
371 '''Get the I{ground range}, distance (C{meter}).
372 '''
373 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
375 @Property_RO
376 def xyzLocal(self):
377 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
378 '''
379 return Aer(self).xyzLocal
382class Attitude4Tuple(_NamedTuple):
383 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
384 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
385 the attitude of a plane or camera.
386 '''
387 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
388 _Units_ = ( Meter, Bearing, Degrees, Degrees)
390 @Property_RO
391 def atyr(self):
392 '''Return this attitude (L{Attitude4Tuple}).
393 '''
394 return self
396 @Property_RO
397 def tyr3d(self):
398 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
399 '''
400 return _MODS.ltp.Attitude(self).tyr3d
403class Ned(_NamedAerNed):
404 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
406 @see: L{Enu} and L{Ltp}.
407 '''
408 _down = _0_0 # down, -XyzLocal.z (C{meter}).
409 _east = _0_0 # east, XyzLocal.y (C{meter}).
410# _ltp = None # local tangent plane (C{Ltp}), origin
411 _north = _0_0 # north, XyzLocal.x (C{meter})
412 _toStr = _ned_
414 def __init__(self, north_ned, east=0, down=0, ltp=None, name=NN):
415 '''New L{Ned} vector.
417 @arg north_ned: Scalar North component (C{meter}) or a previous
418 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
419 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
420 L{XyzLocal} or L{Xyz4Tuple}).
421 @kwarg east: Scalar East component (C{meter}), only used with
422 scalar B{C{north_ned}}.
423 @kwarg down: Scalar Down component, normal to I{inside} surface
424 of the ellipsoid or sphere (C{meter}), only used with
425 scalar B{C{north_ned}}.
426 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
427 L{LocalCartesian}).
428 @kwarg name: Optional name (C{str}).
430 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
432 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
433 '''
434 if _isMeter(north_ned):
435 self._north = Meter(north=north_ned or _0_0)
436 self._east = Meter(east=east or _0_0)
437 self._down = Meter(down=down or _0_0)
438 p, n = ltp, name
439 else: # PYCHOK no cover
440 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
441 ned = p.toNed() if p else north_ned
442 self._north, self._east, self._down = ned.north, ned.east, ned.down
443 p = _xattr(ned, ltp=ltp)
444 n = name or _xattr(ned, name=name)
446 if p:
447 self._ltp = _MODS.ltp._xLtp(p)
448 if n:
449 self.name = n
451 @Property_RO
452 def aer4(self):
453 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
454 '''
455 return _xyz2aer4(self)
457 @Property_RO
458 def azimuth(self):
459 '''Get the Azimuth, bearing from North (C{degrees360}).
460 '''
461 return self.aer4.azimuth
463 @deprecated_Property_RO
464 def bearing(self):
465 '''DEPRECATED, use C{azimuth}.'''
466 return self.azimuth
468 @Property_RO
469 def down(self):
470 '''Get the Down component (C{meter}).
471 '''
472 return self._down
474 @Property_RO
475 def east(self):
476 '''Get the East component (C{meter}).
477 '''
478 return self._east
480 @Property_RO
481 def elevation(self):
482 '''Get the Elevation, tilt above horizon (C{degrees90}).
483 '''
484 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
486 @Property_RO
487 def groundrange(self):
488 '''Get the I{ground range}, distance (C{meter}).
489 '''
490 return Meter(groundrange=hypot(self.north, self.east))
492 @deprecated_Property_RO
493 def length(self):
494 '''DEPRECATED, use C{slantrange}.'''
495 return self.slantrange
497 @deprecated_Property_RO
498 def ned(self):
499 '''DEPRECATED, use property C{ned4}.'''
500 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
502 @Property_RO
503 def ned4(self):
504 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
505 '''
506 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
508 @Property_RO
509 def north(self):
510 '''Get the North component (C{meter}).
511 '''
512 return self._north
514 @Property_RO
515 def slantrange(self):
516 '''Get the I{slant Range}, distance (C{meter}).
517 '''
518 return self.aer4.slantrange
520 @deprecated_method
521 def to3ned(self): # PYCHOK no cover
522 '''DEPRECATED, use property L{ned4}.'''
523 return self.ned # XXX deprecated too
525 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
526 '''Return a string representation of this NED.
528 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
529 @kwarg fmt: Enclosing backets format (C{str}).
530 @kwarg sep: Separator to join (C{str}).
532 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
533 '''
534 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
535 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
537 def toStr(self, **prec_fmt_sep): # PYCHOK expected
538 '''Return a string representation of this NED.
540 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
541 number of (decimal) digits, unstripped
542 (C{int}), C{B{fmt}='[]'} the enclosing
543 backets format (C{str}) and separator
544 C{B{sep}=', '} to join (C{str}).
546 @return: This NED as "[meter, meter, meter]" (C{str}).
547 '''
548 _, t = _toStr2(self, **prec_fmt_sep)
549 return t
551 @deprecated_method
552 def toVector3d(self):
553 '''DEPRECATED, use property L{xyz}.'''
554 return self.xyz
556 @Property_RO
557 def up(self):
558 '''Get the Up component (C{meter}).
559 '''
560 return Meter(up=-self._down) # negated
562 @Property_RO
563 def x(self):
564 '''Get the X component (C{meter}).
565 '''
566 return Meter(x=self._east) # 2nd arg, E
568 @Property_RO
569 def xyz4(self):
570 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
571 '''
572 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
574 @Property_RO
575 def y(self):
576 '''Get the Y component (C{meter}).
577 '''
578 return Meter(y=self._north) # 1st arg N
580 @Property_RO
581 def z(self):
582 '''Get the Z component (C{meter}).
583 '''
584 return Meter(z=-self._down) # negated
587class Ned4Tuple(_NamedTuple):
588 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
589 '''
590 _Names_ = (_north_, _east_, _down_, _ltp_)
591 _Units_ = ( Meter, Meter, Meter, _Pass)
593 def _toNed(self, Cls, Cls_kwds):
594 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
595 '''
596 if issubclassof(Cls, Ned):
597 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
598 else:
599 return _4Tuple2Cls(self, Cls, Cls_kwds)
601 @Property_RO
602 def xyzLocal(self):
603 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
604 '''
605 return Ned(self).xyzLocal
608class _Vector3d(Vector3d):
610 _toStr = _xyz_
612 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
613 '''Return a string representation of this ENU/NED/XYZ.
615 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
616 @kwarg fmt: Enclosing backets format (C{str}).
617 @kwarg sep: Separator to join (C{str}).
619 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
620 "[N:meter, E:meter, D:meter]",
621 "[U:meter, V:meter, W:meter]" respectively
622 "[X:meter, Y:meter, Z:meter]" (C{str}).
623 '''
624 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
625 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
627 def toStr(self, **prec_fmt_sep): # PYCHOK expected
628 '''Return a string representation of this XYZ.
630 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
631 number of (decimal) digits, unstripped
632 (C{int}), C{B{fmt}='[]'} the enclosing
633 backets format (C{str}) and separator
634 C{B{sep}=', '} to join (C{str}).
636 @return: This XYZ as "[meter, meter, meter]" (C{str}).
637 '''
638 _, t = _toStr2(self, **prec_fmt_sep)
639 return t
642class XyzLocal(_Vector3d):
643 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
644 also base class for local L{Enu}.
645 '''
646 _ltp = None # local tangent plane (C{Ltp}), origin
648 def __init__(self, x_xyz, y=0, z=0, ltp=None, name=NN):
649 '''New L{XyzLocal}.
651 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
652 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
653 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
654 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
655 @kwarg y: Scalar Y component (C{meter}), only used with scalar
656 B{C{x_xyz}}, C{positive north}.
657 @kwarg z: Scalar Z component, normal C{positive up} from the
658 surface of the ellipsoid or sphere (C{meter}), only
659 used with scalar B{C{x_xyz}}.
660 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
661 L{LocalCartesian}).
663 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
665 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
666 '''
667 if _isMeter(x_xyz):
668 self._x = Meter(x=x_xyz or _0_0)
669 self._y = Meter(y=y or _0_0)
670 self._z = Meter(z=z or _0_0)
671 p, n = ltp, name
672 else:
673 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
674 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z
675 p = _xattr(xyz, ltp=ltp)
676 n = name or _xattr(xyz, name=NN)
678 if p:
679 self._ltp = _MODS.ltp._xLtp(p)
680 if n:
681 self.name = n
683 def __str__(self):
684 return self.toStr()
686 @Property_RO
687 def aer4(self):
688 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
689 '''
690 return _xyz2aer4(self)
692 @Property_RO
693 def azimuth(self):
694 '''Get the Azimuth, bearing from North (C{degrees360}).
696 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
697 Transformations_between_ECEF_and_ENU_coordinates>}.
698 '''
699 return self.aer4.azimuth
701 def classof(self, *args, **kwds): # PYCHOK no cover
702 '''Create another instance of this very class.
704 @arg args: Optional, positional arguments.
705 @kwarg kwds: Optional, keyword arguments.
707 @return: New instance (C{self.__class__}).
708 '''
709 kwds = _xkwds(kwds, ltp=self.ltp, name=self.name)
710 return self.__class__(*args, **kwds)
712 @Property_RO
713 def down(self):
714 '''Get the Down component (C{meter}).
715 '''
716 return Meter(down=-self.z)
718 @property_RO
719 def ecef(self):
720 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
721 '''
722 return self.ltp.ecef
724 @Property_RO
725 def east(self):
726 '''Get the East component (C{meter}).
727 '''
728 return Meter(east=self.x)
730 @Property_RO
731 def elevation(self):
732 '''Get the Elevation, tilt above horizon (C{degrees90}).
734 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
735 Transformations_between_ECEF_and_ENU_coordinates>}.
736 '''
737 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
739 @Property_RO
740 def enu4(self):
741 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
742 '''
743 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
745 @Property_RO
746 def groundrange(self):
747 '''Get the I{ground range}, distance (C{meter}).
748 '''
749 return Meter(groundrange=hypot(self.x, self.y))
751 @Property_RO
752 def ltp(self):
753 '''Get the I{local tangent plane} (L{Ltp}).
754 '''
755 return self._ltp
757 @Property_RO
758 def ned4(self):
759 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
760 '''
761 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
763 @Property_RO
764 def north(self):
765 '''Get the North component (C{meter}).
766 '''
767 return Meter(north=self.y)
769 @Property_RO
770 def slantrange(self):
771 '''Get the I{slant Range}, distance (C{meter}).
772 '''
773 return self.aer4.slantrange
775 def toAer(self, Aer=None, **Aer_kwds):
776 '''Get the local I{Azimuth, Elevation, slantRange} components.
778 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
779 @kwarg Aer_kwds: Optional, additional B{C{Aer}} keyword
780 arguments, ignored if C{B{Aer} is None}.
782 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
783 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
785 @raise TypeError: Invalid B{C{Aer}}.
786 '''
787 return self.aer4._toAer(Aer, Aer_kwds)
789 def toCartesian(self, Cartesian=None, ltp=None, **Cartesian_kwds):
790 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
792 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
793 or C{None}.
794 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
795 overriding this C{ltp}.
796 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
797 arguments, ignored if C{B{Cartesian} is None}.
799 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an
800 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
801 with C{M=None}, always.
803 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or
804 B{C{Cartesian_kwds}} argument.
805 '''
806 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
807 if Cartesian is None:
808 r = ltp._local2ecef(self, nine=True)
809 else:
810 x, y, z = ltp._local2ecef(self)
811 kwds = _xkwds(Cartesian_kwds, datum=ltp.datum)
812 r = Cartesian(x, y, z, **kwds)
813 return _xnamed(r, self.name or ltp.name)
815 def toEnu(self, Enu=None, **Enu_kwds):
816 '''Get the local I{East, North, Up} (ENU) components.
818 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
819 @kwarg Enu_kwds: Optional, additional B{C{Enu}} keyword
820 arguments, ignored if C{B{Enu} is None}.
822 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
823 an L{Enu4Tuple}C{(east, north, up, ltp)}.
824 '''
825 return self.enu4._toEnu(Enu, Enu_kwds)
827 def toLatLon(self, LatLon=None, ltp=None, **LatLon_kwds):
828 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
830 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon})
831 or C{None}.
832 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
833 overriding this ENU/NED/AER/XYZ's LTP.
834 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
835 arguments, ignored if C{B{LatLon} is None}.
837 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an
838 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
839 datum)} with C{M=None}, always.
841 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or
842 B{C{LatLon_kwds}} argument.
843 '''
844 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
845 r = ltp._local2ecef(self, nine=True)
846 if LatLon is None:
847 r = _xnamed(r, self.name or ltp.name)
848 else:
849 kwds = _xkwds(LatLon_kwds, height=r.height, datum=r.datum,
850 name=self.name or ltp.name)
851 r = LatLon(r.lat, r.lon, **kwds) # XXX ltp?
852 return r
854 def toLocal9Tuple(self, M=False, name=NN):
855 '''Get this local as a C{Local9Tuple}.
857 @kwarg M: Optionally include the rotation matrix (C{bool}).
858 @kwarg name: Optional name (C{str}).
860 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp,
861 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an
862 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}.
863 '''
864 ltp = self.ltp # see C{self.toLatLon}
865 t = ltp._local2ecef(self, nine=True, M=M)
866 return Local9Tuple(self.x, self.y, self.z, t.lat, t.lon, t.height,
867 ltp, t, t.M, name=name or t.name)
869 def toNed(self, Ned=None, **Ned_kwds):
870 '''Get the local I{North, East, Down} (Ned) components.
872 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
873 @kwarg Ned_kwds: Optional, additional B{C{Ned}} keyword
874 arguments, ignored if C{B{Ned} is None}.
876 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
877 an L{Ned4Tuple}C{(north, east, down, ltp)}.
878 '''
879 return self.ned4._toNed(Ned, Ned_kwds)
881 def toXyz(self, Xyz=None, **Xyz_kwds):
882 '''Get the local I{X, Y, Z} (XYZ) components.
884 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
885 L{Ned}, L{Aer}) or C{None}.
886 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
887 arguments, ignored if C{B{Xyz} is None}.
889 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
890 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
891 '''
892 return self.xyz4._toXyz(Xyz, Xyz_kwds)
894 @Property_RO
895 def up(self):
896 '''Get the Up component (C{meter}).
897 '''
898 return Meter(up=self.z)
900# @Property_RO
901# def x(self): # see: Vector3d.x
902# '''Get the X component (C{meter}).
903# '''
904# return self._x
906# @Property_RO
907# def xyz(self): # see: Vector3d.xyz
908# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
909# '''
910# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
912 @Property_RO
913 def xyz4(self):
914 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
915 '''
916 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
918 @Property_RO
919 def xyzLocal(self):
920 '''Get this L{XyzLocal}.
921 '''
922 return self
924# @Property_RO
925# def y(self): # see: Vector3d.y
926# '''Get the Y component (C{meter}).
927# '''
928# return self._y
930# @Property_RO
931# def z(self): # see: Vector3d.z
932# '''Get the Z component (C{meter}).
933# '''
934# return self._z
937class Xyz4Tuple(_NamedTuple):
938 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
939 '''
940 _Names_ = (_x_, _y_, _z_, _ltp_)
941 _Units_ = ( Meter, Meter, Meter, _Pass)
943 def _toXyz(self, Cls, Cls_kwds):
944 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
945 '''
946 if issubclassof(Cls, XyzLocal):
947 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
948 else:
949 return _4Tuple2Cls(self, Cls, Cls_kwds)
951 @Property_RO
952 def xyzLocal(self):
953 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
954 '''
955 return XyzLocal(*self, name=self.name)
958class Enu(XyzLocal):
959 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
961 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
962 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
963 '''
964 _toStr = _enu_
966 def __init__(self, east_enu, north=0, up=0, ltp=None, name=NN):
967 '''New L{Enu}.
969 @arg east_enu: Scalar East component (C{meter}) or a previous
970 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
971 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
972 L{XyzLocal} or L{Xyz4Tuple}).
973 @kwarg north: Scalar North component (C{meter}) only used with
974 scalar B{C{east_enu}}.
975 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
976 normal from the surface of the ellipsoid or sphere (C{meter}).
977 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
978 L{LocalCartesian}).
979 @kwarg name: Optional name (C{str}).
981 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
983 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
984 '''
985 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, name=name)
987 def toUvw(self, location, Uvw=None, **Uvw_kwds):
988 '''Get the I{u, v, w} (UVW) components at a location.
990 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
991 L{Vector3d}) location, like a Point-Of-View.
992 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
993 @kwarg Uvw_kwds: Optional, additional B{L{Uvw}} keyword
994 arguments, ignored if C{B{Uvw} is None}.
996 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
997 L{Uvw3Tuple}C{(u, v, w)}.
999 @raise TypeError: InvalidB{C{location}}.
1001 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1002 '''
1003 try:
1004 sa, ca, sb, cb = sincos2_(*location.philam)
1005 except Exception as x:
1006 raise _TypeError(location=location, cause=x)
1007 e, n, u, _ = self.enu4
1009 t = ca * u - sa * n
1010 U = cb * t - sb * e
1011 V = cb * e + sb * t
1012 W = ca * n + sa * u
1013 return Uvw3Tuple(U, V, W, name=self.name) if Uvw is None else \
1014 Uvw( U, V, W, **_xkwds(Uvw_kwds, name=self.name))
1016 @Property_RO
1017 def xyzLocal(self):
1018 '''Get this ENU as an L{XyzLocal}.
1019 '''
1020 return XyzLocal(*self.xyz4, name=self.name)
1023class Enu4Tuple(_NamedTuple):
1024 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1025 '''
1026 _Names_ = (_east_, _north_, _up_, _ltp_)
1027 _Units_ = ( Meter, Meter, Meter, _Pass)
1029 def _toEnu(self, Cls, Cls_kwds):
1030 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1031 '''
1032 if issubclassof(Cls, XyzLocal):
1033 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
1034 else:
1035 return _4Tuple2Cls(self, Cls, Cls_kwds)
1037 @Property_RO
1038 def xyzLocal(self):
1039 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1040 '''
1041 return XyzLocal(*self, name=self.name)
1044class Local9Tuple(_NamedTuple):
1045 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1046 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1047 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1048 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1049 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1050 '''
1051 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1052 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1054 @Property_RO
1055 def azimuth(self):
1056 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1057 '''
1058 return self.xyzLocal.aer4.azimuth
1060 @Property_RO
1061 def down(self):
1062 '''Get the I{local} Down, C{-z} component (C{meter}).
1063 '''
1064 return -self.z
1066 @Property_RO
1067 def east(self):
1068 '''Get the I{local} East, C{x} component (C{meter}).
1069 '''
1070 return self.x
1072 @Property_RO
1073 def elevation(self):
1074 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1075 '''
1076 return self.xyzLocal.aer4.elevation
1078 @Property_RO
1079 def groundrange(self):
1080 '''Get the I{local} ground range, distance (C{meter}).
1081 '''
1082 return self.xyzLocal.aer4.groundrange
1084 @Property_RO
1085 def lam(self):
1086 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1087 '''
1088 return self.philam.lam
1090 @Property_RO
1091 def latlon(self):
1092 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1093 '''
1094 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1096 @Property_RO
1097 def latlonheight(self):
1098 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1099 '''
1100 return self.latlon.to3Tuple(self.height)
1102 @Property_RO
1103 def north(self):
1104 '''Get the I{local} North, C{y} component (C{meter}).
1105 '''
1106 return self.y
1108 @Property_RO
1109 def phi(self):
1110 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1111 '''
1112 return self.philam.phi
1114 @Property_RO
1115 def philam(self):
1116 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1117 '''
1118 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1120 @Property_RO
1121 def philamheight(self):
1122 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1123 '''
1124 return self.philam.to3Tuple(self.height)
1126 @Property_RO
1127 def slantrange(self):
1128 '''Get the I{local} slant Range, distance (C{meter}).
1129 '''
1130 return self.xyzLocal.aer4.slantrange
1132 def toAer(self, Aer=None, **Aer_kwds):
1133 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1135 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1136 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
1137 arguments, ignored if B{C{Aer}} is C{None}.
1139 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
1140 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1141 '''
1142 return self.xyzLocal.toAer(Aer=Aer, **Aer_kwds)
1144 def toCartesian(self, Cartesian=None, **Cartesian_kwds):
1145 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1147 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1148 or C{None}.
1149 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
1150 arguments, ignored if C{B{Cartesian} is None}.
1152 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance
1153 or a L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
1155 @raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}
1156 argument.
1157 '''
1158 return self.ecef.toCartesian(Cartesian=Cartesian, **Cartesian_kwds) # PYCHOK _Tuple
1160 def toEnu(self, Enu=None, **Enu_kwds):
1161 '''Get the I{local} I{East, North, Up} (ENU) components.
1163 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1164 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1165 arguments, ignored if C{B{Enu} is None}.
1167 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
1168 an L{Enu4Tuple}C{(east, north, up, ltp)}.
1169 '''
1170 return self.xyzLocal.toEnu(Enu=Enu, **Enu_kwds)
1172 def toLatLon(self, LatLon=None, **LatLon_kwds):
1173 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1175 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1176 (C{LatLon}) or C{None}.
1177 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1178 arguments, ignored if C{B{LatLon} is None}.
1180 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
1181 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon,
1182 height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
1183 datum)} depending on whether C{datum} is un-/specified.
1185 @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}
1186 argument.
1187 '''
1188 return self.ecef.toLatLon(LatLon=LatLon, **LatLon_kwds) # PYCHOK _Tuple
1190 def toNed(self, Ned=None, **Ned_kwds):
1191 '''Get the I{local} I{North, East, Down} (NED) components.
1193 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1194 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
1195 arguments, ignored if B{C{Ned}} is C{None}.
1197 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
1198 an L{Ned4Tuple}C{(north, east, down, ltp)}.
1199 '''
1200 return self.xyzLocal.toNed(Ned=Ned, **Ned_kwds)
1202 def toXyz(self, Xyz=None, **Xyz_kwds):
1203 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1205 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1206 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
1207 arguments, ignored if C{B{Xyz} is None}.
1209 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1210 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1211 '''
1212 return self.xyzLocal.toXyz(Xyz=Xyz, **Xyz_kwds)
1214 @Property_RO
1215 def up(self):
1216 '''Get the I{local} Up, C{z} component (C{meter}).
1217 '''
1218 return self.z
1220 @Property_RO
1221 def xyz(self):
1222 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1223 '''
1224 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1226 @Property_RO
1227 def xyzLocal(self):
1228 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1229 '''
1230 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1233_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1234_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1237class Uvw(_Vector3d):
1238 '''3-D C{u-v-w} (UVW) components.
1239 '''
1240 _toStr = _uvw_
1242 def __init__(self, u_uvw, v=0, w=0, name=NN):
1243 '''New L{Uvw}.
1245 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1246 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1247 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1248 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1249 @kwarg name: Optional name (C{str}).
1251 @raise TypeError: Invalid B{C{east_enu}}.
1253 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1254 '''
1255 Vector3d.__init__(self, u_uvw, v, w, name=name)
1257 def toEnu(self, location, Enu=Enu, **Enu_kwds):
1258 '''Get the I{East, North, Up} (ENU) components at a location.
1260 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1261 L{Vector3d}) location from where to cast the L{Los}.
1262 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1263 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1264 arguments, ignored if C{B{Enu} is None}.
1266 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1267 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1269 @raise TypeError: InvalidB{C{location}}.
1271 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1272 '''
1273 try:
1274 sa, ca, sb, cb = sincos2_(*location.philam)
1275 except Exception as x:
1276 raise _TypeError(location=location, cause=x)
1277 u, v, w = self.uvw
1279 t = cb * u + sb * v
1280 E = cb * v - sb * u
1281 N = ca * w - sa * t
1282 U = ca * t + sa * w
1283 return Enu4Tuple(E, N, U, name=self.name) if Enu is None else \
1284 Enu( E, N, U, **_xkwds(Enu_kwds, name=self.name))
1286 u = Vector3d.x
1288 @Property_RO
1289 def uvw(self):
1290 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1291 '''
1292 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1294 v = Vector3d.y
1295 w = Vector3d.z
1298class Uvw3Tuple(_NamedTuple):
1299 '''3-Tuple C{(u, v, w)}, in C{meter}.
1300 '''
1301 _Names_ = ('u', 'v', 'w')
1302 _Units_ = ( Meter, Meter, Meter)
1305class Los(Aer):
1306 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1307 '''
1309 def __init__(self, azimuth_aer, elevation=0, name=NN):
1310 '''New L{Los}.
1312 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1313 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1314 L{Enu4Tuple} or L{Los}).
1315 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1316 is 0, zenith +90, nadir -90), only used with scalar
1317 B{C{azimuth_aer}}.
1318 @kwarg name: Optional name (C{str}).
1320 @raise TypeError: Invalid B{C{azimuth_aer}}.
1322 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1323 '''
1324 t = Aer(azimuth_aer, elevation)
1325 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, name=name)
1327 def toUvw(self, location, Uvw=Uvw, **Uvw_kwds):
1328 '''Get this LOS' I{target} (UVW) components from a location.
1330 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1331 L{Vector3d}) location from where to cast the L{Los}.
1333 @see: Method L{Enu.toUvw} for further details.
1334 '''
1335 return self.toEnu().toUvw(location, Uvw=Uvw, **Uvw_kwds)
1337 def toEnu(self, Enu=Enu, **Enu_kwds):
1338 '''Get this LOS as I{East, North, Up} (ENU) components.
1340 @see: Method L{Aer.toEnu} for further details.
1341 '''
1342 return Aer.toEnu(self, Enu=Enu, **Enu_kwds)
1345class ChLV9Tuple(Local9Tuple):
1346 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1347 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1348 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1349 otherwise like L{Local9Tuple}.
1350 '''
1351 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1353 @Property_RO
1354 def E_LV95(self):
1355 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1356 '''
1357 return self.EN2_LV95.E_LV95
1359 @Property_RO
1360 def EN2_LV95(self):
1361 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1362 '''
1363 return ChLVEN2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1365 @Property_RO
1366 def h_LV03(self):
1367 '''Get the I{Swiss h_} height (C{meter}).
1368 '''
1369 return self.h_
1371 @Property_RO
1372 def h_LV95(self):
1373 '''Get the I{Swiss h_} height (C{meter}).
1374 '''
1375 return self.h_
1377 @property_RO
1378 def isChLV(self):
1379 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1380 '''
1381 return self.ltp.__class__ is _MODS.ltp.ChLV
1383 @property_RO
1384 def isChLVa(self):
1385 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1386 '''
1387 return self.ltp.__class__ is _MODS.ltp.ChLVa
1389 @property_RO
1390 def isChLVe(self):
1391 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1392 '''
1393 return self.ltp.__class__ is _MODS.ltp.ChLVe
1395 @Property_RO
1396 def N_LV95(self):
1397 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1398 '''
1399 return self.EN2_LV95.N_LV95
1401 @Property_RO
1402 def x(self):
1403 '''Get the I{local x, Swiss Y} easting (C{meter}).
1404 '''
1405 return self.Y
1407 @Property_RO
1408 def x_LV03(self):
1409 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1410 '''
1411 return self.yx2_LV03.x_LV03
1413 @Property_RO
1414 def y(self):
1415 '''Get the I{local y, Swiss X} northing (C{meter}).
1416 '''
1417 return self.X
1419 @Property_RO
1420 def y_LV03(self):
1421 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1422 '''
1423 return self.yx2_LV03.y_LV03
1425 @Property_RO
1426 def YX(self):
1427 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1428 '''
1429 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1431 @Property_RO
1432 def yx2_LV03(self):
1433 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1434 '''
1435 return ChLVyx2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1437 @Property_RO
1438 def z(self):
1439 '''Get the I{local z, Swiss h_} height (C{meter}).
1440 '''
1441 return self.h_
1444class ChLVYX2Tuple(_NamedTuple):
1445 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1446 in C{meter}.
1447 '''
1448 _Names_ = (_Y_, _X_)
1449 _Units_ = ( Meter, Meter)
1451 def false2(self, LV95=True):
1452 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1454 @see: Function L{ChLV.false2} for more information.
1455 '''
1456 return _MODS.ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1459class ChLVEN2Tuple(_NamedTuple):
1460 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1461 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1462 '''
1463 _Names_ = ('E_LV95', 'N_LV95')
1464 _Units_ = ChLVYX2Tuple._Units_
1466 def unfalse2(self):
1467 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1469 @see: Function L{ChLV.unfalse2} for more information.
1470 '''
1471 return _MODS.ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1474class ChLVyx2Tuple(_NamedTuple):
1475 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1476 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1477 '''
1478 _Names_ = ('y_LV03', 'x_LV03')
1479 _Units_ = ChLVYX2Tuple._Units_
1481 def unfalse2(self):
1482 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1484 @see: Function L{ChLV.unfalse2} for more information.
1485 '''
1486 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1489class Footprint5Tuple(_NamedTuple):
1490 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1491 with the C{center} and 4 corners of the I{local} projection of
1492 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1494 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1495 '''
1496 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1497 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1499 def toLatLon5(self, ltp=None, LatLon=None, **LatLon_kwds):
1500 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1501 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1503 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1504 footprint's C{center} or C{frustrum} C{ltp}.
1505 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1506 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1507 arguments, ignored if C{B{LatLon} is None}.
1509 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon,
1510 **B{LatLon_kwds})} instances or if C{B{LatLon} is None},
1511 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively
1512 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending
1513 on keyword argument C{datum} is un-/specified.
1515 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{LatLon_kwds}}.
1517 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1518 '''
1519 kwds = _xkwds(LatLon_kwds, ltp=_MODS.ltp._xLtp(ltp, self.center.ltp), # PYCHOK .center
1520 LatLon=LatLon, name=self.name,)
1521 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1523 def xyzLocal5(self, ltp=None):
1524 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1526 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1527 the {center} and corner C{ltp}s.
1529 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1531 @raise TypeError: Invalid B{C{ltp}}.
1532 '''
1533 if ltp is None:
1534 p = self
1535 else:
1536 p = _MODS.ltp._xLtp(ltp)
1537 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1538 return Footprint5Tuple(t.xyzLocal for t in p)
1541__all__ += _ALL_DOCS(_NamedAerNed)
1543# **) MIT License
1544#
1545# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1546#
1547# Permission is hereby granted, free of charge, to any person obtaining a
1548# copy of this software and associated documentation files (the "Software"),
1549# to deal in the Software without restriction, including without limitation
1550# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1551# and/or sell copies of the Software, and to permit persons to whom the
1552# Software is furnished to do so, subject to the following conditions:
1553#
1554# The above copyright notice and this permission notice shall be included
1555# in all copies or substantial portions of the Software.
1556#
1557# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1558# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1559# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1560# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1561# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1562# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1563# OTHER DEALINGS IN THE SOFTWARE.