Coverage for pygeodesy/ltpTuples.py: 94%
558 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-25 12:04 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-25 12:04 -0400
2# -*- coding: utf-8 -*-
4u'''Named, I{Local Tangent Plane} (LTP) tuples.
6Local coordinate classes L{XyzLocal}, L{Enu}, L{Ned} and L{Aer}
7and local coordinate tuples L{Local9Tuple}, L{Xyz4Tuple}, L{Enu4Tuple},
8L{Ned4Tuple}, L{Aer4Tuple}, L{ChLV9Tuple}, L{ChLVEN2Tuple},
9L{ChLVYX2Tuple}, L{ChLVyx2Tuple} and L{Footprint5Tuple}.
11@see: References in module L{ltp}.
12'''
14# from pygeodesy.basics import issubclassof # from .units
15from pygeodesy.constants import _0_0, _1_0, _90_0, _N_90_0
16from pygeodesy.dms import F_D, toDMS
17from pygeodesy.errors import _TypeError, _TypesError, _xattr, \
18 _xkwds, _xkwds_item2
19from pygeodesy.fmath import hypot, hypot_
20from pygeodesy.interns import NN, _4_, _azimuth_, _center_, _COMMASPACE_, \
21 _ecef_, _elevation_, _height_, _lat_, _lon_, \
22 _ltp_, _M_, _up_, _X_, _x_, _xyz_, _Y_, _y_, _z_
23from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
24from pygeodesy.named import _NamedBase, _NamedTuple, _Pass, _xnamed
25from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
26from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
27 Property_RO, property_RO
28from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
29from pygeodesy.units import Bearing, Degrees, Degrees_, Height, _isDegrees, \
30 _isMeter, Lat, Lon, Meter, Meter_, issubclassof
31from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_
32from pygeodesy.vector3d import Vector3d
34from math import cos, radians
36__all__ = _ALL_LAZY.ltpTuples
37__version__ = '24.05.24'
39_aer_ = 'aer'
40_alt_ = 'alt'
41_down_ = 'down'
42_east_ = 'east'
43_enu_ = 'enu'
44_h__ = 'h_'
45_ned_ = 'ned'
46_north_ = 'north'
47_local_ = 'local'
48_roll_ = 'roll'
49_slantrange_ = 'slantrange'
50_tilt_ = 'tilt'
51_uvw_ = 'uvw'
52_yaw_ = 'yaw'
55def _er2gr(e, r):
56 '''(INTERNAL) Elevation and slant range to ground range.
57 '''
58 c = cos(radians(e))
59 return Meter_(groundrange=r * c)
62def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
63 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
64 '''
65 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
66 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
67 t = strs(t, prec=3 if prec is None else prec)
68 if sep:
69 t = sep.join(t)
70 if fmt:
71 t = fmt(t)
72 return a, t
75def _4Tuple2Cls(inst, Cls, Cls_kwds):
76 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
77 '''
78 if Cls is None:
79 return inst
80 elif issubclassof(Cls, Aer):
81 return inst.xyzLocal.toAer(Aer=Cls, **Cls_kwds)
82 elif issubclassof(Cls, Enu): # PYCHOK no cover
83 return inst.xyzLocal.toEnu(Enu=Cls, **Cls_kwds)
84 elif issubclassof(Cls, Ned):
85 return inst.xyzLocal.toNed(Ned=Cls, **Cls_kwds)
86 elif issubclassof(Cls, XyzLocal): # PYCHOK no cover
87 return inst.xyzLocal.toXyz(Xyz=Cls, **Cls_kwds)
88 elif Cls is Local9Tuple: # PYCHOK no cover
89 return inst.xyzLocal.toLocal9Tuple(**Cls_kwds)
90 n = inst.__class__.__name__[:3] # PYCHOK no cover
91 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
94def _xyz2aer4(inst):
95 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
96 '''
97 x, y, z, _ = inst.xyz4
98 A = Bearing(azimuth=atan2b(x, y))
99 E = Degrees(elevation=atan2d(z, hypot(x, y)))
100 R = Meter(slantrange=hypot_(x, y, z))
101 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
104def _xyzLocal(*Types, **name_inst):
105 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
106 '''
107 n, inst = _xkwds_item2(name_inst)
108 if isinstance(inst, Types):
109 return None
110 try:
111 return inst.xyzLocal
112 except (AttributeError, TypeError):
113 raise _TypeError(n, inst, txt_not_=_local_)
116class _NamedAerNed(_NamedBase):
117 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
118 '''
119 _ltp = None # local tangent plane (C{Ltp}), origin
121 @Property_RO
122 def ltp(self):
123 '''Get the I{local tangent plane} (L{Ltp}).
124 '''
125 return self._ltp
127 def toAer(self, Aer=None, **Aer_kwds):
128 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
130 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
131 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
132 arguments, ignored if B{C{Aer}} is C{None}.
134 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
135 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
136 '''
137 return self.xyz4._toXyz(Aer, Aer_kwds)
139 def toEnu(self, Enu=None, **Enu_kwds):
140 '''Get the I{local} I{East, North, Up} (ENU) components.
142 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
143 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
144 arguments, ignored if C{B{Enu} is None}.
146 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
147 an L{Enu4Tuple}C{(east, north, up, ltp)}.
148 '''
149 return self.xyz4._toXyz(Enu, Enu_kwds)
151 def toNed(self, Ned=None, **Ned_kwds):
152 '''Get the I{local} I{North, East, Down} (NED) components.
154 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
155 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
156 arguments, ignored if B{C{Ned}} is C{None}.
158 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
159 an L{Ned4Tuple}C{(north, east, down, ltp)}.
160 '''
161 return self.xyz4._toXyz(Ned, Ned_kwds)
163 def toXyz(self, Xyz=None, **Xyz_kwds):
164 '''Get the local I{X, Y, Z} (XYZ) components.
166 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
167 L{Ned}, L{Aer}) or C{None}.
168 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
169 arguments, ignored if C{B{Xyz} is None}.
171 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
172 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
174 @raise TypeError: Invalid B{C{Xyz}}.
175 '''
176 return self.xyz4._toXyz(Xyz, Xyz_kwds)
178 @Property_RO
179 def xyz(self):
180 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
181 '''
182 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
184 @property_RO
185 def xyz4(self): # PYCHOK no cover
186 '''I{Must be overloaded}.'''
187 self._notOverloaded()
189 @Property_RO
190 def xyzLocal(self):
191 '''Get this AER or NED as an L{XyzLocal}.
192 '''
193 return XyzLocal(self.xyz4, name=self.name)
196class Aer(_NamedAerNed):
197 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
198 '''
199 _azimuth = _0_0 # bearing from North (C{degrees360})
200 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
201# _ltp = None # local tangent plane (C{Ltp}), origin
202 _slantrange = _0_0 # distance (C{Meter})
203 _toStr = _aer_
205 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, name=NN):
206 '''New L{Aer}.
208 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
209 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
210 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
211 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
212 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
213 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
214 only used with scalar B{C{azimuth_aer}}.
215 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
216 B{C{azimuth_aer}}.
217 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
218 L{LocalCartesian}).
219 @kwarg name: Optional name (C{str}).
221 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
223 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
224 or B{C{slantrange}}.
225 '''
226 if _isDegrees(azimuth_aer):
227 self._azimuth = Bearing(azimuth=azimuth_aer)
228 self._elevation = Degrees_(elevation=elevation, low=_N_90_0, high=_90_0)
229 self._slantrange = Meter_(slantrange=slantrange)
230 p, n = ltp, name
231 else: # PYCHOK no cover
232 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
233 aer = p.toAer() if p else azimuth_aer
234 self._azimuth, self._elevation, self._slantrange = \
235 aer.azimuth, aer.elevation, aer.slantrange
236 p = _xattr(aer, ltp=ltp)
237 n = name or _xattr(aer, name=name)
239 if p:
240 self._ltp = _MODS.ltp._xLtp(p)
241 if name:
242 self.name = n
244 @Property_RO
245 def aer4(self):
246 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
247 '''
248 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
250 @Property_RO
251 def azimuth(self):
252 '''Get the Azimuth, bearing from North (C{degrees360}).
253 '''
254 return self._azimuth
256 @Property_RO
257 def down(self):
258 '''Get the Down component (C{meter}).
259 '''
260 return self.xyzLocal.down
262 @Property_RO
263 def east(self):
264 '''Get the East component (C{meter}).
265 '''
266 return self.xyzLocal.east
268 @Property_RO
269 def elevation(self):
270 '''Get the Elevation, tilt above horizon (C{degrees90}).
271 '''
272 return self._elevation
274 @Property_RO
275 def groundrange(self):
276 '''Get the I{ground range}, distance (C{meter}).
277 '''
278 return _er2gr(self._elevation, self._slantrange)
280 @Property_RO
281 def north(self):
282 '''Get the North component (C{meter}).
283 '''
284 return self.xyzLocal.north
286 @Property_RO
287 def slantrange(self):
288 '''Get the I{slant Range}, distance (C{meter}).
289 '''
290 return self._slantrange
292 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
293 '''Return a string representation of this AER as azimuth
294 (bearing), elevation and slant range.
296 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
297 @kwarg fmt: Enclosing backets format (C{str}).
298 @kwarg sep: Optional separator between AERs (C{str}).
300 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
301 '''
302 t = (toDMS(self.azimuth, form=F_D, prec=prec, ddd=0),
303 toDMS(self.elevation, form=F_D, prec=prec, ddd=0),
304 fstr( self.slantrange, prec=3 if prec is None else prec))
305 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
307 def toStr(self, **prec_fmt_sep): # PYCHOK expected
308 '''Return a string representation of this AER.
310 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
311 number of (decimal) digits, unstripped
312 (C{int}), C{B{fmt}='[]'} the enclosing
313 backets format (C{str}) and separator
314 C{B{sep}=', '} to join (C{str}).
316 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
317 '''
318 _, t = _toStr2(self, **prec_fmt_sep)
319 return t
321 @Property_RO
322 def up(self):
323 '''Get the Up component (C{meter}).
324 '''
325 return self.xyzLocal.up
327 @Property_RO
328 def x(self):
329 '''Get the X component (C{meter}).
330 '''
331 return self.xyz4.x
333 @Property_RO
334 def xyz4(self):
335 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
336 '''
337 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
338 R = self._slantrange
339 r = cE * R # ground range
340 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
342 @Property_RO
343 def y(self):
344 '''Get the Y component (C{meter}).
345 '''
346 return self.xyz4.y
348 @Property_RO
349 def z(self):
350 '''Get the Z component (C{meter}).
351 '''
352 return self.xyz4.z
355class Aer4Tuple(_NamedTuple):
356 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
357 all in C{meter} except C{ltp}.
358 '''
359 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
360 _Units_ = ( Meter, Meter, Meter, _Pass)
362 def _toAer(self, Cls, Cls_kwds):
363 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
364 '''
365 if issubclassof(Cls, Aer):
366 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
367 else:
368 return _4Tuple2Cls(self, Cls, Cls_kwds)
370 @Property_RO
371 def groundrange(self):
372 '''Get the I{ground range}, distance (C{meter}).
373 '''
374 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
376 @Property_RO
377 def xyzLocal(self):
378 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
379 '''
380 return Aer(self).xyzLocal
383class Attitude4Tuple(_NamedTuple):
384 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
385 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
386 the attitude of a plane or camera.
387 '''
388 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
389 _Units_ = ( Meter, Bearing, Degrees, Degrees)
391 @Property_RO
392 def atyr(self):
393 '''Return this attitude (L{Attitude4Tuple}).
394 '''
395 return self
397 @Property_RO
398 def tyr3d(self):
399 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
400 '''
401 return _MODS.ltp.Attitude(self).tyr3d
404class Ned(_NamedAerNed):
405 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
407 @see: L{Enu} and L{Ltp}.
408 '''
409 _down = _0_0 # down, -XyzLocal.z (C{meter}).
410 _east = _0_0 # east, XyzLocal.y (C{meter}).
411# _ltp = None # local tangent plane (C{Ltp}), origin
412 _north = _0_0 # north, XyzLocal.x (C{meter})
413 _toStr = _ned_
415 def __init__(self, north_ned, east=0, down=0, ltp=None, name=NN):
416 '''New L{Ned} vector.
418 @arg north_ned: Scalar North component (C{meter}) or a previous
419 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
420 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
421 L{XyzLocal} or L{Xyz4Tuple}).
422 @kwarg east: Scalar East component (C{meter}), only used with
423 scalar B{C{north_ned}}.
424 @kwarg down: Scalar Down component, normal to I{inside} surface
425 of the ellipsoid or sphere (C{meter}), only used with
426 scalar B{C{north_ned}}.
427 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
428 L{LocalCartesian}).
429 @kwarg name: Optional name (C{str}).
431 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
433 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
434 '''
435 if _isMeter(north_ned):
436 self._north = Meter(north=north_ned or _0_0)
437 self._east = Meter(east=east or _0_0)
438 self._down = Meter(down=down or _0_0)
439 p, n = ltp, name
440 else: # PYCHOK no cover
441 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
442 ned = p.toNed() if p else north_ned
443 self._north, self._east, self._down = ned.north, ned.east, ned.down
444 p = _xattr(ned, ltp=ltp)
445 n = name or _xattr(ned, name=name)
447 if p:
448 self._ltp = _MODS.ltp._xLtp(p)
449 if n:
450 self.name = n
452 @Property_RO
453 def aer4(self):
454 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
455 '''
456 return _xyz2aer4(self)
458 @Property_RO
459 def azimuth(self):
460 '''Get the Azimuth, bearing from North (C{degrees360}).
461 '''
462 return self.aer4.azimuth
464 @deprecated_Property_RO
465 def bearing(self):
466 '''DEPRECATED, use C{azimuth}.'''
467 return self.azimuth
469 @Property_RO
470 def down(self):
471 '''Get the Down component (C{meter}).
472 '''
473 return self._down
475 @Property_RO
476 def east(self):
477 '''Get the East component (C{meter}).
478 '''
479 return self._east
481 @Property_RO
482 def elevation(self):
483 '''Get the Elevation, tilt above horizon (C{degrees90}).
484 '''
485 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
487 @Property_RO
488 def groundrange(self):
489 '''Get the I{ground range}, distance (C{meter}).
490 '''
491 return Meter(groundrange=hypot(self.north, self.east))
493 @deprecated_Property_RO
494 def length(self):
495 '''DEPRECATED, use C{slantrange}.'''
496 return self.slantrange
498 @deprecated_Property_RO
499 def ned(self):
500 '''DEPRECATED, use property C{ned4}.'''
501 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
503 @Property_RO
504 def ned4(self):
505 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
506 '''
507 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
509 @Property_RO
510 def north(self):
511 '''Get the North component (C{meter}).
512 '''
513 return self._north
515 @Property_RO
516 def slantrange(self):
517 '''Get the I{slant Range}, distance (C{meter}).
518 '''
519 return self.aer4.slantrange
521 @deprecated_method
522 def to3ned(self): # PYCHOK no cover
523 '''DEPRECATED, use property L{ned4}.'''
524 return self.ned # XXX deprecated too
526 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
527 '''Return a string representation of this NED.
529 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
530 @kwarg fmt: Enclosing backets format (C{str}).
531 @kwarg sep: Separator to join (C{str}).
533 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
534 '''
535 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
536 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
538 def toStr(self, **prec_fmt_sep): # PYCHOK expected
539 '''Return a string representation of this NED.
541 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
542 number of (decimal) digits, unstripped
543 (C{int}), C{B{fmt}='[]'} the enclosing
544 backets format (C{str}) and separator
545 C{B{sep}=', '} to join (C{str}).
547 @return: This NED as "[meter, meter, meter]" (C{str}).
548 '''
549 _, t = _toStr2(self, **prec_fmt_sep)
550 return t
552 @deprecated_method
553 def toVector3d(self):
554 '''DEPRECATED, use property L{xyz}.'''
555 return self.xyz
557 @Property_RO
558 def up(self):
559 '''Get the Up component (C{meter}).
560 '''
561 return Meter(up=-self._down) # negated
563 @Property_RO
564 def x(self):
565 '''Get the X component (C{meter}).
566 '''
567 return Meter(x=self._east) # 2nd arg, E
569 @Property_RO
570 def xyz4(self):
571 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
572 '''
573 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
575 @Property_RO
576 def y(self):
577 '''Get the Y component (C{meter}).
578 '''
579 return Meter(y=self._north) # 1st arg N
581 @Property_RO
582 def z(self):
583 '''Get the Z component (C{meter}).
584 '''
585 return Meter(z=-self._down) # negated
588class Ned4Tuple(_NamedTuple):
589 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
590 '''
591 _Names_ = (_north_, _east_, _down_, _ltp_)
592 _Units_ = ( Meter, Meter, Meter, _Pass)
594 def _toNed(self, Cls, Cls_kwds):
595 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
596 '''
597 if issubclassof(Cls, Ned):
598 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
599 else:
600 return _4Tuple2Cls(self, Cls, Cls_kwds)
602 @Property_RO
603 def xyzLocal(self):
604 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
605 '''
606 return Ned(self).xyzLocal
609class _Vector3d(Vector3d):
611 _toStr = _xyz_
613 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
614 '''Return a string representation of this ENU/NED/XYZ.
616 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
617 @kwarg fmt: Enclosing backets format (C{str}).
618 @kwarg sep: Separator to join (C{str}).
620 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
621 "[N:meter, E:meter, D:meter]",
622 "[U:meter, V:meter, W:meter]" respectively
623 "[X:meter, Y:meter, Z:meter]" (C{str}).
624 '''
625 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
626 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
628 def toStr(self, **prec_fmt_sep): # PYCHOK expected
629 '''Return a string representation of this XYZ.
631 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
632 number of (decimal) digits, unstripped
633 (C{int}), C{B{fmt}='[]'} the enclosing
634 backets format (C{str}) and separator
635 C{B{sep}=', '} to join (C{str}).
637 @return: This XYZ as "[meter, meter, meter]" (C{str}).
638 '''
639 _, t = _toStr2(self, **prec_fmt_sep)
640 return t
643class XyzLocal(_Vector3d):
644 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
645 also base class for local L{Enu}.
646 '''
647 _ltp = None # local tangent plane (C{Ltp}), origin
649 def __init__(self, x_xyz, y=0, z=0, ltp=None, name=NN):
650 '''New L{XyzLocal}.
652 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
653 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
654 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
655 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
656 @kwarg y: Scalar Y component (C{meter}), only used with scalar
657 B{C{x_xyz}}, C{positive north}.
658 @kwarg z: Scalar Z component, normal C{positive up} from the
659 surface of the ellipsoid or sphere (C{meter}), only
660 used with scalar B{C{x_xyz}}.
661 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
662 L{LocalCartesian}).
664 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
666 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
667 '''
668 if _isMeter(x_xyz):
669 self._x = Meter(x=x_xyz or _0_0)
670 self._y = Meter(y=y or _0_0)
671 self._z = Meter(z=z or _0_0)
672 p, n = ltp, name
673 else:
674 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
675 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z
676 p = _xattr(xyz, ltp=ltp)
677 n = name or _xattr(xyz, name=NN)
679 if p:
680 self._ltp = _MODS.ltp._xLtp(p)
681 if n:
682 self.name = n
684 def __str__(self):
685 return self.toStr()
687 @Property_RO
688 def aer4(self):
689 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
690 '''
691 return _xyz2aer4(self)
693 @Property_RO
694 def azimuth(self):
695 '''Get the Azimuth, bearing from North (C{degrees360}).
697 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
698 Transformations_between_ECEF_and_ENU_coordinates>}.
699 '''
700 return self.aer4.azimuth
702 def classof(self, *args, **kwds): # PYCHOK no cover
703 '''Create another instance of this very class.
705 @arg args: Optional, positional arguments.
706 @kwarg kwds: Optional, keyword arguments.
708 @return: New instance (C{self.__class__}).
709 '''
710 kwds = _xkwds(kwds, ltp=self.ltp, name=self.name)
711 return self.__class__(*args, **kwds)
713 @Property_RO
714 def down(self):
715 '''Get the Down component (C{meter}).
716 '''
717 return Meter(down=-self.z)
719 @property_RO
720 def ecef(self):
721 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
722 '''
723 return self.ltp.ecef
725 @Property_RO
726 def east(self):
727 '''Get the East component (C{meter}).
728 '''
729 return Meter(east=self.x)
731 @Property_RO
732 def elevation(self):
733 '''Get the Elevation, tilt above horizon (C{degrees90}).
735 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
736 Transformations_between_ECEF_and_ENU_coordinates>}.
737 '''
738 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
740 @Property_RO
741 def enu4(self):
742 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
743 '''
744 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
746 @Property_RO
747 def groundrange(self):
748 '''Get the I{ground range}, distance (C{meter}).
749 '''
750 return Meter(groundrange=hypot(self.x, self.y))
752 @Property_RO
753 def ltp(self):
754 '''Get the I{local tangent plane} (L{Ltp}).
755 '''
756 return self._ltp
758 @Property_RO
759 def ned4(self):
760 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
761 '''
762 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
764 @Property_RO
765 def north(self):
766 '''Get the North component (C{meter}).
767 '''
768 return Meter(north=self.y)
770 @Property_RO
771 def slantrange(self):
772 '''Get the I{slant Range}, distance (C{meter}).
773 '''
774 return self.aer4.slantrange
776 def toAer(self, Aer=None, **Aer_kwds):
777 '''Get the local I{Azimuth, Elevation, slantRange} components.
779 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
780 @kwarg Aer_kwds: Optional, additional B{C{Aer}} keyword
781 arguments, ignored if C{B{Aer} is None}.
783 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
784 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
786 @raise TypeError: Invalid B{C{Aer}}.
787 '''
788 return self.aer4._toAer(Aer, Aer_kwds)
790 def toCartesian(self, Cartesian=None, ltp=None, **Cartesian_kwds):
791 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
793 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
794 or C{None}.
795 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
796 overriding this C{ltp}.
797 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
798 arguments, ignored if C{B{Cartesian} is None}.
800 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an
801 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
802 with C{M=None}, always.
804 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or
805 B{C{Cartesian_kwds}} argument.
806 '''
807 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
808 if Cartesian is None:
809 r = ltp._local2ecef(self, nine=True)
810 else:
811 x, y, z = ltp._local2ecef(self)
812 kwds = _xkwds(Cartesian_kwds, datum=ltp.datum)
813 r = Cartesian(x, y, z, **kwds)
814 return _xnamed(r, self.name or ltp.name)
816 def toEnu(self, Enu=None, **Enu_kwds):
817 '''Get the local I{East, North, Up} (ENU) components.
819 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
820 @kwarg Enu_kwds: Optional, additional B{C{Enu}} keyword
821 arguments, ignored if C{B{Enu} is None}.
823 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
824 an L{Enu4Tuple}C{(east, north, up, ltp)}.
825 '''
826 return self.enu4._toEnu(Enu, Enu_kwds)
828 def toLatLon(self, LatLon=None, ltp=None, **LatLon_kwds):
829 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
831 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon})
832 or C{None}.
833 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
834 overriding this ENU/NED/AER/XYZ's LTP.
835 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
836 arguments, ignored if C{B{LatLon} is None}.
838 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an
839 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
840 datum)} with C{M=None}, always.
842 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or
843 B{C{LatLon_kwds}} argument.
844 '''
845 ltp = _MODS.ltp._xLtp(ltp, self.ltp)
846 r = ltp._local2ecef(self, nine=True)
847 if LatLon is None:
848 r = _xnamed(r, self.name or ltp.name)
849 else:
850 kwds = _xkwds(LatLon_kwds, height=r.height, datum=r.datum,
851 name=self.name or ltp.name)
852 r = LatLon(r.lat, r.lon, **kwds) # XXX ltp?
853 return r
855 def toLocal9Tuple(self, M=False, name=NN):
856 '''Get this local as a C{Local9Tuple}.
858 @kwarg M: Optionally include the rotation matrix (C{bool}).
859 @kwarg name: Optional name (C{str}).
861 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp,
862 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an
863 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}.
864 '''
865 ltp = self.ltp # see C{self.toLatLon}
866 t = ltp._local2ecef(self, nine=True, M=M)
867 return Local9Tuple(self.x, self.y, self.z, t.lat, t.lon, t.height,
868 ltp, t, t.M, name=name or t.name)
870 def toNed(self, Ned=None, **Ned_kwds):
871 '''Get the local I{North, East, Down} (Ned) components.
873 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
874 @kwarg Ned_kwds: Optional, additional B{C{Ned}} keyword
875 arguments, ignored if C{B{Ned} is None}.
877 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
878 an L{Ned4Tuple}C{(north, east, down, ltp)}.
879 '''
880 return self.ned4._toNed(Ned, Ned_kwds)
882 def toXyz(self, Xyz=None, **Xyz_kwds):
883 '''Get the local I{X, Y, Z} (XYZ) components.
885 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
886 L{Ned}, L{Aer}) or C{None}.
887 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
888 arguments, ignored if C{B{Xyz} is None}.
890 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
891 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
892 '''
893 return self.xyz4._toXyz(Xyz, Xyz_kwds)
895 @Property_RO
896 def up(self):
897 '''Get the Up component (C{meter}).
898 '''
899 return Meter(up=self.z)
901# @Property_RO
902# def x(self): # see: Vector3d.x
903# '''Get the X component (C{meter}).
904# '''
905# return self._x
907# @Property_RO
908# def xyz(self): # see: Vector3d.xyz
909# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
910# '''
911# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
913 @Property_RO
914 def xyz4(self):
915 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
916 '''
917 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
919 @Property_RO
920 def xyzLocal(self):
921 '''Get this L{XyzLocal}.
922 '''
923 return self
925# @Property_RO
926# def y(self): # see: Vector3d.y
927# '''Get the Y component (C{meter}).
928# '''
929# return self._y
931# @Property_RO
932# def z(self): # see: Vector3d.z
933# '''Get the Z component (C{meter}).
934# '''
935# return self._z
938class Xyz4Tuple(_NamedTuple):
939 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
940 '''
941 _Names_ = (_x_, _y_, _z_, _ltp_)
942 _Units_ = ( Meter, Meter, Meter, _Pass)
944 def _toXyz(self, Cls, Cls_kwds):
945 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
946 '''
947 if issubclassof(Cls, XyzLocal):
948 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
949 else:
950 return _4Tuple2Cls(self, Cls, Cls_kwds)
952 @Property_RO
953 def xyzLocal(self):
954 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
955 '''
956 return XyzLocal(*self, name=self.name)
959class Enu(XyzLocal):
960 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
962 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
963 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
964 '''
965 _toStr = _enu_
967 def __init__(self, east_enu, north=0, up=0, ltp=None, name=NN):
968 '''New L{Enu}.
970 @arg east_enu: Scalar East component (C{meter}) or a previous
971 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
972 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
973 L{XyzLocal} or L{Xyz4Tuple}).
974 @kwarg north: Scalar North component (C{meter}) only used with
975 scalar B{C{east_enu}}.
976 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
977 normal from the surface of the ellipsoid or sphere (C{meter}).
978 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
979 L{LocalCartesian}).
980 @kwarg name: Optional name (C{str}).
982 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
984 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
985 '''
986 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, name=name)
988 def toUvw(self, location, Uvw=None, **Uvw_kwds):
989 '''Get the I{u, v, w} (UVW) components at a location.
991 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
992 L{Vector3d}) location, like a Point-Of-View.
993 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
994 @kwarg Uvw_kwds: Optional, additional B{L{Uvw}} keyword
995 arguments, ignored if C{B{Uvw} is None}.
997 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
998 L{Uvw3Tuple}C{(u, v, w)}.
1000 @raise TypeError: InvalidB{C{location}}.
1002 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1003 '''
1004 try:
1005 sa, ca, sb, cb = sincos2_(*location.philam)
1006 except Exception as x:
1007 raise _TypeError(location=location, cause=x)
1008 e, n, u, _ = self.enu4
1010 t = ca * u - sa * n
1011 U = cb * t - sb * e
1012 V = cb * e + sb * t
1013 W = ca * n + sa * u
1014 return Uvw3Tuple(U, V, W, name=self.name) if Uvw is None else \
1015 Uvw( U, V, W, **_xkwds(Uvw_kwds, name=self.name))
1017 @Property_RO
1018 def xyzLocal(self):
1019 '''Get this ENU as an L{XyzLocal}.
1020 '''
1021 return XyzLocal(*self.xyz4, name=self.name)
1024class Enu4Tuple(_NamedTuple):
1025 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1026 '''
1027 _Names_ = (_east_, _north_, _up_, _ltp_)
1028 _Units_ = ( Meter, Meter, Meter, _Pass)
1030 def _toEnu(self, Cls, Cls_kwds):
1031 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1032 '''
1033 if issubclassof(Cls, XyzLocal):
1034 return Cls(*self, **_xkwds(Cls_kwds, name=self.name))
1035 else:
1036 return _4Tuple2Cls(self, Cls, Cls_kwds)
1038 @Property_RO
1039 def xyzLocal(self):
1040 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1041 '''
1042 return XyzLocal(*self, name=self.name)
1045class Local9Tuple(_NamedTuple):
1046 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1047 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1048 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1049 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1050 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1051 '''
1052 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1053 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1055 @Property_RO
1056 def azimuth(self):
1057 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1058 '''
1059 return self.xyzLocal.aer4.azimuth
1061 @Property_RO
1062 def down(self):
1063 '''Get the I{local} Down, C{-z} component (C{meter}).
1064 '''
1065 return -self.z
1067 @Property_RO
1068 def east(self):
1069 '''Get the I{local} East, C{x} component (C{meter}).
1070 '''
1071 return self.x
1073 @Property_RO
1074 def elevation(self):
1075 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1076 '''
1077 return self.xyzLocal.aer4.elevation
1079 @Property_RO
1080 def groundrange(self):
1081 '''Get the I{local} ground range, distance (C{meter}).
1082 '''
1083 return self.xyzLocal.aer4.groundrange
1085 @Property_RO
1086 def lam(self):
1087 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1088 '''
1089 return self.philam.lam
1091 @Property_RO
1092 def latlon(self):
1093 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1094 '''
1095 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1097 @Property_RO
1098 def latlonheight(self):
1099 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1100 '''
1101 return self.latlon.to3Tuple(self.height)
1103 @Property_RO
1104 def north(self):
1105 '''Get the I{local} North, C{y} component (C{meter}).
1106 '''
1107 return self.y
1109 @Property_RO
1110 def phi(self):
1111 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1112 '''
1113 return self.philam.phi
1115 @Property_RO
1116 def philam(self):
1117 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1118 '''
1119 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1121 @Property_RO
1122 def philamheight(self):
1123 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1124 '''
1125 return self.philam.to3Tuple(self.height)
1127 @Property_RO
1128 def slantrange(self):
1129 '''Get the I{local} slant Range, distance (C{meter}).
1130 '''
1131 return self.xyzLocal.aer4.slantrange
1133 def toAer(self, Aer=None, **Aer_kwds):
1134 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1136 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1137 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword
1138 arguments, ignored if B{C{Aer}} is C{None}.
1140 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
1141 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1142 '''
1143 return self.xyzLocal.toAer(Aer=Aer, **Aer_kwds)
1145 def toCartesian(self, Cartesian=None, **Cartesian_kwds):
1146 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1148 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1149 or C{None}.
1150 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
1151 arguments, ignored if C{B{Cartesian} is None}.
1153 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance
1154 or a L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
1156 @raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}
1157 argument.
1158 '''
1159 return self.ecef.toCartesian(Cartesian=Cartesian, **Cartesian_kwds) # PYCHOK _Tuple
1161 def toEnu(self, Enu=None, **Enu_kwds):
1162 '''Get the I{local} I{East, North, Up} (ENU) components.
1164 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1165 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1166 arguments, ignored if C{B{Enu} is None}.
1168 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
1169 an L{Enu4Tuple}C{(east, north, up, ltp)}.
1170 '''
1171 return self.xyzLocal.toEnu(Enu=Enu, **Enu_kwds)
1173 def toLatLon(self, LatLon=None, **LatLon_kwds):
1174 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1176 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1177 (C{LatLon}) or C{None}.
1178 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1179 arguments, ignored if C{B{LatLon} is None}.
1181 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
1182 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon,
1183 height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
1184 datum)} depending on whether C{datum} is un-/specified.
1186 @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}
1187 argument.
1188 '''
1189 return self.ecef.toLatLon(LatLon=LatLon, **LatLon_kwds) # PYCHOK _Tuple
1191 def toNed(self, Ned=None, **Ned_kwds):
1192 '''Get the I{local} I{North, East, Down} (NED) components.
1194 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1195 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword
1196 arguments, ignored if B{C{Ned}} is C{None}.
1198 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
1199 an L{Ned4Tuple}C{(north, east, down, ltp)}.
1200 '''
1201 return self.xyzLocal.toNed(Ned=Ned, **Ned_kwds)
1203 def toXyz(self, Xyz=None, **Xyz_kwds):
1204 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1206 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1207 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
1208 arguments, ignored if C{B{Xyz} is None}.
1210 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1211 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1212 '''
1213 return self.xyzLocal.toXyz(Xyz=Xyz, **Xyz_kwds)
1215 @Property_RO
1216 def up(self):
1217 '''Get the I{local} Up, C{z} component (C{meter}).
1218 '''
1219 return self.z
1221 @Property_RO
1222 def xyz(self):
1223 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1224 '''
1225 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1227 @Property_RO
1228 def xyzLocal(self):
1229 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1230 '''
1231 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1234_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1235_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1238class Uvw(_Vector3d):
1239 '''3-D C{u-v-w} (UVW) components.
1240 '''
1241 _toStr = _uvw_
1243 def __init__(self, u_uvw, v=0, w=0, name=NN):
1244 '''New L{Uvw}.
1246 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1247 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1248 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1249 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1250 @kwarg name: Optional name (C{str}).
1252 @raise TypeError: Invalid B{C{east_enu}}.
1254 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1255 '''
1256 Vector3d.__init__(self, u_uvw, v, w, name=name)
1258 def toEnu(self, location, Enu=Enu, **Enu_kwds):
1259 '''Get the I{East, North, Up} (ENU) components at a location.
1261 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1262 L{Vector3d}) location from where to cast the L{Los}.
1263 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1264 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword
1265 arguments, ignored if C{B{Enu} is None}.
1267 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1268 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1270 @raise TypeError: InvalidB{C{location}}.
1272 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1273 '''
1274 try:
1275 sa, ca, sb, cb = sincos2_(*location.philam)
1276 except Exception as x:
1277 raise _TypeError(location=location, cause=x)
1278 u, v, w = self.uvw
1280 t = cb * u + sb * v
1281 E = cb * v - sb * u
1282 N = ca * w - sa * t
1283 U = ca * t + sa * w
1284 return Enu4Tuple(E, N, U, name=self.name) if Enu is None else \
1285 Enu( E, N, U, **_xkwds(Enu_kwds, name=self.name))
1287 u = Vector3d.x
1289 @Property_RO
1290 def uvw(self):
1291 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1292 '''
1293 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1295 v = Vector3d.y
1296 w = Vector3d.z
1299class Uvw3Tuple(_NamedTuple):
1300 '''3-Tuple C{(u, v, w)}, in C{meter}.
1301 '''
1302 _Names_ = ('u', 'v', 'w')
1303 _Units_ = ( Meter, Meter, Meter)
1306class Los(Aer):
1307 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1308 '''
1310 def __init__(self, azimuth_aer, elevation=0, name=NN):
1311 '''New L{Los}.
1313 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1314 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1315 L{Enu4Tuple} or L{Los}).
1316 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1317 is 0, zenith +90, nadir -90), only used with scalar
1318 B{C{azimuth_aer}}.
1319 @kwarg name: Optional name (C{str}).
1321 @raise TypeError: Invalid B{C{azimuth_aer}}.
1323 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1324 '''
1325 t = Aer(azimuth_aer, elevation)
1326 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, name=name)
1328 def toUvw(self, location, Uvw=Uvw, **Uvw_kwds):
1329 '''Get this LOS' I{target} (UVW) components from a location.
1331 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1332 L{Vector3d}) location from where to cast the L{Los}.
1334 @see: Method L{Enu.toUvw} for further details.
1335 '''
1336 return self.toEnu().toUvw(location, Uvw=Uvw, **Uvw_kwds)
1338 def toEnu(self, Enu=Enu, **Enu_kwds):
1339 '''Get this LOS as I{East, North, Up} (ENU) components.
1341 @see: Method L{Aer.toEnu} for further details.
1342 '''
1343 return Aer.toEnu(self, Enu=Enu, **Enu_kwds)
1346class ChLV9Tuple(Local9Tuple):
1347 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1348 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1349 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1350 otherwise like L{Local9Tuple}.
1351 '''
1352 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1354 @Property_RO
1355 def E_LV95(self):
1356 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1357 '''
1358 return self.EN2_LV95.E_LV95
1360 @Property_RO
1361 def EN2_LV95(self):
1362 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1363 '''
1364 return ChLVEN2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1366 @Property_RO
1367 def h_LV03(self):
1368 '''Get the I{Swiss h_} height (C{meter}).
1369 '''
1370 return self.h_
1372 @Property_RO
1373 def h_LV95(self):
1374 '''Get the I{Swiss h_} height (C{meter}).
1375 '''
1376 return self.h_
1378 @property_RO
1379 def isChLV(self):
1380 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1381 '''
1382 return self.ltp.__class__ is _MODS.ltp.ChLV
1384 @property_RO
1385 def isChLVa(self):
1386 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1387 '''
1388 return self.ltp.__class__ is _MODS.ltp.ChLVa
1390 @property_RO
1391 def isChLVe(self):
1392 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1393 '''
1394 return self.ltp.__class__ is _MODS.ltp.ChLVe
1396 @Property_RO
1397 def N_LV95(self):
1398 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1399 '''
1400 return self.EN2_LV95.N_LV95
1402 @Property_RO
1403 def x(self):
1404 '''Get the I{local x, Swiss Y} easting (C{meter}).
1405 '''
1406 return self.Y
1408 @Property_RO
1409 def x_LV03(self):
1410 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1411 '''
1412 return self.yx2_LV03.x_LV03
1414 @Property_RO
1415 def y(self):
1416 '''Get the I{local y, Swiss X} northing (C{meter}).
1417 '''
1418 return self.X
1420 @Property_RO
1421 def y_LV03(self):
1422 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1423 '''
1424 return self.yx2_LV03.y_LV03
1426 @Property_RO
1427 def YX(self):
1428 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1429 '''
1430 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1432 @Property_RO
1433 def yx2_LV03(self):
1434 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1435 '''
1436 return ChLVyx2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1438 @Property_RO
1439 def z(self):
1440 '''Get the I{local z, Swiss h_} height (C{meter}).
1441 '''
1442 return self.h_
1445class ChLVYX2Tuple(_NamedTuple):
1446 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1447 in C{meter}.
1448 '''
1449 _Names_ = (_Y_, _X_)
1450 _Units_ = ( Meter, Meter)
1452 def false2(self, LV95=True):
1453 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1455 @see: Function L{ChLV.false2} for more information.
1456 '''
1457 return _MODS.ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1460class ChLVEN2Tuple(_NamedTuple):
1461 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1462 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1463 '''
1464 _Names_ = ('E_LV95', 'N_LV95')
1465 _Units_ = ChLVYX2Tuple._Units_
1467 def unfalse2(self):
1468 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1470 @see: Function L{ChLV.unfalse2} for more information.
1471 '''
1472 return _MODS.ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1475class ChLVyx2Tuple(_NamedTuple):
1476 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1477 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1478 '''
1479 _Names_ = ('y_LV03', 'x_LV03')
1480 _Units_ = ChLVYX2Tuple._Units_
1482 def unfalse2(self):
1483 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1485 @see: Function L{ChLV.unfalse2} for more information.
1486 '''
1487 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1490class Footprint5Tuple(_NamedTuple):
1491 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1492 with the C{center} and 4 corners of the I{local} projection of
1493 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1495 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1496 '''
1497 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1498 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1500 def toLatLon5(self, ltp=None, LatLon=None, **LatLon_kwds):
1501 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1502 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1504 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1505 footprint's C{center} or C{frustrum} C{ltp}.
1506 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1507 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1508 arguments, ignored if C{B{LatLon} is None}.
1510 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon,
1511 **B{LatLon_kwds})} instances or if C{B{LatLon} is None},
1512 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively
1513 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending
1514 on keyword argument C{datum} is un-/specified.
1516 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{LatLon_kwds}}.
1518 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1519 '''
1520 kwds = _xkwds(LatLon_kwds, ltp=_MODS.ltp._xLtp(ltp, self.center.ltp), # PYCHOK .center
1521 LatLon=LatLon, name=self.name,)
1522 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1524 def xyzLocal5(self, ltp=None):
1525 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1527 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1528 the {center} and corner C{ltp}s.
1530 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1532 @raise TypeError: Invalid B{C{ltp}}.
1533 '''
1534 if ltp is None:
1535 p = self
1536 else:
1537 p = _MODS.ltp._xLtp(ltp)
1538 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1539 return Footprint5Tuple(t.xyzLocal for t in p)
1542__all__ += _ALL_DOCS(_NamedAerNed)
1544# **) MIT License
1545#
1546# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1547#
1548# Permission is hereby granted, free of charge, to any person obtaining a
1549# copy of this software and associated documentation files (the "Software"),
1550# to deal in the Software without restriction, including without limitation
1551# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1552# and/or sell copies of the Software, and to permit persons to whom the
1553# Software is furnished to do so, subject to the following conditions:
1554#
1555# The above copyright notice and this permission notice shall be included
1556# in all copies or substantial portions of the Software.
1557#
1558# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1559# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1560# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1561# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1562# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1563# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1564# OTHER DEALINGS IN THE SOFTWARE.