Coverage for pygeodesy/ltpTuples.py: 94%
555 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-02 14:35 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-02 14:35 -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, _Pass, _xnamed
26from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
27from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
28 Property_RO, property_RO
29from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
30from pygeodesy.units import Bearing, Degrees, Degrees_, Height, _isDegrees, \
31 _isMeter, Lat, Lon, Meter, Meter_, issubclassof
32from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_
33from pygeodesy.vector3d import Vector3d
35from math import cos, radians
37__all__ = _ALL_LAZY.ltpTuples
38__version__ = '24.04.07'
40_aer_ = 'aer'
41_alt_ = 'alt'
42_enu_ = 'enu'
43_h__ = 'h_'
44_ned_ = 'ned'
45_local_ = 'local'
46_roll_ = 'roll'
47_slantrange_ = 'slantrange'
48_tilt_ = 'tilt'
49_uvw_ = 'uvw'
50_yaw_ = 'yaw'
53def _er2gr(e, r):
54 '''(INTERNAL) Elevation and slant range to ground range.
55 '''
56 c = cos(radians(e))
57 return Meter_(groundrange=r * c)
60def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
61 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
62 '''
63 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
64 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
65 t = strs(t, prec=3 if prec is None else prec)
66 if sep:
67 t = sep.join(t)
68 if fmt:
69 t = fmt(t)
70 return a, t
73def _4Tuple2Cls(inst, Cls, Cls_kwds):
74 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
75 '''
76 if Cls is None:
77 return inst
78 elif issubclassof(Cls, Aer):
79 return inst.xyzLocal.toAer(Aer=Cls, **Cls_kwds)
80 elif issubclassof(Cls, Enu): # PYCHOK no cover
81 return inst.xyzLocal.toEnu(Enu=Cls, **Cls_kwds)
82 elif issubclassof(Cls, Ned):
83 return inst.xyzLocal.toNed(Ned=Cls, **Cls_kwds)
84 elif issubclassof(Cls, XyzLocal): # PYCHOK no cover
85 return inst.xyzLocal.toXyz(Xyz=Cls, **Cls_kwds)
86 elif Cls is Local9Tuple: # PYCHOK no cover
87 return inst.xyzLocal.toLocal9Tuple(**Cls_kwds)
88 n = inst.__class__.__name__[:3] # PYCHOK no cover
89 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
92def _xyz2aer4(inst):
93 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
94 '''
95 x, y, z, _ = inst.xyz4
96 A = Bearing(azimuth=atan2b(x, y))
97 E = Degrees(elevation=atan2d(z, hypot(x, y)))
98 R = Meter(slantrange=hypot_(x, y, z))
99 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
102def _xyzLocal(*Types, **name_inst):
103 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
104 '''
105 n, inst = _xkwds_item2(name_inst)
106 if isinstance(inst, Types):
107 return None
108 try:
109 return inst.xyzLocal
110 except (AttributeError, TypeError):
111 raise _TypeError(n, inst, txt=_not_(_local_))
114class _NamedAerNed(_NamedBase):
115 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
116 '''
117 _ltp = None # local tangent plane (C{Ltp}), origin
119 @Property_RO
120 def ltp(self):
121 '''Get the I{local tangent plane} (L{Ltp}).
122 '''
123 return self._ltp
125 def toAer(self, Aer=None, **Aer_kwds):
126 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
128 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
129 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
130 arguments, ignored if B{C{Aer}} is C{None}.
132 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
133 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
134 '''
135 return self.xyz4._toXyz(Aer, Aer_kwds)
137 def toEnu(self, Enu=None, **Enu_kwds):
138 '''Get the I{local} I{East, North, Up} (ENU) components.
140 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
141 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
142 arguments, ignored if C{B{Enu} is None}.
144 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
145 an L{Enu4Tuple}C{(east, north, up, ltp)}.
146 '''
147 return self.xyz4._toXyz(Enu, Enu_kwds)
149 def toNed(self, Ned=None, **Ned_kwds):
150 '''Get the I{local} I{North, East, Down} (NED) components.
152 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
153 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
154 arguments, ignored if B{C{Ned}} is C{None}.
156 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
157 an L{Ned4Tuple}C{(north, east, down, ltp)}.
158 '''
159 return self.xyz4._toXyz(Ned, Ned_kwds)
161 def toXyz(self, Xyz=None, **Xyz_kwds):
162 '''Get the local I{X, Y, Z} (XYZ) components.
164 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
165 L{Ned}, L{Aer}) or C{None}.
166 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
167 arguments, ignored if C{B{Xyz} is None}.
169 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
170 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
172 @raise TypeError: Invalid B{C{Xyz}}.
173 '''
174 return self.xyz4._toXyz(Xyz, Xyz_kwds)
176 @Property_RO
177 def xyz(self):
178 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
179 '''
180 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
182 @property_RO
183 def xyz4(self): # PYCHOK no cover
184 '''I{Must be overloaded}.'''
185 self._notOverloaded()
187 @Property_RO
188 def xyzLocal(self):
189 '''Get this AER or NED as an L{XyzLocal}.
190 '''
191 return XyzLocal(self.xyz4, name=self.name)
194class Aer(_NamedAerNed):
195 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
196 '''
197 _azimuth = _0_0 # bearing from North (C{degrees360})
198 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
199# _ltp = None # local tangent plane (C{Ltp}), origin
200 _slantrange = _0_0 # distance (C{Meter})
201 _toStr = _aer_
203 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, name=NN):
204 '''New L{Aer}.
206 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
207 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
208 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
209 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
210 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
211 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
212 only used with scalar B{C{azimuth_aer}}.
213 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
214 B{C{azimuth_aer}}.
215 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
216 L{LocalCartesian}).
217 @kwarg name: Optional name (C{str}).
219 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
221 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
222 or B{C{slantrange}}.
223 '''
224 if _isDegrees(azimuth_aer):
225 self._azimuth = Bearing(azimuth=azimuth_aer)
226 self._elevation = Degrees_(elevation=elevation, low=_N_90_0, high=_90_0)
227 self._slantrange = Meter_(slantrange=slantrange)
228 p, n = ltp, name
229 else: # PYCHOK no cover
230 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
231 aer = p.toAer() if p else azimuth_aer
232 self._azimuth, self._elevation, self._slantrange = \
233 aer.azimuth, aer.elevation, aer.slantrange
234 p = _xattr(aer, ltp=ltp)
235 n = name or _xattr(aer, name=name)
237 if p:
238 self._ltp = _MODS.ltp._xLtp(p)
239 if name:
240 self.name = n
242 @Property_RO
243 def aer4(self):
244 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
245 '''
246 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
248 @Property_RO
249 def azimuth(self):
250 '''Get the Azimuth, bearing from North (C{degrees360}).
251 '''
252 return self._azimuth
254 @Property_RO
255 def down(self):
256 '''Get the Down component (C{meter}).
257 '''
258 return self.xyzLocal.down
260 @Property_RO
261 def east(self):
262 '''Get the East component (C{meter}).
263 '''
264 return self.xyzLocal.east
266 @Property_RO
267 def elevation(self):
268 '''Get the Elevation, tilt above horizon (C{degrees90}).
269 '''
270 return self._elevation
272 @Property_RO
273 def groundrange(self):
274 '''Get the I{ground range}, distance (C{meter}).
275 '''
276 return _er2gr(self._elevation, self._slantrange)
278 @Property_RO
279 def north(self):
280 '''Get the North component (C{meter}).
281 '''
282 return self.xyzLocal.north
284 @Property_RO
285 def slantrange(self):
286 '''Get the I{slant Range}, distance (C{meter}).
287 '''
288 return self._slantrange
290 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
291 '''Return a string representation of this AER as azimuth
292 (bearing), elevation and slant range.
294 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
295 @kwarg fmt: Enclosing backets format (C{str}).
296 @kwarg sep: Optional separator between AERs (C{str}).
298 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
299 '''
300 t = (toDMS(self.azimuth, form=F_D, prec=prec, ddd=0),
301 toDMS(self.elevation, form=F_D, prec=prec, ddd=0),
302 fstr( self.slantrange, prec=3 if prec is None else prec))
303 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
305 def toStr(self, **prec_fmt_sep): # PYCHOK expected
306 '''Return a string representation of this AER.
308 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
309 number of (decimal) digits, unstripped
310 (C{int}), C{B{fmt}='[]'} the enclosing
311 backets format (C{str}) and separator
312 C{B{sep}=', '} to join (C{str}).
314 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
315 '''
316 _, t = _toStr2(self, **prec_fmt_sep)
317 return t
319 @Property_RO
320 def up(self):
321 '''Get the Up component (C{meter}).
322 '''
323 return self.xyzLocal.up
325 @Property_RO
326 def x(self):
327 '''Get the X component (C{meter}).
328 '''
329 return self.xyz4.x
331 @Property_RO
332 def xyz4(self):
333 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
334 '''
335 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
336 R = self._slantrange
337 r = cE * R # ground range
338 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
340 @Property_RO
341 def y(self):
342 '''Get the Y component (C{meter}).
343 '''
344 return self.xyz4.y
346 @Property_RO
347 def z(self):
348 '''Get the Z component (C{meter}).
349 '''
350 return self.xyz4.z
353class Aer4Tuple(_NamedTuple):
354 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
355 all in C{meter} except C{ltp}.
356 '''
357 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
358 _Units_ = ( Meter, Meter, Meter, _Pass)
360 def _toAer(self, Cls, Cls_kwds):
361 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
362 '''
363 if issubclassof(Cls, Aer):
364 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
365 else:
366 return _4Tuple2Cls(self, Cls, Cls_kwds)
368 @Property_RO
369 def groundrange(self):
370 '''Get the I{ground range}, distance (C{meter}).
371 '''
372 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
374 @Property_RO
375 def xyzLocal(self):
376 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
377 '''
378 return Aer(self).xyzLocal
381class Attitude4Tuple(_NamedTuple):
382 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
383 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
384 the attitude of a plane or camera.
385 '''
386 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
387 _Units_ = ( Meter, Bearing, Degrees, Degrees)
389 @Property_RO
390 def atyr(self):
391 '''Return this attitude (L{Attitude4Tuple}).
392 '''
393 return self
395 @Property_RO
396 def tyr3d(self):
397 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
398 '''
399 return _MODS.ltp.Attitude(self).tyr3d
402class Ned(_NamedAerNed):
403 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
405 @see: L{Enu} and L{Ltp}.
406 '''
407 _down = _0_0 # down, -XyzLocal.z (C{meter}).
408 _east = _0_0 # east, XyzLocal.y (C{meter}).
409# _ltp = None # local tangent plane (C{Ltp}), origin
410 _north = _0_0 # north, XyzLocal.x (C{meter})
411 _toStr = _ned_
413 def __init__(self, north_ned, east=0, down=0, ltp=None, name=NN):
414 '''New L{Ned} vector.
416 @arg north_ned: Scalar North component (C{meter}) or a previous
417 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
418 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
419 L{XyzLocal} or L{Xyz4Tuple}).
420 @kwarg east: Scalar East component (C{meter}), only used with
421 scalar B{C{north_ned}}.
422 @kwarg down: Scalar Down component, normal to I{inside} surface
423 of the ellipsoid or sphere (C{meter}), only used with
424 scalar B{C{north_ned}}.
425 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
426 L{LocalCartesian}).
427 @kwarg name: Optional name (C{str}).
429 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
431 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
432 '''
433 if _isMeter(north_ned):
434 self._north = Meter(north=north_ned or _0_0)
435 self._east = Meter(east=east or _0_0)
436 self._down = Meter(down=down or _0_0)
437 p, n = ltp, name
438 else: # PYCHOK no cover
439 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
440 ned = p.toNed() if p else north_ned
441 self._north, self._east, self._down = ned.north, ned.east, ned.down
442 p = _xattr(ned, ltp=ltp)
443 n = name or _xattr(ned, name=name)
445 if p:
446 self._ltp = _MODS.ltp._xLtp(p)
447 if n:
448 self.name = n
450 @Property_RO
451 def aer4(self):
452 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
453 '''
454 return _xyz2aer4(self)
456 @Property_RO
457 def azimuth(self):
458 '''Get the Azimuth, bearing from North (C{degrees360}).
459 '''
460 return self.aer4.azimuth
462 @deprecated_Property_RO
463 def bearing(self):
464 '''DEPRECATED, use C{azimuth}.'''
465 return self.azimuth
467 @Property_RO
468 def down(self):
469 '''Get the Down component (C{meter}).
470 '''
471 return self._down
473 @Property_RO
474 def east(self):
475 '''Get the East component (C{meter}).
476 '''
477 return self._east
479 @Property_RO
480 def elevation(self):
481 '''Get the Elevation, tilt above horizon (C{degrees90}).
482 '''
483 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
485 @Property_RO
486 def groundrange(self):
487 '''Get the I{ground range}, distance (C{meter}).
488 '''
489 return Meter(groundrange=hypot(self.north, self.east))
491 @deprecated_Property_RO
492 def length(self):
493 '''DEPRECATED, use C{slantrange}.'''
494 return self.slantrange
496 @deprecated_Property_RO
497 def ned(self):
498 '''DEPRECATED, use property C{ned4}.'''
499 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
501 @Property_RO
502 def ned4(self):
503 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
504 '''
505 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
507 @Property_RO
508 def north(self):
509 '''Get the North component (C{meter}).
510 '''
511 return self._north
513 @Property_RO
514 def slantrange(self):
515 '''Get the I{slant Range}, distance (C{meter}).
516 '''
517 return self.aer4.slantrange
519 @deprecated_method
520 def to3ned(self): # PYCHOK no cover
521 '''DEPRECATED, use property L{ned4}.'''
522 return self.ned # XXX deprecated too
524 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
525 '''Return a string representation of this NED.
527 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
528 @kwarg fmt: Enclosing backets format (C{str}).
529 @kwarg sep: Separator to join (C{str}).
531 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
532 '''
533 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
534 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
536 def toStr(self, **prec_fmt_sep): # PYCHOK expected
537 '''Return a string representation of this NED.
539 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
540 number of (decimal) digits, unstripped
541 (C{int}), C{B{fmt}='[]'} the enclosing
542 backets format (C{str}) and separator
543 C{B{sep}=', '} to join (C{str}).
545 @return: This NED as "[meter, meter, meter]" (C{str}).
546 '''
547 _, t = _toStr2(self, **prec_fmt_sep)
548 return t
550 @deprecated_method
551 def toVector3d(self):
552 '''DEPRECATED, use property L{xyz}.'''
553 return self.xyz
555 @Property_RO
556 def up(self):
557 '''Get the Up component (C{meter}).
558 '''
559 return Meter(up=-self._down) # negated
561 @Property_RO
562 def x(self):
563 '''Get the X component (C{meter}).
564 '''
565 return Meter(x=self._east) # 2nd arg, E
567 @Property_RO
568 def xyz4(self):
569 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
570 '''
571 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
573 @Property_RO
574 def y(self):
575 '''Get the Y component (C{meter}).
576 '''
577 return Meter(y=self._north) # 1st arg N
579 @Property_RO
580 def z(self):
581 '''Get the Z component (C{meter}).
582 '''
583 return Meter(z=-self._down) # negated
586class Ned4Tuple(_NamedTuple):
587 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
588 '''
589 _Names_ = (_north_, _east_, _down_, _ltp_)
590 _Units_ = ( Meter, Meter, Meter, _Pass)
592 def _toNed(self, Cls, Cls_kwds):
593 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
594 '''
595 if issubclassof(Cls, Ned):
596 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
597 else:
598 return _4Tuple2Cls(self, Cls, Cls_kwds)
600 @Property_RO
601 def xyzLocal(self):
602 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
603 '''
604 return Ned(self).xyzLocal
607class _Vector3d(Vector3d):
609 _toStr = _xyz_
611 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
612 '''Return a string representation of this ENU/NED/XYZ.
614 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
615 @kwarg fmt: Enclosing backets format (C{str}).
616 @kwarg sep: Separator to join (C{str}).
618 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
619 "[N:meter, E:meter, D:meter]",
620 "[U:meter, V:meter, W:meter]" respectively
621 "[X:meter, Y:meter, Z:meter]" (C{str}).
622 '''
623 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
624 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
626 def toStr(self, **prec_fmt_sep): # PYCHOK expected
627 '''Return a string representation of this XYZ.
629 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
630 number of (decimal) digits, unstripped
631 (C{int}), C{B{fmt}='[]'} the enclosing
632 backets format (C{str}) and separator
633 C{B{sep}=', '} to join (C{str}).
635 @return: This XYZ as "[meter, meter, meter]" (C{str}).
636 '''
637 _, t = _toStr2(self, **prec_fmt_sep)
638 return t
641class XyzLocal(_Vector3d):
642 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
643 also base class for local L{Enu}.
644 '''
645 _ltp = None # local tangent plane (C{Ltp}), origin
647 def __init__(self, x_xyz, y=0, z=0, ltp=None, name=NN):
648 '''New L{XyzLocal}.
650 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
651 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
652 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
653 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
654 @kwarg y: Scalar Y component (C{meter}), only used with scalar
655 B{C{x_xyz}}, C{positive north}.
656 @kwarg z: Scalar Z component, normal C{positive up} from the
657 surface of the ellipsoid or sphere (C{meter}), only
658 used with scalar B{C{x_xyz}}.
659 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
660 L{LocalCartesian}).
662 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
664 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
665 '''
666 if _isMeter(x_xyz):
667 self._x = Meter(x=x_xyz or _0_0)
668 self._y = Meter(y=y or _0_0)
669 self._z = Meter(z=z or _0_0)
670 p, n = ltp, name
671 else:
672 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
673 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z
674 p = _xattr(xyz, ltp=ltp)
675 n = name or _xattr(xyz, name=NN)
677 if p:
678 self._ltp = _MODS.ltp._xLtp(p)
679 if n:
680 self.name = n
682 def __str__(self):
683 return self.toStr()
685 @Property_RO
686 def aer4(self):
687 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
688 '''
689 return _xyz2aer4(self)
691 @Property_RO
692 def azimuth(self):
693 '''Get the Azimuth, bearing from North (C{degrees360}).
695 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
696 Transformations_between_ECEF_and_ENU_coordinates>}.
697 '''
698 return self.aer4.azimuth
700 def classof(self, *args, **kwds): # PYCHOK no cover
701 '''Create another instance of this very class.
703 @arg args: Optional, positional arguments.
704 @kwarg kwds: Optional, keyword arguments.
706 @return: New instance (C{self.__class__}).
707 '''
708 kwds = _xkwds(kwds, ltp=self.ltp, name=self.name)
709 return self.__class__(*args, **kwds)
711 @Property_RO
712 def down(self):
713 '''Get the Down component (C{meter}).
714 '''
715 return Meter(down=-self.z)
717 @property_RO
718 def ecef(self):
719 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
720 '''
721 return self.ltp.ecef
723 @Property_RO
724 def east(self):
725 '''Get the East component (C{meter}).
726 '''
727 return Meter(east=self.x)
729 @Property_RO
730 def elevation(self):
731 '''Get the Elevation, tilt above horizon (C{degrees90}).
733 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
734 Transformations_between_ECEF_and_ENU_coordinates>}.
735 '''
736 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
738 @Property_RO
739 def enu4(self):
740 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
741 '''
742 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
744 @Property_RO
745 def groundrange(self):
746 '''Get the I{ground range}, distance (C{meter}).
747 '''
748 return Meter(groundrange=hypot(self.x, self.y))
750 @Property_RO
751 def ltp(self):
752 '''Get the I{local tangent plane} (L{Ltp}).
753 '''
754 return self._ltp
756 @Property_RO
757 def ned4(self):
758 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
759 '''
760 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
762 @Property_RO
763 def north(self):
764 '''Get the North component (C{meter}).
765 '''
766 return Meter(north=self.y)
768 @Property_RO
769 def slantrange(self):
770 '''Get the I{slant Range}, distance (C{meter}).
771 '''
772 return self.aer4.slantrange
774 def toAer(self, Aer=None, **Aer_kwds):
775 '''Get the local I{Azimuth, Elevation, slantRange} components.
777 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
778 @kwarg Aer_kwds: Optional, additional B{C{Aer}} keyword
779 arguments, ignored if C{B{Aer} is None}.
781 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
782 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
784 @raise TypeError: Invalid B{C{Aer}}.
785 '''
786 return self.aer4._toAer(Aer, Aer_kwds)
788 def toCartesian(self, Cartesian=None, ltp=None, **Cartesian_kwds):
789 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
791 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
792 or C{None}.
793 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
794 overriding this C{ltp}.
795 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
796 arguments, ignored if C{B{Cartesian} is None}.
798 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an
799 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
800 with C{M=None}, always.
802 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or
803 B{C{Cartesian_kwds}} argument.
804 '''
805 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
806 if Cartesian is None:
807 r = ltp._local2ecef(self, nine=True)
808 else:
809 x, y, z = ltp._local2ecef(self)
810 kwds = _xkwds(Cartesian_kwds, datum=ltp.datum)
811 r = Cartesian(x, y, z, **kwds)
812 return _xnamed(r, self.name or ltp.name)
814 def toEnu(self, Enu=None, **Enu_kwds):
815 '''Get the local I{East, North, Up} (ENU) components.
817 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
818 @kwarg Enu_kwds: Optional, additional B{C{Enu}} keyword
819 arguments, ignored if C{B{Enu} is None}.
821 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
822 an L{Enu4Tuple}C{(east, north, up, ltp)}.
823 '''
824 return self.enu4._toEnu(Enu, Enu_kwds)
826 def toLatLon(self, LatLon=None, ltp=None, **LatLon_kwds):
827 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
829 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon})
830 or C{None}.
831 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
832 overriding this ENU/NED/AER/XYZ's LTP.
833 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
834 arguments, ignored if C{B{LatLon} is None}.
836 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an
837 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
838 datum)} with C{M=None}, always.
840 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or
841 B{C{LatLon_kwds}} argument.
842 '''
843 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
844 r = ltp._local2ecef(self, nine=True)
845 if LatLon is None:
846 r = _xnamed(r, self.name or ltp.name)
847 else:
848 kwds = _xkwds(LatLon_kwds, height=r.height, datum=r.datum,
849 name=self.name or ltp.name)
850 r = LatLon(r.lat, r.lon, **kwds) # XXX ltp?
851 return r
853 def toLocal9Tuple(self, M=False, name=NN):
854 '''Get this local as a C{Local9Tuple}.
856 @kwarg M: Optionally include the rotation matrix (C{bool}).
857 @kwarg name: Optional name (C{str}).
859 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp,
860 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an
861 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}.
862 '''
863 ltp = self.ltp # see C{self.toLatLon}
864 t = ltp._local2ecef(self, nine=True, M=M)
865 return Local9Tuple(self.x, self.y, self.z, t.lat, t.lon, t.height,
866 ltp, t, t.M, name=name or t.name)
868 def toNed(self, Ned=None, **Ned_kwds):
869 '''Get the local I{North, East, Down} (Ned) components.
871 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
872 @kwarg Ned_kwds: Optional, additional B{C{Ned}} keyword
873 arguments, ignored if C{B{Ned} is None}.
875 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
876 an L{Ned4Tuple}C{(north, east, down, ltp)}.
877 '''
878 return self.ned4._toNed(Ned, Ned_kwds)
880 def toXyz(self, Xyz=None, **Xyz_kwds):
881 '''Get the local I{X, Y, Z} (XYZ) components.
883 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
884 L{Ned}, L{Aer}) or C{None}.
885 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
886 arguments, ignored if C{B{Xyz} is None}.
888 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
889 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
890 '''
891 return self.xyz4._toXyz(Xyz, Xyz_kwds)
893 @Property_RO
894 def up(self):
895 '''Get the Up component (C{meter}).
896 '''
897 return Meter(up=self.z)
899# @Property_RO
900# def x(self): # see: Vector3d.x
901# '''Get the X component (C{meter}).
902# '''
903# return self._x
905# @Property_RO
906# def xyz(self): # see: Vector3d.xyz
907# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
908# '''
909# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
911 @Property_RO
912 def xyz4(self):
913 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
914 '''
915 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
917 @Property_RO
918 def xyzLocal(self):
919 '''Get this L{XyzLocal}.
920 '''
921 return self
923# @Property_RO
924# def y(self): # see: Vector3d.y
925# '''Get the Y component (C{meter}).
926# '''
927# return self._y
929# @Property_RO
930# def z(self): # see: Vector3d.z
931# '''Get the Z component (C{meter}).
932# '''
933# return self._z
936class Xyz4Tuple(_NamedTuple):
937 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
938 '''
939 _Names_ = (_x_, _y_, _z_, _ltp_)
940 _Units_ = ( Meter, Meter, Meter, _Pass)
942 def _toXyz(self, Cls, Cls_kwds):
943 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
944 '''
945 if issubclassof(Cls, XyzLocal):
946 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
947 else:
948 return _4Tuple2Cls(self, Cls, Cls_kwds)
950 @Property_RO
951 def xyzLocal(self):
952 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
953 '''
954 return XyzLocal(*self, name=self.name)
957class Enu(XyzLocal):
958 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
960 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
961 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
962 '''
963 _toStr = _enu_
965 def __init__(self, east_enu, north=0, up=0, ltp=None, name=NN):
966 '''New L{Enu}.
968 @arg east_enu: Scalar East component (C{meter}) or a previous
969 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
970 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
971 L{XyzLocal} or L{Xyz4Tuple}).
972 @kwarg north: Scalar North component (C{meter}) only used with
973 scalar B{C{east_enu}}.
974 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
975 normal from the surface of the ellipsoid or sphere (C{meter}).
976 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
977 L{LocalCartesian}).
978 @kwarg name: Optional name (C{str}).
980 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
982 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
983 '''
984 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, name=name)
986 def toUvw(self, location, Uvw=None, **Uvw_kwds):
987 '''Get the I{u, v, w} (UVW) components at a location.
989 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
990 L{Vector3d}) location, like a Point-Of-View.
991 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
992 @kwarg Uvw_kwds: Optional, additional B{L{Uvw}} keyword
993 arguments, ignored if C{B{Uvw} is None}.
995 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
996 L{Uvw3Tuple}C{(u, v, w)}.
998 @raise TypeError: InvalidB{C{location}}.
1000 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1001 '''
1002 try:
1003 sa, ca, sb, cb = sincos2_(*location.philam)
1004 except Exception as x:
1005 raise _TypeError(location=location, cause=x)
1006 e, n, u, _ = self.enu4
1008 t = ca * u - sa * n
1009 U = cb * t - sb * e
1010 V = cb * e + sb * t
1011 W = ca * n + sa * u
1012 return Uvw3Tuple(U, V, W, name=self.name) if Uvw is None else \
1013 Uvw( U, V, W, **_xkwds(Uvw_kwds, name=self.name))
1015 @Property_RO
1016 def xyzLocal(self):
1017 '''Get this ENU as an L{XyzLocal}.
1018 '''
1019 return XyzLocal(*self.xyz4, name=self.name)
1022class Enu4Tuple(_NamedTuple):
1023 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1024 '''
1025 _Names_ = (_east_, _north_, _up_, _ltp_)
1026 _Units_ = ( Meter, Meter, Meter, _Pass)
1028 def _toEnu(self, Cls, Cls_kwds):
1029 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1030 '''
1031 if issubclassof(Cls, XyzLocal):
1032 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
1033 else:
1034 return _4Tuple2Cls(self, Cls, Cls_kwds)
1036 @Property_RO
1037 def xyzLocal(self):
1038 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1039 '''
1040 return XyzLocal(*self, name=self.name)
1043class Local9Tuple(_NamedTuple):
1044 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1045 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1046 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1047 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1048 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1049 '''
1050 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1051 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1053 @Property_RO
1054 def azimuth(self):
1055 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1056 '''
1057 return self.xyzLocal.aer4.azimuth
1059 @Property_RO
1060 def down(self):
1061 '''Get the I{local} Down, C{-z} component (C{meter}).
1062 '''
1063 return -self.z
1065 @Property_RO
1066 def east(self):
1067 '''Get the I{local} East, C{x} component (C{meter}).
1068 '''
1069 return self.x
1071 @Property_RO
1072 def elevation(self):
1073 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1074 '''
1075 return self.xyzLocal.aer4.elevation
1077 @Property_RO
1078 def groundrange(self):
1079 '''Get the I{local} ground range, distance (C{meter}).
1080 '''
1081 return self.xyzLocal.aer4.groundrange
1083 @Property_RO
1084 def lam(self):
1085 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1086 '''
1087 return self.philam.lam
1089 @Property_RO
1090 def latlon(self):
1091 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1092 '''
1093 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1095 @Property_RO
1096 def latlonheight(self):
1097 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1098 '''
1099 return self.latlon.to3Tuple(self.height)
1101 @Property_RO
1102 def north(self):
1103 '''Get the I{local} North, C{y} component (C{meter}).
1104 '''
1105 return self.y
1107 @Property_RO
1108 def phi(self):
1109 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1110 '''
1111 return self.philam.phi
1113 @Property_RO
1114 def philam(self):
1115 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1116 '''
1117 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1119 @Property_RO
1120 def philamheight(self):
1121 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1122 '''
1123 return self.philam.to3Tuple(self.height)
1125 @Property_RO
1126 def slantrange(self):
1127 '''Get the I{local} slant Range, distance (C{meter}).
1128 '''
1129 return self.xyzLocal.aer4.slantrange
1131 def toAer(self, Aer=None, **Aer_kwds):
1132 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1134 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1135 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
1136 arguments, ignored if B{C{Aer}} is C{None}.
1138 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
1139 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1140 '''
1141 return self.xyzLocal.toAer(Aer=Aer, **Aer_kwds)
1143 def toCartesian(self, Cartesian=None, **Cartesian_kwds):
1144 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1146 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1147 or C{None}.
1148 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
1149 arguments, ignored if C{B{Cartesian} is None}.
1151 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance
1152 or a L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
1154 @raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}
1155 argument.
1156 '''
1157 return self.ecef.toCartesian(Cartesian=Cartesian, **Cartesian_kwds) # PYCHOK _Tuple
1159 def toEnu(self, Enu=None, **Enu_kwds):
1160 '''Get the I{local} I{East, North, Up} (ENU) components.
1162 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1163 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1164 arguments, ignored if C{B{Enu} is None}.
1166 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
1167 an L{Enu4Tuple}C{(east, north, up, ltp)}.
1168 '''
1169 return self.xyzLocal.toEnu(Enu=Enu, **Enu_kwds)
1171 def toLatLon(self, LatLon=None, **LatLon_kwds):
1172 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1174 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1175 (C{LatLon}) or C{None}.
1176 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1177 arguments, ignored if C{B{LatLon} is None}.
1179 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
1180 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon,
1181 height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
1182 datum)} depending on whether C{datum} is un-/specified.
1184 @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}
1185 argument.
1186 '''
1187 return self.ecef.toLatLon(LatLon=LatLon, **LatLon_kwds) # PYCHOK _Tuple
1189 def toNed(self, Ned=None, **Ned_kwds):
1190 '''Get the I{local} I{North, East, Down} (NED) components.
1192 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1193 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
1194 arguments, ignored if B{C{Ned}} is C{None}.
1196 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
1197 an L{Ned4Tuple}C{(north, east, down, ltp)}.
1198 '''
1199 return self.xyzLocal.toNed(Ned=Ned, **Ned_kwds)
1201 def toXyz(self, Xyz=None, **Xyz_kwds):
1202 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1204 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1205 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
1206 arguments, ignored if C{B{Xyz} is None}.
1208 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1209 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1210 '''
1211 return self.xyzLocal.toXyz(Xyz=Xyz, **Xyz_kwds)
1213 @Property_RO
1214 def up(self):
1215 '''Get the I{local} Up, C{z} component (C{meter}).
1216 '''
1217 return self.z
1219 @Property_RO
1220 def xyz(self):
1221 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1222 '''
1223 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1225 @Property_RO
1226 def xyzLocal(self):
1227 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1228 '''
1229 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1232_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1233_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1236class Uvw(_Vector3d):
1237 '''3-D C{u-v-w} (UVW) components.
1238 '''
1239 _toStr = _uvw_
1241 def __init__(self, u_uvw, v=0, w=0, name=NN):
1242 '''New L{Uvw}.
1244 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1245 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1246 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1247 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1248 @kwarg name: Optional name (C{str}).
1250 @raise TypeError: Invalid B{C{east_enu}}.
1252 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1253 '''
1254 Vector3d.__init__(self, u_uvw, v, w, name=name)
1256 def toEnu(self, location, Enu=Enu, **Enu_kwds):
1257 '''Get the I{East, North, Up} (ENU) components at a location.
1259 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1260 L{Vector3d}) location from where to cast the L{Los}.
1261 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1262 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1263 arguments, ignored if C{B{Enu} is None}.
1265 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1266 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1268 @raise TypeError: InvalidB{C{location}}.
1270 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1271 '''
1272 try:
1273 sa, ca, sb, cb = sincos2_(*location.philam)
1274 except Exception as x:
1275 raise _TypeError(location=location, cause=x)
1276 u, v, w = self.uvw
1278 t = cb * u + sb * v
1279 E = cb * v - sb * u
1280 N = ca * w - sa * t
1281 U = ca * t + sa * w
1282 return Enu4Tuple(E, N, U, name=self.name) if Enu is None else \
1283 Enu( E, N, U, **_xkwds(Enu_kwds, name=self.name))
1285 u = Vector3d.x
1287 @Property_RO
1288 def uvw(self):
1289 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1290 '''
1291 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1293 v = Vector3d.y
1294 w = Vector3d.z
1297class Uvw3Tuple(_NamedTuple):
1298 '''3-Tuple C{(u, v, w)}, in C{meter}.
1299 '''
1300 _Names_ = ('u', 'v', 'w')
1301 _Units_ = ( Meter, Meter, Meter)
1304class Los(Aer):
1305 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1306 '''
1308 def __init__(self, azimuth_aer, elevation=0, name=NN):
1309 '''New L{Los}.
1311 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1312 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1313 L{Enu4Tuple} or L{Los}).
1314 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1315 is 0, zenith +90, nadir -90), only used with scalar
1316 B{C{azimuth_aer}}.
1317 @kwarg name: Optional name (C{str}).
1319 @raise TypeError: Invalid B{C{azimuth_aer}}.
1321 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1322 '''
1323 t = Aer(azimuth_aer, elevation)
1324 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, name=name)
1326 def toUvw(self, location, Uvw=Uvw, **Uvw_kwds):
1327 '''Get this LOS' I{target} (UVW) components from a location.
1329 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1330 L{Vector3d}) location from where to cast the L{Los}.
1332 @see: Method L{Enu.toUvw} for further details.
1333 '''
1334 return self.toEnu().toUvw(location, Uvw=Uvw, **Uvw_kwds)
1336 def toEnu(self, Enu=Enu, **Enu_kwds):
1337 '''Get this LOS as I{East, North, Up} (ENU) components.
1339 @see: Method L{Aer.toEnu} for further details.
1340 '''
1341 return Aer.toEnu(self, Enu=Enu, **Enu_kwds)
1344class ChLV9Tuple(Local9Tuple):
1345 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1346 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1347 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1348 otherwise like L{Local9Tuple}.
1349 '''
1350 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1352 @Property_RO
1353 def E_LV95(self):
1354 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1355 '''
1356 return self.EN2_LV95.E_LV95
1358 @Property_RO
1359 def EN2_LV95(self):
1360 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1361 '''
1362 return ChLVEN2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1364 @Property_RO
1365 def h_LV03(self):
1366 '''Get the I{Swiss h_} height (C{meter}).
1367 '''
1368 return self.h_
1370 @Property_RO
1371 def h_LV95(self):
1372 '''Get the I{Swiss h_} height (C{meter}).
1373 '''
1374 return self.h_
1376 @property_RO
1377 def isChLV(self):
1378 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1379 '''
1380 return self.ltp.__class__ is _MODS.ltp.ChLV
1382 @property_RO
1383 def isChLVa(self):
1384 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1385 '''
1386 return self.ltp.__class__ is _MODS.ltp.ChLVa
1388 @property_RO
1389 def isChLVe(self):
1390 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1391 '''
1392 return self.ltp.__class__ is _MODS.ltp.ChLVe
1394 @Property_RO
1395 def N_LV95(self):
1396 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1397 '''
1398 return self.EN2_LV95.N_LV95
1400 @Property_RO
1401 def x(self):
1402 '''Get the I{local x, Swiss Y} easting (C{meter}).
1403 '''
1404 return self.Y
1406 @Property_RO
1407 def x_LV03(self):
1408 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1409 '''
1410 return self.yx2_LV03.x_LV03
1412 @Property_RO
1413 def y(self):
1414 '''Get the I{local y, Swiss X} northing (C{meter}).
1415 '''
1416 return self.X
1418 @Property_RO
1419 def y_LV03(self):
1420 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1421 '''
1422 return self.yx2_LV03.y_LV03
1424 @Property_RO
1425 def YX(self):
1426 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1427 '''
1428 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1430 @Property_RO
1431 def yx2_LV03(self):
1432 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1433 '''
1434 return ChLVyx2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1436 @Property_RO
1437 def z(self):
1438 '''Get the I{local z, Swiss h_} height (C{meter}).
1439 '''
1440 return self.h_
1443class ChLVYX2Tuple(_NamedTuple):
1444 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1445 in C{meter}.
1446 '''
1447 _Names_ = (_Y_, _X_)
1448 _Units_ = ( Meter, Meter)
1450 def false2(self, LV95=True):
1451 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1453 @see: Function L{ChLV.false2} for more information.
1454 '''
1455 return _MODS.ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1458class ChLVEN2Tuple(_NamedTuple):
1459 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1460 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1461 '''
1462 _Names_ = ('E_LV95', 'N_LV95')
1463 _Units_ = ChLVYX2Tuple._Units_
1465 def unfalse2(self):
1466 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1468 @see: Function L{ChLV.unfalse2} for more information.
1469 '''
1470 return _MODS.ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1473class ChLVyx2Tuple(_NamedTuple):
1474 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1475 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1476 '''
1477 _Names_ = ('y_LV03', 'x_LV03')
1478 _Units_ = ChLVYX2Tuple._Units_
1480 def unfalse2(self):
1481 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1483 @see: Function L{ChLV.unfalse2} for more information.
1484 '''
1485 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1488class Footprint5Tuple(_NamedTuple):
1489 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1490 with the C{center} and 4 corners of the I{local} projection of
1491 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1493 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1494 '''
1495 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1496 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1498 def toLatLon5(self, ltp=None, LatLon=None, **LatLon_kwds):
1499 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1500 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1502 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1503 footprint's C{center} or C{frustrum} C{ltp}.
1504 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1505 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1506 arguments, ignored if C{B{LatLon} is None}.
1508 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon,
1509 **B{LatLon_kwds})} instances or if C{B{LatLon} is None},
1510 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively
1511 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending
1512 on keyword argument C{datum} is un-/specified.
1514 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{LatLon_kwds}}.
1516 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1517 '''
1518 kwds = _xkwds(LatLon_kwds, ltp=_MODS.ltp._xLtp(ltp, self.center.ltp), # PYCHOK .center
1519 LatLon=LatLon, name=self.name,)
1520 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1522 def xyzLocal5(self, ltp=None):
1523 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1525 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1526 the {center} and corner C{ltp}s.
1528 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1530 @raise TypeError: Invalid B{C{ltp}}.
1531 '''
1532 if ltp is None:
1533 p = self
1534 else:
1535 p = _MODS.ltp._xLtp(ltp)
1536 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1537 return Footprint5Tuple(t.xyzLocal for t in p)
1540__all__ += _ALL_DOCS(_NamedAerNed)
1542# **) MIT License
1543#
1544# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1545#
1546# Permission is hereby granted, free of charge, to any person obtaining a
1547# copy of this software and associated documentation files (the "Software"),
1548# to deal in the Software without restriction, including without limitation
1549# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1550# and/or sell copies of the Software, and to permit persons to whom the
1551# Software is furnished to do so, subject to the following conditions:
1552#
1553# The above copyright notice and this permission notice shall be included
1554# in all copies or substantial portions of the Software.
1555#
1556# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1557# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1558# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1559# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1560# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1561# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1562# OTHER DEALINGS IN THE SOFTWARE.