Coverage for pygeodesy/ltpTuples.py: 95%
566 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -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
16# from pygeodesy.dms import F_D, toDMS # _MODS
17from pygeodesy.errors import _TypeError, _TypesError, _xattr, \
18 _xkwds, _xkwds_item2
19from pygeodesy.fmath import hypot, hypot_
20from pygeodesy.interns import NN, _4_, _azimuth_, _center_, _COMMASPACE_, \
21 _ecef_, _elevation_, _height_, _lat_, _lon_, \
22 _ltp_, _M_, _name_, _up_, _X_, _x_, _xyz_, \
23 _Y_, _y_, _z_
24from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
25from pygeodesy.named import _name__, _name1__, _name2__, _NamedBase, \
26 _NamedTuple, _Pass, _xnamed
27from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
28from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
29 Property_RO, property_RO
30from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
31from pygeodesy.units import Bearing, Degrees, Degrees_, Height, _isDegrees, \
32 _isMeter, Lat, Lon, Meter, Meter_, issubclassof
33from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians
34from pygeodesy.vector3d import Vector3d
36# from math import cos, radians # from .utily
38__all__ = _ALL_LAZY.ltpTuples
39__version__ = '24.06.08'
41_aer_ = 'aer'
42_alt_ = 'alt'
43_down_ = 'down'
44_east_ = 'east'
45_enu_ = 'enu'
46_h__ = 'h_'
47_ltp = _MODS.into(ltp=__name__)
48_ned_ = 'ned'
49_north_ = 'north'
50_local_ = 'local'
51_roll_ = 'roll'
52_slantrange_ = 'slantrange'
53_tilt_ = 'tilt'
54_uvw_ = 'uvw'
55_yaw_ = 'yaw'
58def _er2gr(e, r):
59 '''(INTERNAL) Elevation and slant range to ground range.
60 '''
61 c = cos(radians(e))
62 return Meter_(groundrange=r * c)
65def _init(inst, abc, ltp, name):
66 '''(INTERNAL) Complete C{__init__}.
67 '''
68 if abc is None:
69 n = _name__(**name)
70 else:
71 n = abc._name__(name)
72 ltp = _xattr(abc, ltp=ltp)
73 if ltp:
74 inst._ltp = _ltp._xLtp(ltp)
75 if n:
76 inst.name = n
79def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
80 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
81 '''
82 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
83 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
84 t = strs(t, prec=3 if prec is None else prec)
85 if sep:
86 t = sep.join(t)
87 if fmt:
88 t = fmt(t)
89 return a, t
92def _xyz2aer4(inst):
93 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
94 '''
95 x, y, z, _ = inst.xyz4
96 A = 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 _AbcBase(_NamedBase):
115 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
116 '''
117 _ltp = None # local tangent plane (C{Ltp}), origin
119 @Property_RO
120 def ltp(self):
121 '''Get the I{local tangent plane} (L{Ltp}).
122 '''
123 return self._ltp
125 def toAer(self, Aer=None, **name_Aer_kwds):
126 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
128 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
129 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and
130 optional, additional B{L{Aer}} keyword arguments,
131 ignored if B{C{Aer}} is C{None}.
133 @return: AER as an L{Aer} instance or if C{B{Aer} is None},
134 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
136 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
137 '''
138 return self.xyz4._toXyz(Aer, name_Aer_kwds)
140 def toEnu(self, Enu=None, **name_Enu_kwds):
141 '''Get the I{local} I{East, North, Up} (ENU) components.
143 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
144 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and
145 optional, additional B{L{Enu}} keyword arguments,
146 ignored if C{B{Enu} is None}.
148 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
149 an L{Enu4Tuple}C{(east, north, up, ltp)}.
151 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
152 '''
153 return self.xyz4._toXyz(Enu, name_Enu_kwds)
155 def toNed(self, Ned=None, **name_Ned_kwds):
156 '''Get the I{local} I{North, East, Down} (NED) components.
158 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
159 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and
160 optional, additional B{L{Ned}} keyword arguments,
161 ignored if B{C{Ned}} is C{None}.
163 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
164 an L{Ned4Tuple}C{(north, east, down, ltp)}.
166 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
167 '''
168 return self.xyz4._toXyz(Ned, name_Ned_kwds)
170 def toXyz(self, Xyz=None, **name_Xyz_kwds):
171 '''Get the local I{X, Y, Z} (XYZ) components.
173 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
174 L{Ned}, L{Aer}) or C{None}.
175 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and
176 optional, additional B{C{Xyz}} keyword arguments,
177 ignored if C{B{Xyz} is None}.
179 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
180 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
182 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
183 '''
184 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
186 @Property_RO
187 def xyz(self):
188 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
189 '''
190 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
192 @property_RO
193 def xyz4(self): # PYCHOK no cover
194 '''I{Must be overloaded}.'''
195 self._notOverloaded()
197 @Property_RO
198 def xyzLocal(self):
199 '''Get this AER or NED as an L{XyzLocal}.
200 '''
201 return XyzLocal(self.xyz4, name=self.name)
204class _Abc4Tuple(_NamedTuple):
205 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple},
206 C{Ned4Tuple} and C{Xyz4Tuple}.
207 '''
208 def _2Cls(self, Abc, Cls, Cls_kwds):
209 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
210 '''
211 kwds = _name1__(Cls_kwds, _or_nameof=self)
212 _is = issubclassof
213 if Cls is None:
214 n, _ = _name2__(Cls_kwds)
215 r = self.copy(name=n) if n else self
216 elif _is(Cls, Abc):
217 r = Cls(*self, **kwds)
218 elif _is(Cls, Aer):
219 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls))
220 elif _is(Cls, Enu): # PYCHOK no cover
221 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls))
222 elif _is(Cls, Ned):
223 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls))
224 elif _is(Cls, XyzLocal): # PYCHOK no cover
225 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls))
226 elif Cls is Local9Tuple: # PYCHOK no cover
227 r = self.xyzLocal.toLocal9Tuple(**kwds)
228 else: # PYCHOK no cover
229 n = Abc.__name__[:3]
230 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
231 return r
233 @property_RO
234 def xyzLocal(self): # PYCHOK no cover
235 '''I{Must be overloaded}.'''
236 self._notOverloaded()
239class Aer(_AbcBase):
240 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
241 '''
242 _azimuth = _0_0 # bearing from North (C{degrees360})
243 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
244# _ltp = None # local tangent plane (C{Ltp}), origin
245 _slantrange = _0_0 # distance (C{Meter})
246 _toStr = _aer_
248 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name):
249 '''New L{Aer}.
251 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
252 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
253 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
254 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
255 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
256 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
257 only used with scalar B{C{azimuth_aer}}.
258 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
259 B{C{azimuth_aer}}.
260 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
261 L{LocalCartesian}).
262 @kwarg name: Optional C{B{name}=NN} (C{str}).
264 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
266 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
267 or B{C{slantrange}}.
268 '''
269 if _isDegrees(azimuth_aer):
270 aer = None
271 t = (Bearing(azimuth=azimuth_aer),
272 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0),
273 Meter_(slantrange=slantrange), ltp)
274 else: # PYCHOK no cover
275 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
276 aer = p.toAer() if p else azimuth_aer
277 t = aer.aer4
278 self._azimuth, self._elevation, self._slantrange, _ = t
279 _init(self, aer, ltp, name)
281 @Property_RO
282 def aer4(self):
283 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
284 '''
285 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
287 @Property_RO
288 def azimuth(self):
289 '''Get the Azimuth, bearing from North (C{degrees360}).
290 '''
291 return self._azimuth
293 @Property_RO
294 def down(self):
295 '''Get the Down component (C{meter}).
296 '''
297 return self.xyzLocal.down
299 @Property_RO
300 def east(self):
301 '''Get the East component (C{meter}).
302 '''
303 return self.xyzLocal.east
305 @Property_RO
306 def elevation(self):
307 '''Get the Elevation, tilt above horizon (C{degrees90}).
308 '''
309 return self._elevation
311 @Property_RO
312 def groundrange(self):
313 '''Get the I{ground range}, distance (C{meter}).
314 '''
315 return _er2gr(self._elevation, self._slantrange)
317 @Property_RO
318 def north(self):
319 '''Get the North component (C{meter}).
320 '''
321 return self.xyzLocal.north
323 @Property_RO
324 def slantrange(self):
325 '''Get the I{slant Range}, distance (C{meter}).
326 '''
327 return self._slantrange
329 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
330 '''Return a string representation of this AER as azimuth
331 (bearing), elevation and slant range.
333 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
334 @kwarg fmt: Enclosing backets format (C{str}).
335 @kwarg sep: Optional separator between AERs (C{str}).
337 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
338 '''
339 m = _MODS.dms
340 t = (m.toDMS(self.azimuth, form=m.F_D, prec=prec, ddd=0),
341 m.toDMS(self.elevation, form=m.F_D, prec=prec, ddd=0),
342 fstr( self.slantrange, prec=3 if prec is None else prec))
343 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
345 def toStr(self, **prec_fmt_sep): # PYCHOK expected
346 '''Return a string representation of this AER.
348 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
349 number of (decimal) digits, unstripped
350 (C{int}), C{B{fmt}='[]'} the enclosing
351 backets format (C{str}) and separator
352 C{B{sep}=', '} to join (C{str}).
354 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
355 '''
356 _, t = _toStr2(self, **prec_fmt_sep)
357 return t
359 @Property_RO
360 def up(self):
361 '''Get the Up component (C{meter}).
362 '''
363 return self.xyzLocal.up
365 @Property_RO
366 def x(self):
367 '''Get the X component (C{meter}).
368 '''
369 return self.xyz4.x
371 @Property_RO
372 def xyz4(self):
373 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
374 '''
375 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
376 R = self._slantrange
377 r = cE * R # ground range
378 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
380 @Property_RO
381 def y(self):
382 '''Get the Y component (C{meter}).
383 '''
384 return self.xyz4.y
386 @Property_RO
387 def z(self):
388 '''Get the Z component (C{meter}).
389 '''
390 return self.xyz4.z
393class Aer4Tuple(_Abc4Tuple):
394 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
395 all in C{meter} except C{ltp}.
396 '''
397 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
398 _Units_ = ( Meter, Meter, Meter, _Pass)
400 def _toAer(self, Cls, Cls_kwds):
401 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
402 '''
403 return self._2Cls(Aer, Cls, Cls_kwds)
405 @Property_RO
406 def groundrange(self):
407 '''Get the I{ground range}, distance (C{meter}).
408 '''
409 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
411 @Property_RO
412 def xyzLocal(self):
413 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
414 '''
415 return Aer(self).xyzLocal
418class Attitude4Tuple(_NamedTuple):
419 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
420 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
421 the attitude of a plane or camera.
422 '''
423 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
424 _Units_ = ( Meter, Bearing, Degrees, Degrees)
426 @Property_RO
427 def atyr(self):
428 '''Return this attitude (L{Attitude4Tuple}).
429 '''
430 return self
432 @Property_RO
433 def tyr3d(self):
434 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
435 '''
436 return _ltp.Attitude(self).tyr3d
439class Ned(_AbcBase):
440 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
442 @see: L{Enu} and L{Ltp}.
443 '''
444 _down = _0_0 # down, -XyzLocal.z (C{meter}).
445 _east = _0_0 # east, XyzLocal.y (C{meter}).
446# _ltp = None # local tangent plane (C{Ltp}), origin
447 _north = _0_0 # north, XyzLocal.x (C{meter})
448 _toStr = _ned_
450 def __init__(self, north_ned, east=0, down=0, ltp=None, **name):
451 '''New L{Ned} vector.
453 @arg north_ned: Scalar North component (C{meter}) or a previous
454 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
455 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
456 L{XyzLocal} or L{Xyz4Tuple}).
457 @kwarg east: Scalar East component (C{meter}), only used with
458 scalar B{C{north_ned}}.
459 @kwarg down: Scalar Down component, normal to I{inside} surface
460 of the ellipsoid or sphere (C{meter}), only used with
461 scalar B{C{north_ned}}.
462 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
463 L{LocalCartesian}).
464 @kwarg name: Optional C{B{name}=NN} (C{str}).
466 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
468 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
469 '''
470 if _isMeter(north_ned):
471 ned = None
472 t = (Meter(north=north_ned or _0_0),
473 Meter(east=east or _0_0),
474 Meter(down=down or _0_0), ltp)
475 else: # PYCHOK no cover
476 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
477 ned = p.toNed() if p else north_ned
478 t = ned.ned4
479 self._north, self._east, self._down, _ = t
480 _init(self, ned, ltp, name)
482 @Property_RO
483 def aer4(self):
484 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
485 '''
486 return _xyz2aer4(self)
488 @Property_RO
489 def azimuth(self):
490 '''Get the Azimuth, bearing from North (C{degrees360}).
491 '''
492 return self.aer4.azimuth
494 @deprecated_Property_RO
495 def bearing(self):
496 '''DEPRECATED, use C{azimuth}.'''
497 return self.azimuth
499 @Property_RO
500 def down(self):
501 '''Get the Down component (C{meter}).
502 '''
503 return self._down
505 @Property_RO
506 def east(self):
507 '''Get the East component (C{meter}).
508 '''
509 return self._east
511 @Property_RO
512 def elevation(self):
513 '''Get the Elevation, tilt above horizon (C{degrees90}).
514 '''
515 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
517 @Property_RO
518 def groundrange(self):
519 '''Get the I{ground range}, distance (C{meter}).
520 '''
521 return Meter(groundrange=hypot(self.north, self.east))
523 @deprecated_Property_RO
524 def length(self):
525 '''DEPRECATED, use C{slantrange}.'''
526 return self.slantrange
528 @deprecated_Property_RO
529 def ned(self):
530 '''DEPRECATED, use property C{ned4}.'''
531 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
533 @Property_RO
534 def ned4(self):
535 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
536 '''
537 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
539 @Property_RO
540 def north(self):
541 '''Get the North component (C{meter}).
542 '''
543 return self._north
545 @Property_RO
546 def slantrange(self):
547 '''Get the I{slant Range}, distance (C{meter}).
548 '''
549 return self.aer4.slantrange
551 @deprecated_method
552 def to3ned(self): # PYCHOK no cover
553 '''DEPRECATED, use property L{ned4}.'''
554 return self.ned # XXX deprecated too
556 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
557 '''Return a string representation of this NED.
559 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
560 @kwarg fmt: Enclosing backets format (C{str}).
561 @kwarg sep: Separator to join (C{str}).
563 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
564 '''
565 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
566 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
568 def toStr(self, **prec_fmt_sep): # PYCHOK expected
569 '''Return a string representation of this NED.
571 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
572 number of (decimal) digits, unstripped
573 (C{int}), C{B{fmt}='[]'} the enclosing
574 backets format (C{str}) and separator
575 C{B{sep}=', '} to join (C{str}).
577 @return: This NED as "[meter, meter, meter]" (C{str}).
578 '''
579 _, t = _toStr2(self, **prec_fmt_sep)
580 return t
582 @deprecated_method
583 def toVector3d(self):
584 '''DEPRECATED, use property L{xyz}.'''
585 return self.xyz
587 @Property_RO
588 def up(self):
589 '''Get the Up component (C{meter}).
590 '''
591 return Meter(up=-self._down) # negated
593 @Property_RO
594 def x(self):
595 '''Get the X component (C{meter}).
596 '''
597 return Meter(x=self._east) # 2nd arg, E
599 @Property_RO
600 def xyz4(self):
601 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
602 '''
603 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
605 @Property_RO
606 def y(self):
607 '''Get the Y component (C{meter}).
608 '''
609 return Meter(y=self._north) # 1st arg N
611 @Property_RO
612 def z(self):
613 '''Get the Z component (C{meter}).
614 '''
615 return Meter(z=-self._down) # negated
618class Ned4Tuple(_Abc4Tuple):
619 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
620 '''
621 _Names_ = (_north_, _east_, _down_, _ltp_)
622 _Units_ = ( Meter, Meter, Meter, _Pass)
624 def _toNed(self, Cls, Cls_kwds):
625 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
626 '''
627 return self._2Cls(Ned, Cls, Cls_kwds)
629 @Property_RO
630 def xyzLocal(self):
631 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
632 '''
633 return Ned(self).xyzLocal
636class _Vector3d(Vector3d):
638 _toStr = _xyz_
640 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
641 '''Return a string representation of this ENU/NED/XYZ.
643 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
644 @kwarg fmt: Enclosing backets format (C{str}).
645 @kwarg sep: Separator to join (C{str}).
647 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
648 "[N:meter, E:meter, D:meter]",
649 "[U:meter, V:meter, W:meter]" respectively
650 "[X:meter, Y:meter, Z:meter]" (C{str}).
651 '''
652 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
653 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
655 def toStr(self, **prec_fmt_sep): # PYCHOK expected
656 '''Return a string representation of this XYZ.
658 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
659 number of (decimal) digits, unstripped
660 (C{int}), C{B{fmt}='[]'} the enclosing
661 backets format (C{str}) and separator
662 C{B{sep}=', '} to join (C{str}).
664 @return: This XYZ as "[meter, meter, meter]" (C{str}).
665 '''
666 _, t = _toStr2(self, **prec_fmt_sep)
667 return t
670class XyzLocal(_Vector3d):
671 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
672 also base class for local L{Enu}.
673 '''
674 _ltp = None # local tangent plane (C{Ltp}), origin
676 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name):
677 '''New L{XyzLocal}.
679 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
680 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
681 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
682 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
683 @kwarg y: Scalar Y component (C{meter}), only used with scalar
684 B{C{x_xyz}}, C{positive north}.
685 @kwarg z: Scalar Z component, normal C{positive up} from the
686 surface of the ellipsoid or sphere (C{meter}), only
687 used with scalar B{C{x_xyz}}.
688 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
689 L{LocalCartesian}).
690 @kwarg name: Optional C{B{name}=NN} (C{str}).
692 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
694 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
695 '''
696 if _isMeter(x_xyz):
697 xyz = None
698 t = (Meter(x=x_xyz or _0_0),
699 Meter(y=y or _0_0),
700 Meter(z=z or _0_0), ltp)
701 else:
702 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
703 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp
704 self._x, self._y, self._z, _ = t
705 _init(self, xyz, ltp, name)
707 def __str__(self):
708 return self.toStr()
710 @Property_RO
711 def aer4(self):
712 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
713 '''
714 return _xyz2aer4(self)
716 @Property_RO
717 def azimuth(self):
718 '''Get the Azimuth, bearing from North (C{degrees360}).
720 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
721 Transformations_between_ECEF_and_ENU_coordinates>}.
722 '''
723 return self.aer4.azimuth
725 def classof(self, *args, **kwds): # PYCHOK no cover
726 '''Create another instance of this very class.
728 @arg args: Optional, positional arguments.
729 @kwarg kwds: Optional, keyword arguments.
731 @return: New instance (C{self.__class__}).
732 '''
733 kwds = _name1__(kwds, _or_nameof=self)
734 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp))
736 @Property_RO
737 def down(self):
738 '''Get the Down component (C{meter}).
739 '''
740 return Meter(down=-self.z)
742 @property_RO
743 def ecef(self):
744 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
745 '''
746 return self.ltp.ecef
748 @Property_RO
749 def east(self):
750 '''Get the East component (C{meter}).
751 '''
752 return Meter(east=self.x)
754 @Property_RO
755 def elevation(self):
756 '''Get the Elevation, tilt above horizon (C{degrees90}).
758 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
759 Transformations_between_ECEF_and_ENU_coordinates>}.
760 '''
761 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
763 @Property_RO
764 def enu4(self):
765 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
766 '''
767 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
769 @Property_RO
770 def groundrange(self):
771 '''Get the I{ground range}, distance (C{meter}).
772 '''
773 return Meter(groundrange=hypot(self.x, self.y))
775 @Property_RO
776 def ltp(self):
777 '''Get the I{local tangent plane} (L{Ltp}).
778 '''
779 return self._ltp
781 def _ltp_kwds_name3(self, ltp, kwds):
782 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}.
783 '''
784 ltp = _ltp._xLtp(ltp, self.ltp)
785 kwds = _name1__(kwds, _or_nameof=self)
786 kwds = _name1__(kwds, _or_nameof=ltp)
787 return ltp, kwds, kwds.get(_name_, NN)
789 @Property_RO
790 def ned4(self):
791 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
792 '''
793 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
795 @Property_RO
796 def north(self):
797 '''Get the North component (C{meter}).
798 '''
799 return Meter(north=self.y)
801 @Property_RO
802 def slantrange(self):
803 '''Get the I{slant Range}, distance (C{meter}).
804 '''
805 return self.aer4.slantrange
807 def toAer(self, Aer=None, **name_Aer_kwds):
808 '''Get the local I{Azimuth, Elevation, slant Range} components.
810 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
811 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and
812 optional, additional B{C{Aer}} keyword arguments,
813 ignored if C{B{Aer} is None}.
815 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
816 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
818 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
819 '''
820 return self.aer4._toAer(Aer, name_Aer_kwds)
822 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds):
823 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
825 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
826 or C{None}.
827 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
828 overriding this C{ltp}.
829 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and
830 optional, additional B{C{Cartesian}} keyword arguments,
831 ignored if C{B{Cartesian} is None}.
833 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an
834 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
835 with C{M=None}, always.
837 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or
838 B{C{name_Cartesian_kwds}}.
839 '''
840 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds)
841 if Cartesian is None:
842 t = ltp._local2ecef(self, nine=True)
843 r = _xnamed(t, n) if n else t
844 else:
845 kwds = _xkwds(kwds, datum=ltp.datum)
846 xyz = ltp._local2ecef(self) # [:3]
847 r = Cartesian(*xyz, **kwds)
848 return r
850 def toEnu(self, Enu=None, **name_Enu_kwds):
851 '''Get the local I{East, North, Up} (ENU) components.
853 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
854 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and
855 optional, additional B{C{Enu}} keyword arguments,
856 ignored if C{B{Enu} is None}.
858 @return: ENU as an L{Enu} instance or if C{B{Enu} is None},
859 an L{Enu4Tuple}C{(east, north, up, ltp)}.
861 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
862 '''
863 return self.enu4._toEnu(Enu, name_Enu_kwds)
865 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds):
866 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
868 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon})
869 or C{None}.
870 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}),
871 overriding this ENU/NED/AER/XYZ's LTP.
872 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and
873 optional, additional B{C{LatLon}} keyword arguments,
874 ignored if C{B{LatLon} is None}.
876 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an
877 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
878 datum)} with C{M=None}, always.
880 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or
881 B{C{name_LatLon_kwds}}.
882 '''
883 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds)
884 t = ltp._local2ecef(self, nine=True)
885 if LatLon is None:
886 r = _xnamed(t, n) if n else t
887 else:
888 kwds = _xkwds(kwds, height=t.height, datum=t.datum)
889 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp?
890 return r
892 def toLocal9Tuple(self, M=False, **name):
893 '''Get this local as a C{Local9Tuple}.
895 @kwarg M: Optionally include the rotation matrix (C{bool}).
896 @kwarg name: Optional C{B{name}=NN} (C{str}).
898 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp,
899 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an
900 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}.
901 '''
902 ltp = self.ltp # see C{self.toLatLon}
903 t = ltp._local2ecef(self, nine=True, M=M)
904 return Local9Tuple(self.x, self.y, self.z,
905 t.lat, t.lon, t.height,
906 ltp, t, t.M, name=t._name__(name))
908 def toNed(self, Ned=None, **name_Ned_kwds):
909 '''Get the local I{North, East, Down} (Ned) components.
911 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
912 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and
913 optional, additional B{C{Ned}} keyword arguments,
914 ignored if C{B{Ned} is None}.
916 @return: NED as an L{Ned} instance or if C{B{Ned} is None},
917 an L{Ned4Tuple}C{(north, east, down, ltp)}.
919 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
920 '''
921 return self.ned4._toNed(Ned, name_Ned_kwds)
923 def toXyz(self, Xyz=None, **name_Xyz_kwds):
924 '''Get the local I{X, Y, Z} (XYZ) components.
926 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu},
927 L{Ned}, L{Aer}) or C{None}.
928 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and
929 optional, additional B{C{Xyz}} keyword arguments,
930 ignored if C{B{Xyz} is None}.
932 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
933 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
935 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_EXyz_kwds}}.
936 '''
937 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
939 @Property_RO
940 def up(self):
941 '''Get the Up component (C{meter}).
942 '''
943 return Meter(up=self.z)
945# @Property_RO
946# def x(self): # see: Vector3d.x
947# '''Get the X component (C{meter}).
948# '''
949# return self._x
951# @Property_RO
952# def xyz(self): # see: Vector3d.xyz
953# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
954# '''
955# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
957 @Property_RO
958 def xyz4(self):
959 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
960 '''
961 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
963 @Property_RO
964 def xyzLocal(self):
965 '''Get this L{XyzLocal}.
966 '''
967 return self
969# @Property_RO
970# def y(self): # see: Vector3d.y
971# '''Get the Y component (C{meter}).
972# '''
973# return self._y
975# @Property_RO
976# def z(self): # see: Vector3d.z
977# '''Get the Z component (C{meter}).
978# '''
979# return self._z
982class Xyz4Tuple(_Abc4Tuple):
983 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
984 '''
985 _Names_ = (_x_, _y_, _z_, _ltp_)
986 _Units_ = ( Meter, Meter, Meter, _Pass)
988 def _toXyz(self, Cls, Cls_kwds):
989 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
990 '''
991 return self._2Cls(XyzLocal, Cls, Cls_kwds)
993 @property_RO
994 def xyz4(self):
995 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
996 '''
997 return self
999 @Property_RO
1000 def xyzLocal(self):
1001 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
1002 '''
1003 return XyzLocal(*self, name=self.name)
1006class Enu(XyzLocal):
1007 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
1009 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
1010 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
1011 '''
1012 _toStr = _enu_
1014 def __init__(self, east_enu, north=0, up=0, ltp=None, **name):
1015 '''New L{Enu}.
1017 @arg east_enu: Scalar East component (C{meter}) or a previous
1018 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
1019 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
1020 L{XyzLocal} or L{Xyz4Tuple}).
1021 @kwarg north: Scalar North component (C{meter}) only used with
1022 scalar B{C{east_enu}}.
1023 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}},
1024 normal from the surface of the ellipsoid or sphere (C{meter}).
1025 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
1026 L{LocalCartesian}).
1027 @kwarg name: Optional C{B{name}=NN} (C{str}).
1029 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
1031 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
1032 '''
1033 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name)
1035 def toUvw(self, location, Uvw=None, **name_Uvw_kwds):
1036 '''Get the I{u, v, w} (UVW) components at a location.
1038 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1039 L{Vector3d}) location, like a Point-Of-View.
1040 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
1041 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1042 additional B{L{Uvw}} keyword arguments, ignored if
1043 C{B{Uvw} is None}.
1045 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a
1046 L{Uvw3Tuple}C{(u, v, w)}.
1048 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}}.
1050 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1051 '''
1052 try:
1053 sa, ca, sb, cb = sincos2_(*location.philam)
1054 except Exception as x:
1055 raise _TypeError(location=location, cause=x)
1056 e, n, u, _ = self.enu4
1058 t = ca * u - sa * n
1059 U = cb * t - sb * e
1060 V = cb * e + sb * t
1061 W = ca * n + sa * u
1063 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self)
1064 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \
1065 Uvw(U, V, W, name=n, **kwds)
1067 @Property_RO
1068 def xyzLocal(self):
1069 '''Get this ENU as an L{XyzLocal}.
1070 '''
1071 return XyzLocal(*self.xyz4, name=self.name)
1074class Enu4Tuple(_Abc4Tuple):
1075 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}.
1076 '''
1077 _Names_ = (_east_, _north_, _up_, _ltp_)
1078 _Units_ = ( Meter, Meter, Meter, _Pass)
1080 def _toEnu(self, Cls, Cls_kwds):
1081 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
1082 '''
1083 return self._2Cls(Enu, Cls, Cls_kwds)
1085 @Property_RO
1086 def xyzLocal(self):
1087 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1088 '''
1089 return XyzLocal(*self, name=self.name)
1092class Local9Tuple(_NamedTuple):
1093 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1094 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1095 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1096 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1097 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1098 '''
1099 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1100 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1102 @Property_RO
1103 def azimuth(self):
1104 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1105 '''
1106 return self.xyzLocal.aer4.azimuth
1108 @Property_RO
1109 def down(self):
1110 '''Get the I{local} Down, C{-z} component (C{meter}).
1111 '''
1112 return -self.z
1114 @Property_RO
1115 def east(self):
1116 '''Get the I{local} East, C{x} component (C{meter}).
1117 '''
1118 return self.x
1120 @Property_RO
1121 def elevation(self):
1122 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1123 '''
1124 return self.xyzLocal.aer4.elevation
1126 @Property_RO
1127 def groundrange(self):
1128 '''Get the I{local} ground range, distance (C{meter}).
1129 '''
1130 return self.xyzLocal.aer4.groundrange
1132 @Property_RO
1133 def lam(self):
1134 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1135 '''
1136 return self.philam.lam
1138 @Property_RO
1139 def latlon(self):
1140 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1141 '''
1142 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1144 @Property_RO
1145 def latlonheight(self):
1146 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1147 '''
1148 return self.latlon.to3Tuple(self.height)
1150 @Property_RO
1151 def north(self):
1152 '''Get the I{local} North, C{y} component (C{meter}).
1153 '''
1154 return self.y
1156 @Property_RO
1157 def phi(self):
1158 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1159 '''
1160 return self.philam.phi
1162 @Property_RO
1163 def philam(self):
1164 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1165 '''
1166 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1168 @Property_RO
1169 def philamheight(self):
1170 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1171 '''
1172 return self.philam.to3Tuple(self.height)
1174 @Property_RO
1175 def slantrange(self):
1176 '''Get the I{local} slant Range, distance (C{meter}).
1177 '''
1178 return self.xyzLocal.aer4.slantrange
1180 def toAer(self, Aer=None, **name_Aer_kwds):
1181 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1183 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1184 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1185 additional B{L{Aer}} keyword arguments, ignored if
1186 B{C{Aer}} is C{None}.
1188 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an
1189 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}.
1191 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
1192 '''
1193 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds)
1195 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds):
1196 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1198 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
1199 or C{None}.
1200 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1201 additional B{C{Cartesian}} keyword arguments, ignored if
1202 C{B{Cartesian} is None}.
1204 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or
1205 if C{B{Cartesian} is None}, a L{Vector4Tuple}C{(x, y, z, h)} .
1207 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}}.
1208 '''
1209 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple
1211 def toEnu(self, Enu=None, **name_Enu_kwds):
1212 '''Get the I{local} I{East, North, Up} (ENU) components.
1214 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1215 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1216 additional B{L{Enu}} keyword arguments, ignored if
1217 C{B{Enu} is None}.
1219 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1220 L{Enu4Tuple}C{(east, north, up, ltp)}.
1222 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
1223 '''
1224 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds)
1226 def toLatLon(self, LatLon=None, **name_LatLon_kwds):
1227 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1229 @kwarg LatLon: Optional class to return C{(lat, lon, height)}
1230 (C{LatLon}) or C{None}.
1231 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1232 additional B{C{LatLon}} keyword arguments, ignored if
1233 C{B{LatLon} is None}.
1235 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
1236 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon,
1237 height)} respectively L{LatLon4Tuple}C{(lat, lon, height,
1238 datum)} depending on whether C{datum} is un-/specified.
1240 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1241 '''
1242 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple
1244 def toNed(self, Ned=None, **name_Ned_kwds):
1245 '''Get the I{local} I{North, East, Down} (NED) components.
1247 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1248 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1249 additional B{L{Ned}} keyword arguments, ignored if
1250 B{C{Ned}} is C{None}.
1252 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an
1253 L{Ned4Tuple}C{(north, east, down, ltp)}.
1255 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
1256 '''
1257 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds)
1259 def toXyz(self, Xyz=None, **name_Xyz_kwds):
1260 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1262 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1263 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1264 additional B{C{Xyz}} keyword arguments, ignored if
1265 C{B{Xyz} is None}.
1267 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None},
1268 an L{Xyz4Tuple}C{(x, y, z, ltp)}.
1270 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
1271 '''
1272 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds)
1274 @Property_RO
1275 def up(self):
1276 '''Get the I{local} Up, C{z} component (C{meter}).
1277 '''
1278 return self.z
1280 @Property_RO
1281 def xyz(self):
1282 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1283 '''
1284 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1286 @Property_RO
1287 def xyzLocal(self):
1288 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1289 '''
1290 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1293_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1294_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1297class Uvw(_Vector3d):
1298 '''3-D C{u-v-w} (UVW) components.
1299 '''
1300 _toStr = _uvw_
1302 def __init__(self, u_uvw, v=0, w=0, **name):
1303 '''New L{Uvw}.
1305 @arg u_uvw: Scalar U component (C{meter}) or a previous instance
1306 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}).
1307 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}.
1308 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}.
1309 @kwarg name: Optional C{B{name}=NN} (C{str}).
1311 @raise TypeError: Invalid B{C{east_enu}}.
1313 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1314 '''
1315 Vector3d.__init__(self, u_uvw, v, w, **name)
1317 def toEnu(self, location, Enu=Enu, **name_Enu_kwds):
1318 '''Get the I{East, North, Up} (ENU) components at a location.
1320 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1321 L{Vector3d}) location from where to cast the L{Los}.
1322 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1323 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1324 additional B{L{Enu}} keyword arguments, ignored if
1325 C{B{Enu} is None}.
1327 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1328 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.
1330 @raise TypeError: Invalid B{C{location}} or B{C{name_Enu_kwds}}.
1332 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1333 '''
1334 try:
1335 sa, ca, sb, cb = sincos2_(*location.philam)
1336 except Exception as x:
1337 raise _TypeError(location=location, cause=x)
1338 u, v, w = self.uvw
1340 t = cb * u + sb * v
1341 E = cb * v - sb * u
1342 N = ca * w - sa * t
1343 U = ca * t + sa * w
1345 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self)
1346 return Enu4Tuple(E, N, U, name=n) if Enu is None else \
1347 Enu(E, N, U, name=n, **kwds)
1349 u = Vector3d.x
1351 @Property_RO
1352 def uvw(self):
1353 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1354 '''
1355 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1357 v = Vector3d.y
1358 w = Vector3d.z
1361class Uvw3Tuple(_NamedTuple):
1362 '''3-Tuple C{(u, v, w)}, in C{meter}.
1363 '''
1364 _Names_ = ('u', 'v', 'w')
1365 _Units_ = ( Meter, Meter, Meter)
1368class Los(Aer):
1369 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1370 '''
1372 def __init__(self, azimuth_aer, elevation=0, **name):
1373 '''New L{Los}.
1375 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1376 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1377 L{Enu4Tuple} or L{Los}).
1378 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1379 is 0, zenith +90, nadir -90), only used with scalar
1380 B{C{azimuth_aer}}.
1381 @kwarg name: Optional C{B{name}=NN} (C{str}).
1383 @raise TypeError: Invalid B{C{azimuth_aer}}.
1385 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1386 '''
1387 t = Aer(azimuth_aer, elevation)
1388 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name)
1390 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds):
1391 '''Get this LOS' I{target} (UVW) components from a location.
1393 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1394 L{Vector3d}) location from where to cast this LOS.
1396 @see: Method L{Enu.toUvw} for further details.
1397 '''
1398 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds)
1400 def toEnu(self, Enu=Enu, **name_Enu_kwds):
1401 '''Get this LOS as I{East, North, Up} (ENU) components.
1403 @see: Method L{Aer.toEnu} for further details.
1404 '''
1405 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds)
1408class ChLV9Tuple(Local9Tuple):
1409 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1410 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1411 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1412 otherwise like L{Local9Tuple}.
1413 '''
1414 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1416 @Property_RO
1417 def E_LV95(self):
1418 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1419 '''
1420 return self.EN2_LV95.E_LV95
1422 @Property_RO
1423 def EN2_LV95(self):
1424 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1425 '''
1426 return ChLVEN2Tuple(*_ltp.ChLV.false2(self.Y, self.X, True), name=self.name)
1428 @Property_RO
1429 def h_LV03(self):
1430 '''Get the I{Swiss h_} height (C{meter}).
1431 '''
1432 return self.h_
1434 @Property_RO
1435 def h_LV95(self):
1436 '''Get the I{Swiss h_} height (C{meter}).
1437 '''
1438 return self.h_
1440 @property_RO
1441 def isChLV(self):
1442 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1443 '''
1444 return self.ltp.__class__ is _ltp.ChLV
1446 @property_RO
1447 def isChLVa(self):
1448 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1449 '''
1450 return self.ltp.__class__ is _ltp.ChLVa
1452 @property_RO
1453 def isChLVe(self):
1454 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1455 '''
1456 return self.ltp.__class__ is _ltp.ChLVe
1458 @Property_RO
1459 def N_LV95(self):
1460 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1461 '''
1462 return self.EN2_LV95.N_LV95
1464 @Property_RO
1465 def x(self):
1466 '''Get the I{local x, Swiss Y} easting (C{meter}).
1467 '''
1468 return self.Y
1470 @Property_RO
1471 def x_LV03(self):
1472 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1473 '''
1474 return self.yx2_LV03.x_LV03
1476 @Property_RO
1477 def y(self):
1478 '''Get the I{local y, Swiss X} northing (C{meter}).
1479 '''
1480 return self.X
1482 @Property_RO
1483 def y_LV03(self):
1484 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1485 '''
1486 return self.yx2_LV03.y_LV03
1488 @Property_RO
1489 def YX(self):
1490 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1491 '''
1492 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1494 @Property_RO
1495 def yx2_LV03(self):
1496 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1497 '''
1498 return ChLVyx2Tuple(*_ltp.ChLV.false2(self.Y, self.X, False), name=self.name)
1500 @Property_RO
1501 def z(self):
1502 '''Get the I{local z, Swiss h_} height (C{meter}).
1503 '''
1504 return self.h_
1507class ChLVYX2Tuple(_NamedTuple):
1508 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1509 in C{meter}.
1510 '''
1511 _Names_ = (_Y_, _X_)
1512 _Units_ = ( Meter, Meter)
1514 def false2(self, LV95=True):
1515 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1517 @see: Function L{ChLV.false2} for more information.
1518 '''
1519 return _ltp.ChLV.false2(*self, LV95=LV95, name=self.name)
1522class ChLVEN2Tuple(_NamedTuple):
1523 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1524 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1525 '''
1526 _Names_ = ('E_LV95', 'N_LV95')
1527 _Units_ = ChLVYX2Tuple._Units_
1529 def unfalse2(self):
1530 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1532 @see: Function L{ChLV.unfalse2} for more information.
1533 '''
1534 return _ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)
1537class ChLVyx2Tuple(_NamedTuple):
1538 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1539 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1540 '''
1541 _Names_ = ('y_LV03', 'x_LV03')
1542 _Units_ = ChLVYX2Tuple._Units_
1544 def unfalse2(self):
1545 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1547 @see: Function L{ChLV.unfalse2} for more information.
1548 '''
1549 return _ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)
1552class Footprint5Tuple(_NamedTuple):
1553 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1554 with the C{center} and 4 corners of the I{local} projection of
1555 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1557 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1558 '''
1559 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1560 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1562 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds):
1563 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1564 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s.
1566 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1567 footprint's C{center} or C{frustrum} C{ltp}.
1568 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1569 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional,
1570 additional B{C{LatLon}} keyword arguments, ignored if
1571 C{B{LatLon} is None}.
1573 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon,
1574 **B{name_LatLon_kwds})} instances or if C{B{LatLon} is None},
1575 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively
1576 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending
1577 on whether keyword argument C{datum} is un-/specified.
1579 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1581 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1582 '''
1583 ltp = _ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center
1584 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)
1585 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon)
1586 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1588 def xyzLocal5(self, ltp=None):
1589 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1591 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1592 the {center} and corner C{ltp}s.
1594 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1596 @raise TypeError: Invalid B{C{ltp}}.
1597 '''
1598 if ltp is None:
1599 p = self
1600 else:
1601 p = _ltp._xLtp(ltp)
1602 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1603 return Footprint5Tuple(t.xyzLocal for t in p)
1606__all__ += _ALL_DOCS(_AbcBase)
1608# **) MIT License
1609#
1610# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1611#
1612# Permission is hereby granted, free of charge, to any person obtaining a
1613# copy of this software and associated documentation files (the "Software"),
1614# to deal in the Software without restriction, including without limitation
1615# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1616# and/or sell copies of the Software, and to permit persons to whom the
1617# Software is furnished to do so, subject to the following conditions:
1618#
1619# The above copyright notice and this permission notice shall be included
1620# in all copies or substantial portions of the Software.
1621#
1622# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1623# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1624# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1625# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1626# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1627# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1628# OTHER DEALINGS IN THE SOFTWARE.