Coverage for pygeodesy/ltpTuples.py: 95%
570 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-29 12:40 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-29 12:40 -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} and
7local 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'''
14from pygeodesy.basics import issubclassof, typename
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, _xkwds, \
18 _xkwds_item2
19from pygeodesy.fmath import fdot_, hypot, hypot_
20# rom pygeodesy.internals import typename # from .basics
21from pygeodesy.interns import NN, _4_, _azimuth_, _center_, _COMMASPACE_, \
22 _ecef_, _elevation_, _height_, _lat_, _lon_, \
23 _ltp_, _M_, _name_, _up_, _X_, _x_, _xyz_, \
24 _Y_, _y_, _z_
25from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
26# from pygeodesy.ltp Attitude, ChLV, ChLVa, ChLVe _Xltp # _MODS.into
27from pygeodesy.named import _name__, _name1__, _name2__, _NamedBase, \
28 _NamedTuple, _Pass, _xnamed
29from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
30from pygeodesy.props import deprecated_method, deprecated_Property_RO, \
31 Property_RO, property_RO
32from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs
33from pygeodesy.units import Azimuth, Bearing, Degrees, Degrees_, Height, \
34 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_
35from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians
36from pygeodesy.vector3d import Vector3d
38# from math import cos, radians # from .utily
40__all__ = _ALL_LAZY.ltpTuples
41__version__ = '25.05.01'
43_aer_ = 'aer'
44_alt_ = 'alt'
45_down_ = 'down'
46_east_ = 'east'
47_enu_ = 'enu'
48_h__ = 'h_'
49_ned_ = 'ned'
50_north_ = 'north'
51_local_ = 'local'
52_ltp = _MODS.into(ltp=__name__)
53_roll_ = 'roll'
54_slantrange_ = 'slantrange'
55_tilt_ = 'tilt'
56_uvw_ = 'uvw'
57_yaw_ = 'yaw'
60class _AbcBase(_NamedBase):
61 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}.
62 '''
63 _ltp = None # local tangent plane (C{Ltp}), origin
65 @Property_RO
66 def ltp(self):
67 '''Get the I{local tangent plane} (L{Ltp}).
68 '''
69 return self._ltp
71 def toAer(self, Aer=None, **name_Aer_kwds):
72 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
74 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
75 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
76 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
77 is None}.
79 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
80 slantrange, ltp)} if C{B{Aer} is None}.
82 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}.
83 '''
84 return self.xyz4._toXyz(Aer, name_Aer_kwds)
86 def toEnu(self, Enu=None, **name_Enu_kwds):
87 '''Get the I{local} I{East, North, Up} (ENU) components.
89 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
90 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
91 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
92 is None}.
94 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up,
95 ltp)} if C{B{Enu} is None}.
97 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}.
98 '''
99 return self.xyz4._toXyz(Enu, name_Enu_kwds)
101 def toNed(self, Ned=None, **name_Ned_kwds):
102 '''Get the I{local} I{North, East, Down} (NED) components.
104 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
105 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
106 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
107 is None}.
109 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
110 ltp)} if C{B{Ned} is None}.
112 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}.
113 '''
114 return self.xyz4._toXyz(Ned, name_Ned_kwds)
116 def toXyz(self, Xyz=None, **name_Xyz_kwds):
117 '''Get the local I{X, Y, Z} (XYZ) components.
119 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
120 or C{None}.
121 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
122 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
123 is None}.
125 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
126 C{B{Xyz} is None}.
128 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}.
129 '''
130 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
132 @Property_RO
133 def xyz(self):
134 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
135 '''
136 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
138 @property_RO
139 def xyz3(self):
140 '''Get the I{local} C{(X, Y, Z)} coordinates as C{3-tuple}.
141 '''
142 return tuple(self.xyz)
144 @property_RO
145 def xyz4(self): # PYCHOK no cover
146 '''I{Must be overloaded}.'''
147 self._notOverloaded()
149 @Property_RO
150 def xyzLocal(self):
151 '''Get this AER or NED as an L{XyzLocal}.
152 '''
153 return XyzLocal(self.xyz4, name=self.name)
156class _Abc4Tuple(_NamedTuple):
157 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple},
158 C{Ned4Tuple} and C{Xyz4Tuple}.
159 '''
160 def _2Cls(self, Abc, Cls, Cls_kwds):
161 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance.
162 '''
163 _isc = issubclassof
164 kwds = _name1__(Cls_kwds, _or_nameof=self)
165 if Cls is None:
166 n, _ = _name2__(Cls_kwds)
167 r = self.copy(name=n) if n else self
168 elif _isc(Cls, Abc):
169 r = Cls(*self, **kwds)
170 elif _isc(Cls, Aer):
171 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls))
172 elif _isc(Cls, Enu): # PYCHOK no cover
173 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls))
174 elif _isc(Cls, Ned):
175 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls))
176 elif _isc(Cls, XyzLocal): # PYCHOK no cover
177 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls))
178 elif Cls is Local9Tuple: # PYCHOK no cover
179 r = self.xyzLocal.toLocal9Tuple(**kwds)
180 else: # PYCHOK no cover
181 n = typename(Abc)[:3]
182 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal)
183 return r
185 @property_RO
186 def xyzLocal(self): # PYCHOK no cover
187 '''I{Must be overloaded}.'''
188 self._notOverloaded()
191class Aer(_AbcBase):
192 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}.
193 '''
194 _azimuth = _0_0 # bearing from North (C{degrees360})
195 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}).
196# _ltp = None # local tangent plane (C{Ltp}), origin
197 _slantrange = _0_0 # distance (C{Meter})
198 _toStr = _aer_
200 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name):
201 '''New L{Aer}.
203 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
204 or a previous I{local} instance (L{Aer}, L{Aer4Tuple},
205 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned},
206 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}).
207 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}}
208 (C{degrees}, horizon is 0, zenith +90 and nadir -90),
209 only used with scalar B{C{azimuth_aer}}.
210 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar
211 B{C{azimuth_aer}}.
212 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
213 L{LocalCartesian}).
214 @kwarg name: Optional C{B{name}=NN} (C{str}).
216 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}.
218 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or
219 or B{C{slantrange}}.
220 '''
221 if _isDegrees(azimuth_aer):
222 aer = None
223 t = (Azimuth(azimuth_aer),
224 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0),
225 Meter_(slantrange=slantrange), ltp)
226 else: # PYCHOK no cover
227 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer)
228 aer = p.toAer() if p else azimuth_aer
229 t = aer.aer4
230 self._azimuth, self._elevation, self._slantrange, _ = t
231 _init(self, aer, ltp, name)
233 @Property_RO
234 def aer4(self):
235 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
236 '''
237 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name)
239 @Property_RO
240 def azimuth(self):
241 '''Get the Azimuth, bearing from North (C{degrees360}).
242 '''
243 return self._azimuth
245 @Property_RO
246 def down(self):
247 '''Get the Down component (C{meter}).
248 '''
249 return self.xyzLocal.down
251 @Property_RO
252 def east(self):
253 '''Get the East component (C{meter}).
254 '''
255 return self.xyzLocal.east
257 @Property_RO
258 def elevation(self):
259 '''Get the Elevation, tilt above horizon (C{degrees90}).
260 '''
261 return self._elevation
263 @Property_RO
264 def groundrange(self):
265 '''Get the I{ground range}, distance (C{meter}).
266 '''
267 return _er2gr(self._elevation, self._slantrange)
269 @Property_RO
270 def north(self):
271 '''Get the North component (C{meter}).
272 '''
273 return self.xyzLocal.north
275 @Property_RO
276 def slantrange(self):
277 '''Get the I{slant Range}, distance (C{meter}).
278 '''
279 return self._slantrange
281 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
282 '''Return a string representation of this AER as azimuth
283 (bearing), elevation and slant range.
285 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
286 @kwarg fmt: Enclosing backets format (C{str}).
287 @kwarg sep: Optional separator between AERs (C{str}).
289 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}).
290 '''
291 m = _MODS.dms
292 t = (m.toDMS(self.azimuth, form=m.F_D, prec=prec, ddd=0),
293 m.toDMS(self.elevation, form=m.F_D, prec=prec, ddd=0),
294 fstr( self.slantrange, prec=3 if prec is None else prec))
295 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt)
297 def toStr(self, **prec_fmt_sep): # PYCHOK expected
298 '''Return a string representation of this AER.
300 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
301 number of (decimal) digits, unstripped
302 (C{int}), C{B{fmt}='[]'} the enclosing
303 backets format (C{str}) and separator
304 C{B{sep}=", "} to join (C{str}).
306 @return: This AER as "[degrees360, degrees90, meter]" (C{str}).
307 '''
308 _, t = _toStr2(self, **prec_fmt_sep)
309 return t
311 @Property_RO
312 def up(self):
313 '''Get the Up component (C{meter}).
314 '''
315 return self.xyzLocal.up
317 @Property_RO
318 def x(self):
319 '''Get the X component (C{meter}).
320 '''
321 return self.xyz4.x
323 @Property_RO
324 def xyz4(self):
325 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
326 '''
327 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation)
328 R = self._slantrange
329 r = cE * R # ground range
330 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name)
332 @Property_RO
333 def y(self):
334 '''Get the Y component (C{meter}).
335 '''
336 return self.xyz4.y
338 @Property_RO
339 def z(self):
340 '''Get the Z component (C{meter}).
341 '''
342 return self.xyz4.z
345class Aer4Tuple(_Abc4Tuple):
346 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)},
347 all in C{meter} except C{ltp}.
348 '''
349 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_)
350 _Units_ = ( Meter, Meter, Meter, _Pass)
352 def _toAer(self, Cls, Cls_kwds):
353 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
354 '''
355 return self._2Cls(Aer, Cls, Cls_kwds)
357 @Property_RO
358 def groundrange(self):
359 '''Get the I{ground range}, distance (C{meter}).
360 '''
361 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple
363 @Property_RO
364 def xyzLocal(self):
365 '''Get this L{Aer4Tuple} as an L{XyzLocal}.
366 '''
367 return Aer(self).xyzLocal
370class Attitude4Tuple(_NamedTuple):
371 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive)
372 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing
373 the attitude of a plane or camera.
374 '''
375 _Names_ = (_alt_, _tilt_, _yaw_, _roll_)
376 _Units_ = ( Meter, Degrees, Bearing, Degrees)
378 @Property_RO
379 def atyr(self):
380 '''Return this attitude (L{Attitude4Tuple}).
381 '''
382 return self
384 @Property_RO
385 def tyr3d(self):
386 '''Get this attitude's (3-D) directional vector (L{Vector3d}).
387 '''
388 return _ltp.Attitude(self).tyr3d
391class Ned(_AbcBase):
392 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}.
394 @see: L{Enu} and L{Ltp}.
395 '''
396 _down = _0_0 # down, -XyzLocal.z (C{meter}).
397 _east = _0_0 # east, XyzLocal.y (C{meter}).
398# _ltp = None # local tangent plane (C{Ltp}), origin
399 _north = _0_0 # north, XyzLocal.x (C{meter})
400 _toStr = _ned_
402 def __init__(self, north_ned, east=0, down=0, ltp=None, **name):
403 '''New L{Ned} vector.
405 @arg north_ned: Scalar North component (C{meter}) or a previous
406 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer},
407 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple},
408 L{XyzLocal} or L{Xyz4Tuple}).
409 @kwarg east: Scalar East component (C{meter}), only used with
410 scalar B{C{north_ned}}.
411 @kwarg down: Scalar Down component, normal to I{inside} surface
412 of the ellipsoid or sphere (C{meter}), only used with
413 scalar B{C{north_ned}}.
414 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
415 L{LocalCartesian}).
416 @kwarg name: Optional C{B{name}=NN} (C{str}).
418 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}.
420 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}.
421 '''
422 if _isMeter(north_ned):
423 ned = None
424 t = (Meter(north=north_ned or _0_0),
425 Meter(east=east or _0_0),
426 Meter(down=down or _0_0), ltp)
427 else: # PYCHOK no cover
428 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned)
429 ned = p.toNed() if p else north_ned
430 t = ned.ned4
431 self._north, self._east, self._down, _ = t
432 _init(self, ned, ltp, name)
434 @Property_RO
435 def aer4(self):
436 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
437 '''
438 return _xyz2aer4(self)
440 @Property_RO
441 def azimuth(self):
442 '''Get the Azimuth, bearing from North (C{degrees360}).
443 '''
444 return self.aer4.azimuth
446 @deprecated_Property_RO
447 def bearing(self):
448 '''DEPRECATED, use C{azimuth}.'''
449 return self.azimuth
451 @Property_RO
452 def down(self):
453 '''Get the Down component (C{meter}).
454 '''
455 return self._down
457 @Property_RO
458 def east(self):
459 '''Get the East component (C{meter}).
460 '''
461 return self._east
463 @Property_RO
464 def elevation(self):
465 '''Get the Elevation, tilt above horizon (C{degrees90}).
466 '''
467 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
469 @Property_RO
470 def groundrange(self):
471 '''Get the I{ground range}, distance (C{meter}).
472 '''
473 return Meter(groundrange=hypot(self.north, self.east))
475 @deprecated_Property_RO
476 def length(self):
477 '''DEPRECATED, use C{slantrange}.'''
478 return self.slantrange
480 @deprecated_Property_RO
481 def ned(self):
482 '''DEPRECATED, use property C{ned4}.'''
483 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name)
485 @Property_RO
486 def ned4(self):
487 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
488 '''
489 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
491 @Property_RO
492 def north(self):
493 '''Get the North component (C{meter}).
494 '''
495 return self._north
497 @Property_RO
498 def slantrange(self):
499 '''Get the I{slant Range}, distance (C{meter}).
500 '''
501 return self.aer4.slantrange
503 @deprecated_method
504 def to3ned(self): # PYCHOK no cover
505 '''DEPRECATED, use property L{ned4}.'''
506 return self.ned # XXX deprecated too
508 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
509 '''Return a string representation of this NED.
511 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
512 @kwarg fmt: Enclosing backets format (C{str}).
513 @kwarg sep: Separator to join (C{str}).
515 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}).
516 '''
517 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
518 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
520 def toStr(self, **prec_fmt_sep): # PYCHOK expected
521 '''Return a string representation of this NED.
523 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
524 number of (decimal) digits, unstripped
525 (C{int}), C{B{fmt}='[]'} the enclosing
526 backets format (C{str}) and separator
527 C{B{sep}=", "} to join (C{str}).
529 @return: This NED as "[meter, meter, meter]" (C{str}).
530 '''
531 _, t = _toStr2(self, **prec_fmt_sep)
532 return t
534 @deprecated_method
535 def toVector3d(self):
536 '''DEPRECATED, use property L{xyz}.'''
537 return self.xyz
539 @Property_RO
540 def up(self):
541 '''Get the Up component (C{meter}).
542 '''
543 return Meter(up=-self._down) # negated
545 @Property_RO
546 def x(self):
547 '''Get the X component (C{meter}).
548 '''
549 return Meter(x=self._east) # 2nd arg, E
551 @Property_RO
552 def xyz4(self):
553 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
554 '''
555 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
557 @Property_RO
558 def y(self):
559 '''Get the Y component (C{meter}).
560 '''
561 return Meter(y=self._north) # 1st arg N
563 @Property_RO
564 def z(self):
565 '''Get the Z component (C{meter}).
566 '''
567 return Meter(z=-self._down) # negated
570class Ned4Tuple(_Abc4Tuple):
571 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}.
572 '''
573 _Names_ = (_north_, _east_, _down_, _ltp_)
574 _Units_ = ( Meter, Meter, Meter, _Pass)
576 def _toNed(self, Cls, Cls_kwds):
577 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
578 '''
579 return self._2Cls(Ned, Cls, Cls_kwds)
581 @Property_RO
582 def xyzLocal(self):
583 '''Get this L{Ned4Tuple} as an L{XyzLocal}.
584 '''
585 return Ned(self).xyzLocal
588class _Vector3d(Vector3d):
590 _toStr = _xyz_
592 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected
593 '''Return a string representation of this ENU/NED/XYZ.
595 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
596 @kwarg fmt: Enclosing backets format (C{str}).
597 @kwarg sep: Separator to join (C{str}).
599 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]",
600 "[N:meter, E:meter, D:meter]",
601 "[U:meter, V:meter, W:meter]" respectively
602 "[X:meter, Y:meter, Z:meter]" (C{str}).
603 '''
604 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN)
605 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt)
607 def toStr(self, **prec_fmt_sep): # PYCHOK expected
608 '''Return a string representation of this XYZ.
610 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the
611 number of (decimal) digits, unstripped
612 (C{int}), C{B{fmt}='[]'} the enclosing
613 backets format (C{str}) and separator
614 C{B{sep}=", "} to join (C{str}).
616 @return: This XYZ as "[meter, meter, meter]" (C{str}).
617 '''
618 _, t = _toStr2(self, **prec_fmt_sep)
619 return t
622class XyzLocal(_Vector3d):
623 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP),
624 also base class for local L{Enu}.
625 '''
626 _ltp = None # local tangent plane (C{Ltp}), origin
628 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name):
629 '''New L{XyzLocal}.
631 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a
632 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple},
633 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple},
634 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}).
635 @kwarg y: Scalar Y component (C{meter}), only used with scalar
636 B{C{x_xyz}}, C{positive north}.
637 @kwarg z: Scalar Z component, normal C{positive up} from the
638 surface of the ellipsoid or sphere (C{meter}), only
639 used with scalar B{C{x_xyz}}.
640 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
641 L{LocalCartesian}).
642 @kwarg name: Optional C{B{name}=NN} (C{str}).
644 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}.
646 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}.
647 '''
648 if _isMeter(x_xyz):
649 xyz = None
650 t = (Meter(x=x_xyz or _0_0),
651 Meter(y=y or _0_0),
652 Meter(z=z or _0_0), ltp)
653 else:
654 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz
655 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp
656 self._x, self._y, self._z, _ = t
657 _init(self, xyz, ltp, name)
659 def __str__(self):
660 return self.toStr()
662 @Property_RO
663 def aer4(self):
664 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}).
665 '''
666 return _xyz2aer4(self)
668 @Property_RO
669 def azimuth(self):
670 '''Get the Azimuth, bearing from North (C{degrees360}).
672 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/
673 Transformations_between_ECEF_and_ENU_coordinates>}.
674 '''
675 return self.aer4.azimuth
677 def classof(self, *args, **kwds): # PYCHOK no cover
678 '''Create another instance of this very class.
680 @arg args: Optional, positional arguments.
681 @kwarg kwds: Optional, keyword arguments.
683 @return: New instance (C{self.__class__}).
684 '''
685 kwds = _name1__(kwds, _or_nameof=self)
686 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp))
688 @Property_RO
689 def down(self):
690 '''Get the Down component (C{meter}).
691 '''
692 return Meter(down=-self.z)
694 @property_RO
695 def ecef(self):
696 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
697 '''
698 return self.ltp.ecef
700 @Property_RO
701 def east(self):
702 '''Get the East component (C{meter}).
703 '''
704 return Meter(east=self.x)
706 @Property_RO
707 def elevation(self):
708 '''Get the Elevation, tilt above horizon (C{degrees90}).
710 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/
711 Transformations_between_ECEF_and_ENU_coordinates>}.
712 '''
713 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length))))
715 @Property_RO
716 def enu4(self):
717 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}).
718 '''
719 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name)
721 @Property_RO
722 def groundrange(self):
723 '''Get the I{ground range}, distance (C{meter}).
724 '''
725 return Meter(groundrange=hypot(self.x, self.y))
727 @Property_RO
728 def ltp(self):
729 '''Get the I{local tangent plane} (L{Ltp}).
730 '''
731 return self._ltp
733 def _ltp_kwds_name3(self, ltp, kwds):
734 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}.
735 '''
736 ltp = _ltp._xLtp(ltp, self.ltp)
737 kwds = _name1__(kwds, _or_nameof=self)
738 kwds = _name1__(kwds, _or_nameof=ltp)
739 return ltp, kwds, kwds.get(_name_, NN)
741 @Property_RO
742 def ned4(self):
743 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}).
744 '''
745 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name)
747 @Property_RO
748 def north(self):
749 '''Get the North component (C{meter}).
750 '''
751 return Meter(north=self.y)
753 @Property_RO
754 def slantrange(self):
755 '''Get the I{slant Range}, distance (C{meter}).
756 '''
757 return self.aer4.slantrange
759 def toAer(self, Aer=None, **name_Aer_kwds):
760 '''Get the local I{Azimuth, Elevation, slant Range} components.
762 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
763 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
764 additional B{C{Aer}} keyword arguments, ignored if C{B{Aer}
765 is None}.
767 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
768 slantrange, ltp)} if C{B{Aer} is None}.
770 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item.
771 '''
772 return self.aer4._toAer(Aer, name_Aer_kwds)
774 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds): # PYCHOK signature
775 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local.
777 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian})
778 or C{None}.
779 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
780 this C{ltp}.
781 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
782 additional B{C{Cartesian}} keyword arguments, ignored if
783 C{B{Cartesian} is None}.
785 @return: A B{C{Cartesian}} instance or if C{B{Cartesian} is None}, an
786 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with
787 C{M=None}, always.
789 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item.
790 '''
791 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds)
792 if Cartesian is None:
793 t = ltp._local2ecef(self, nine=True)
794 r = _xnamed(t, n) if n else t
795 else:
796 kwds = _xkwds(kwds, datum=ltp.datum, name=n)
797 xyz = ltp._local2ecef(self) # [:3]
798 r = Cartesian(*xyz, **kwds)
799 return r
801 def toEnu(self, Enu=None, **name_Enu_kwds):
802 '''Get the local I{East, North, Up} (ENU) components.
804 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
805 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
806 additional B{C{Enu}} keyword arguments, ignored if C{B{Enu}
807 is None}.
809 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up,
810 ltp)} if C{B{Enu} is None}.
812 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item.
813 '''
814 return self.enu4._toEnu(Enu, name_Enu_kwds)
816 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds):
817 '''Get the geodetic C{(lat, lon, height)} coordinates if this local.
819 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon}) or
820 C{None}.
821 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), overriding
822 this ENU/NED/AER/XYZ's LTP.
823 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
824 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
825 is None}.
827 @return: An B{C{LatLon}} instance or an L{Ecef9Tuple}C{(x, y, z, lat, lon,
828 height, C, M, datum)} if C{B{LatLon} is None}, with C{M=None}.
830 @raise TypeError: Invalid B{C{LatLon}}, B{C{ltp}} or B{C{name_LatLon_kwds}} item.
831 '''
832 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds)
833 t = ltp._local2ecef(self, nine=True)
834 if LatLon is None:
835 r = _xnamed(t, n) if n else t
836 else:
837 kwds = _xkwds(kwds, height=t.height, datum=t.datum)
838 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp?
839 return r
841 def toLocal9Tuple(self, M=False, **name):
842 '''Get this local as a C{Local9Tuple}.
844 @kwarg M: Optionally include the rotation matrix (C{bool}).
845 @kwarg name: Optional C{B{name}=NN} (C{str}).
847 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)}
848 with C{ltp} this C{Ltp}, C{ecef} an L{Ecef9Tuple} and C{M}
849 an L{EcefMatrix} or C{None}.
850 '''
851 ltp = self.ltp # see C{self.toLatLon}
852 t = ltp._local2ecef(self, nine=True, M=M)
853 return Local9Tuple(self.x, self.y, self.z,
854 t.lat, t.lon, t.height,
855 ltp, t, t.M, name=t._name__(name))
857 def toNed(self, Ned=None, **name_Ned_kwds):
858 '''Get the local I{North, East, Down} (Ned) components.
860 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
861 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
862 additional B{C{Ned}} keyword arguments, ignored if C{B{Ned}
863 is None}.
865 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
866 ltp)} if C{B{Ned} is None}.
868 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item.
869 '''
870 return self.ned4._toNed(Ned, name_Ned_kwds)
872 def toXyz(self, Xyz=None, **name_Xyz_kwds):
873 '''Get the local I{X, Y, Z} (XYZ) components.
875 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer})
876 or C{None}.
877 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
878 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
879 is None}.
881 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
882 C{B{Xyz} is None}.
884 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item.
885 '''
886 return self.xyz4._toXyz(Xyz, name_Xyz_kwds)
888 @Property_RO
889 def up(self):
890 '''Get the Up component (C{meter}).
891 '''
892 return Meter(up=self.z)
894# @Property_RO
895# def x(self): # see: Vector3d.x
896# '''Get the X component (C{meter}).
897# '''
898# return self._x
900# @Property_RO
901# def xyz(self): # see: Vector3d.xyz
902# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}).
903# '''
904# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz
906 @Property_RO
907 def xyz4(self):
908 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
909 '''
910 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name)
912 @Property_RO
913 def xyzLocal(self):
914 '''Get this L{XyzLocal}.
915 '''
916 return self
918# @Property_RO
919# def y(self): # see: Vector3d.y
920# '''Get the Y component (C{meter}).
921# '''
922# return self._y
924# @Property_RO
925# def z(self): # see: Vector3d.z
926# '''Get the Z component (C{meter}).
927# '''
928# return self._z
931class Xyz4Tuple(_Abc4Tuple):
932 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}.
933 '''
934 _Names_ = (_x_, _y_, _z_, _ltp_)
935 _Units_ = ( Meter, Meter, Meter, _Pass)
937 def _toXyz(self, Cls, Cls_kwds):
938 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance.
939 '''
940 return self._2Cls(XyzLocal, Cls, Cls_kwds)
942 @property_RO
943 def xyz4(self):
944 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}).
945 '''
946 return self
948 @Property_RO
949 def xyzLocal(self):
950 '''Get this L{Xyz4Tuple} as an L{XyzLocal}.
951 '''
952 return XyzLocal(*self, name=self.name)
955class Enu(XyzLocal):
956 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}.
958 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/
959 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.
960 '''
961 _toStr = _enu_
963 def __init__(self, east_enu, north=0, up=0, ltp=None, **name):
964 '''New L{Enu}.
966 @arg east_enu: Scalar East component (C{meter}) or a previous
967 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer},
968 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple},
969 L{XyzLocal} or L{Xyz4Tuple}).
970 @kwarg north: Scalar North component (C{meter}), iff B{C{east_enu}}
971 is C{meter}, ignored otherwise.
972 @kwarg up: Scalar Up component (C{meter}, normal from the surface
973 of the ellipsoid or sphere), iff B{C{east_enu}} is
974 C{meter}, ignored otherwise.
975 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp},
976 L{LocalCartesian}).
977 @kwarg name: Optional C{B{name}=NN} (C{str}).
979 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}.
981 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}.
982 '''
983 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name)
985 def toUvw(self, location, Uvw=None, **name_Uvw_kwds):
986 '''Get the I{u, v, w} (UVW) components at a location.
988 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
989 L{Vector3d}) location, like a Point-Of-View.
990 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}.
991 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
992 additional B{L{Uvw}} keyword arguments, ignored if C{B{Uvw}
993 is None}.
995 @return: A B{C{Uvw}} instance or a L{Uvw3Tuple}C{(u, v, w)} if C{B{Uvw}
996 is None}.
998 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}} item.
1000 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1001 '''
1002 try:
1003 sa, ca, sb, cb = sincos2_(*location.philam)
1004 except Exception as x:
1005 raise _TypeError(location=location, cause=x)
1006 e, n, u, _ = self.enu4
1008 t = fdot_(ca, u, -sa, n)
1009 U = fdot_(cb, t, -sb, e)
1010 V = fdot_(cb, e, sb, t)
1011 W = fdot_(ca, n, sa, u)
1013 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self)
1014 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \
1015 Uvw(U, V, W, name=n, **kwds)
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(_Abc4Tuple):
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 return self._2Cls(Enu, Cls, Cls_kwds)
1035 @Property_RO
1036 def xyzLocal(self):
1037 '''Get this L{Enu4Tuple} as an L{XyzLocal}.
1038 '''
1039 return XyzLocal(*self, name=self.name)
1042class Local9Tuple(_NamedTuple):
1043 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x},
1044 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local
1045 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric}
1046 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated}
1047 rotation matrix C{M} (L{EcefMatrix}) or C{None}.
1048 '''
1049 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_)
1050 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass)
1052 @Property_RO
1053 def azimuth(self):
1054 '''Get the I{local} Azimuth, bearing from North (C{degrees360}).
1055 '''
1056 return self.xyzLocal.aer4.azimuth
1058 @Property_RO
1059 def down(self):
1060 '''Get the I{local} Down, C{-z} component (C{meter}).
1061 '''
1062 return -self.z
1064 @Property_RO
1065 def east(self):
1066 '''Get the I{local} East, C{x} component (C{meter}).
1067 '''
1068 return self.x
1070 @Property_RO
1071 def elevation(self):
1072 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}).
1073 '''
1074 return self.xyzLocal.aer4.elevation
1076 @Property_RO
1077 def groundrange(self):
1078 '''Get the I{local} ground range, distance (C{meter}).
1079 '''
1080 return self.xyzLocal.aer4.groundrange
1082 @Property_RO
1083 def lam(self):
1084 '''Get the I{geodetic} longitude in C{radians} (C{float}).
1085 '''
1086 return self.philam.lam
1088 @Property_RO
1089 def latlon(self):
1090 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
1091 '''
1092 return LatLon2Tuple(self.lat, self.lon, name=self.name)
1094 @Property_RO
1095 def latlonheight(self):
1096 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
1097 '''
1098 return self.latlon.to3Tuple(self.height)
1100 @Property_RO
1101 def north(self):
1102 '''Get the I{local} North, C{y} component (C{meter}).
1103 '''
1104 return self.y
1106 @Property_RO
1107 def phi(self):
1108 '''Get the I{geodetic} latitude in C{radians} (C{float}).
1109 '''
1110 return self.philam.phi
1112 @Property_RO
1113 def philam(self):
1114 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
1115 '''
1116 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name)
1118 @Property_RO
1119 def philamheight(self):
1120 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1121 '''
1122 return self.philam.to3Tuple(self.height)
1124 @Property_RO
1125 def slantrange(self):
1126 '''Get the I{local} slant Range, distance (C{meter}).
1127 '''
1128 return self.xyzLocal.aer4.slantrange
1130 def toAer(self, Aer=None, **name_Aer_kwds):
1131 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components.
1133 @kwarg Aer: Class to return AER (L{Aer}) or C{None}.
1134 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1135 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer}
1136 is None}.
1138 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation,
1139 slantrange, ltp)} if C{B{Aer} is None}.
1141 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item.
1142 '''
1143 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds)
1145 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds):
1146 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF).
1148 @kwarg Cartesian: Optional I{geocentric} class to return C{(x, y, z)}
1149 (C{Cartesian}) or C{None}.
1150 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1151 additional B{C{Cartesian}} keyword arguments, ignored if
1152 C{B{Cartesian} is None}.
1154 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or a
1155 L{Vector4Tuple}C{(x, y, z, h)} if C{B{Cartesian} is None}.
1157 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item.
1158 '''
1159 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple
1161 def toEnu(self, Enu=None, **name_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 name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1166 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1167 is None}.
1169 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an
1170 L{Enu4Tuple}C{(east, north, up, ltp)}.
1172 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item.
1173 '''
1174 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds)
1176 def toLatLon(self, LatLon=None, **name_LatLon_kwds):
1177 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}.
1179 @kwarg LatLon: Optional I{geodetic} class to return C{(lat, lon, height)}
1180 (C{LatLon}) or C{None}.
1181 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1182 additional B{C{LatLon}} keyword arguments, ignored if
1183 C{B{LatLon} is None}.
1185 @return: An C{B{LatLon}(lat, lon, **B{LatLon_kwds})} instance or if
1186 C{B{LatLon} is None}, a L{LatLon4Tuple}C{(lat, lon, height,
1187 datum)} or L{LatLon3Tuple}C{(lat, lon, height)} if C{datum}
1188 is specified or not.
1190 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}.
1191 '''
1192 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple
1194 def toNed(self, Ned=None, **name_Ned_kwds):
1195 '''Get the I{local} I{North, East, Down} (NED) components.
1197 @kwarg Ned: Class to return NED (L{Ned}) or C{None}.
1198 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1199 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned}
1200 is None}.
1202 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down,
1203 ltp)} if C{B{Ned} is None}.
1205 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item.
1206 '''
1207 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds)
1209 def toXyz(self, Xyz=None, **name_Xyz_kwds):
1210 '''Get the I{local} I{X, Y, Z} (XYZ) components.
1212 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}.
1213 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1214 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz}
1215 is None}.
1217 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if
1218 C{B{Xyz} is None}.
1220 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item.
1221 '''
1222 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds)
1224 @Property_RO
1225 def up(self):
1226 '''Get the I{local} Up, C{z} component (C{meter}).
1227 '''
1228 return self.z
1230 @Property_RO
1231 def xyz(self):
1232 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}).
1233 '''
1234 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz
1236 @Property_RO
1237 def xyzLocal(self):
1238 '''Get this L{Local9Tuple} as an L{XyzLocal}.
1239 '''
1240 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp
1243_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp
1244_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp
1247class Uvw(_Vector3d):
1248 '''3-D C{u-v-w} (UVW) components.
1249 '''
1250 _toStr = _uvw_
1252 def __init__(self, u_uvw, v=0, w=0, **name):
1253 '''New L{Uvw}.
1255 @arg u_uvw: Scalar U component (C{meter}) or a previous instance (L{Uvw},
1256 L{Uvw3Tuple}, L{Vector3d}).
1257 @kwarg v: V component (C{meter}), iff B{C{u_uvw}} is C{meter}, ignored
1258 otherwise.
1259 @kwarg w: W component (C{meter}), iff B{C{u_uvw}} is C{meter}, ignored
1260 otherwise.
1261 @kwarg name: Optional C{B{name}=NN} (C{str}).
1263 @raise TypeError: Invalid B{C{east_enu}}.
1265 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}.
1266 '''
1267 Vector3d.__init__(self, u_uvw, v, w, **name)
1269 def toEnu(self, location, Enu=Enu, **name_Enu_kwds):
1270 '''Get the I{East, North, Up} (ENU) components at a location.
1272 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1273 L{Vector3d}) location from where to cast the L{Los}.
1274 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}.
1275 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1276 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu}
1277 is None}.
1279 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up, ltp)}
1280 with C{ltp=None} if C{B{Enu} is None}.
1282 @raise TypeError: Invalid B{C{location}}, B{C{Enu}} or B{C{name_Enu_kwds}} item.
1284 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}.
1285 '''
1286 try:
1287 sa, ca, sb, cb = sincos2_(*location.philam)
1288 except Exception as x:
1289 raise _TypeError(location=location, cause=x)
1290 u, v, w = self.uvw
1292 t = fdot_(cb, u, sb, v)
1293 E = fdot_(cb, v, -sb, u)
1294 N = fdot_(ca, w, -sa, t)
1295 U = fdot_(ca, t, sa, w)
1297 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self)
1298 return Enu4Tuple(E, N, U, name=n) if Enu is None else \
1299 Enu(E, N, U, name=n, **kwds)
1301 u = Vector3d.x
1303 @Property_RO
1304 def uvw(self):
1305 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}).
1306 '''
1307 return Uvw3Tuple(self.u, self.v, self.w, name=self.name)
1309 v = Vector3d.y
1310 w = Vector3d.z
1313class Uvw3Tuple(_NamedTuple):
1314 '''3-Tuple C{(u, v, w)}, in C{meter}.
1315 '''
1316 _Names_ = ('u', 'v', 'w')
1317 _Units_ = ( Meter, Meter, Meter)
1320class Los(Aer):
1321 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location.
1322 '''
1324 def __init__(self, azimuth_aer, elevation=0, **name):
1325 '''New L{Los}.
1327 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees})
1328 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu},
1329 L{Enu4Tuple} or L{Los}).
1330 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon
1331 is 0, zenith +90, nadir -90), if B{C{azimuth_aer}} is
1332 C{degrees}, ignored otherwise.
1333 @kwarg name: Optional C{B{name}=NN} (C{str}).
1335 @raise TypeError: Invalid B{C{azimuth_aer}}.
1337 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}.
1338 '''
1339 t = Aer(azimuth_aer, elevation)
1340 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name)
1342 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds):
1343 '''Get this LOS' I{target} (UVW) components from a location.
1345 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian},
1346 L{Vector3d}) location from where to cast this LOS.
1348 @see: Method L{Enu.toUvw} for further details.
1349 '''
1350 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds)
1352 def toEnu(self, Enu=Enu, **name_Enu_kwds):
1353 '''Get this LOS as I{East, North, Up} (ENU) components.
1355 @see: Method L{Aer.toEnu} for further details.
1356 '''
1357 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds)
1360class ChLV9Tuple(Local9Tuple):
1361 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss
1362 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV},
1363 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}),
1364 otherwise like L{Local9Tuple}.
1365 '''
1366 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:]
1368 @Property_RO
1369 def E_LV95(self):
1370 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}).
1371 '''
1372 return self.EN2_LV95.E_LV95
1374 @Property_RO
1375 def EN2_LV95(self):
1376 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}).
1377 '''
1378 return ChLVEN2Tuple(*_ChLV_false2(*self.YX, LV95=True), name=self.name)
1380 @Property_RO
1381 def h_LV03(self):
1382 '''Get the I{Swiss h_} height (C{meter}).
1383 '''
1384 return self.h_
1386 @Property_RO
1387 def h_LV95(self):
1388 '''Get the I{Swiss h_} height (C{meter}).
1389 '''
1390 return self.h_
1392 @property_RO
1393 def isChLV(self):
1394 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?.
1395 '''
1396 return self.ltp.__class__ is _ltp.ChLV
1398 @property_RO
1399 def isChLVa(self):
1400 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?.
1401 '''
1402 return self.ltp.__class__ is _ltp.ChLVa
1404 @property_RO
1405 def isChLVe(self):
1406 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?.
1407 '''
1408 return self.ltp.__class__ is _ltp.ChLVe
1410 @Property_RO
1411 def N_LV95(self):
1412 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}).
1413 '''
1414 return self.EN2_LV95.N_LV95
1416 @Property_RO
1417 def x(self):
1418 '''Get the I{local x, Swiss Y} easting (C{meter}).
1419 '''
1420 return self.Y
1422 @Property_RO
1423 def x_LV03(self):
1424 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}).
1425 '''
1426 return self.yx2_LV03.x_LV03
1428 @Property_RO
1429 def y(self):
1430 '''Get the I{local y, Swiss X} northing (C{meter}).
1431 '''
1432 return self.X
1434 @Property_RO
1435 def y_LV03(self):
1436 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}).
1437 '''
1438 return self.yx2_LV03.y_LV03
1440 @Property_RO
1441 def YX(self):
1442 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}).
1443 '''
1444 return ChLVYX2Tuple(self.Y, self.X, name=self.name)
1446 @Property_RO
1447 def yx2_LV03(self):
1448 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}).
1449 '''
1450 return ChLVyx2Tuple(*_ChLV_false2(*self.YX, LV95=False), name=self.name)
1452 @Property_RO
1453 def z(self):
1454 '''Get the I{local z, Swiss h_} height (C{meter}).
1455 '''
1456 return self.h_
1459class ChLVYX2Tuple(_NamedTuple):
1460 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting
1461 in C{meter}.
1462 '''
1463 _Names_ = (_Y_, _X_)
1464 _Units_ = ( Meter, Meter)
1466 def false2(self, LV95=True):
1467 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection.
1469 @see: Function L{ChLV.false2} for more information.
1470 '''
1471 return _ChLV_false2(*self, LV95=LV95, name=self.name)
1474class ChLVEN2Tuple(_NamedTuple):
1475 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and
1476 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}.
1477 '''
1478 _Names_ = ('E_LV95', 'N_LV95')
1479 _Units_ = ChLVYX2Tuple._Units_
1481 def unfalse2(self):
1482 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1484 @see: Function L{ChLV.unfalse2} for more information.
1485 '''
1486 return _ChLV_unfalse2(*self, LV95=True, name=self.name)
1489class ChLVyx2Tuple(_NamedTuple):
1490 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and
1491 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}.
1492 '''
1493 _Names_ = ('y_LV03', 'x_LV03')
1494 _Units_ = ChLVYX2Tuple._Units_
1496 def unfalse2(self):
1497 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}.
1499 @see: Function L{ChLV.unfalse2} for more information.
1500 '''
1501 return _ChLV_unfalse2(*self, LV95=False, name=self.name)
1504class Footprint5Tuple(_NamedTuple):
1505 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)}
1506 with the C{center} and 4 corners of the I{local} projection of
1507 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc.
1509 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}.
1510 '''
1511 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft')
1512 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)
1514 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds):
1515 '''Convert this footprint's C{center} and 4 corners to I{geodetic}
1516 C{LatLon(lat, lon, height)}s, C{LatLon3Tuple}s or C{LatLon4Tuple}s.
1518 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
1519 footprint's C{center} or C{frustrum} C{ltp}.
1520 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}.
1521 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
1522 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}
1523 is None}.
1525 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, **B{name_LatLon_kwds})}
1526 instances or if C{B{LatLon} is None}, 5 L{LatLon3Tuple}C{(lat, lon,
1527 height)}s respectively 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s
1528 depending on whether keyword argument C{datum} is un-/specified.
1530 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}} item.
1532 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}.
1533 '''
1534 ltp = _ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center
1535 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)
1536 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon)
1537 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5())
1539 def xyzLocal5(self, ltp=None):
1540 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s.
1542 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding
1543 the {center} and corner C{ltp}s.
1545 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances.
1547 @raise TypeError: Invalid B{C{ltp}}.
1548 '''
1549 if ltp is None:
1550 p = self
1551 else:
1552 p = _ltp._xLtp(ltp)
1553 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self)
1554 return Footprint5Tuple(t.xyzLocal for t in p)
1557def _ChLV_false2(Y, X, **LV95_name):
1558 '''(INTERNAL) Invoke static method C{ltp.ChLV.false2}.
1559 '''
1560 return _ltp.ChLV.false2(Y, X, **LV95_name)
1563def _ChLV_unfalse2(e, n, **LV95_name):
1564 '''(INTERNAL) Invoke static method C{ltp.ChLV.unfalse2}.
1565 '''
1566 return _ltp.ChLV.unfalse2(e, n, **LV95_name)
1569def _er2gr(e, r):
1570 '''(INTERNAL) Elevation and slant range to ground range.
1571 '''
1572 c = cos(radians(e))
1573 return Meter_(groundrange=r * c)
1576def _init(inst, abc, ltp, name):
1577 '''(INTERNAL) Complete C{__init__}.
1578 '''
1579 if abc is None:
1580 n = _name__(**name)
1581 else:
1582 n = abc._name__(name)
1583 ltp = _xattr(abc, ltp=ltp)
1584 if ltp:
1585 inst._ltp = _ltp._xLtp(ltp)
1586 if n:
1587 inst.name = n
1590def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_):
1591 '''(INTERNAL) Get attribute name and value strings, joined and bracketed.
1592 '''
1593 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz'
1594 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a)
1595 t = strs(t, prec=3 if prec is None else prec)
1596 if sep:
1597 t = sep.join(t)
1598 if fmt:
1599 t = fmt(t)
1600 return a, t
1603def _xyz2aer4(inst):
1604 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}.
1605 '''
1606 x, y, z, _ = inst.xyz4
1607 A = Azimuth(atan2b(x, y))
1608 E = Degrees(elevation=atan2d(z, hypot(x, y)))
1609 R = Meter(slantrange=hypot_(x, y, z))
1610 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name)
1613def _xyzLocal(*Types, **name_inst):
1614 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}.
1615 '''
1616 n, inst = _xkwds_item2(name_inst)
1617 if isinstance(inst, Types):
1618 return None
1619 try:
1620 return inst.xyzLocal
1621 except (AttributeError, TypeError):
1622 raise _TypeError(n, inst, txt_not_=_local_)
1625__all__ += _ALL_DOCS(_AbcBase)
1627# **) MIT License
1628#
1629# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1630#
1631# Permission is hereby granted, free of charge, to any person obtaining a
1632# copy of this software and associated documentation files (the "Software"),
1633# to deal in the Software without restriction, including without limitation
1634# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1635# and/or sell copies of the Software, and to permit persons to whom the
1636# Software is furnished to do so, subject to the following conditions:
1637#
1638# The above copyright notice and this permission notice shall be included
1639# in all copies or substantial portions of the Software.
1640#
1641# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1642# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1643# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1644# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1645# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1646# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1647# OTHER DEALINGS IN THE SOFTWARE.