Coverage for pygeodesy/ltpTuples.py: 94%

570 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-06-01 11:43 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Named, I{Local Tangent Plane} (LTP) tuples. 

5 

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}. 

10 

11@see: References in module L{ltp}. 

12''' 

13 

14# from pygeodesy.basics import issubclassof # from .units 

15from pygeodesy.constants import _0_0, _1_0, _90_0, _N_90_0 

16from pygeodesy.dms import F_D, toDMS 

17from pygeodesy.errors import _TypeError, _TypesError, _xattr, \ 

18 _xkwds, _xkwds_item2 

19from pygeodesy.fmath import hypot, hypot_ 

20from pygeodesy.interns import NN, _4_, _azimuth_, _center_, _COMMASPACE_, \ 

21 _ecef_, _elevation_, _height_, _lat_, _lon_, \ 

22 _ltp_, _M_, _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_ 

34from pygeodesy.vector3d import Vector3d 

35 

36from math import cos, radians 

37 

38__all__ = _ALL_LAZY.ltpTuples 

39__version__ = '24.05.31' 

40 

41_aer_ = 'aer' 

42_alt_ = 'alt' 

43_down_ = 'down' 

44_east_ = 'east' 

45_enu_ = 'enu' 

46_h__ = 'h_' 

47_ned_ = 'ned' 

48_north_ = 'north' 

49_local_ = 'local' 

50_roll_ = 'roll' 

51_slantrange_ = 'slantrange' 

52_tilt_ = 'tilt' 

53_uvw_ = 'uvw' 

54_yaw_ = 'yaw' 

55 

56 

57def _er2gr(e, r): 

58 '''(INTERNAL) Elevation and slant range to ground range. 

59 ''' 

60 c = cos(radians(e)) 

61 return Meter_(groundrange=r * c) 

62 

63 

64def _toStr2(inst, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_): 

65 '''(INTERNAL) Get attribute name and value strings, joined and bracketed. 

66 ''' 

67 a = inst._toStr # 'aer', 'enu', 'ned', 'xyz' 

68 t = getattr(inst, a + _4_, ())[:len(a)] or getattr(inst, a) 

69 t = strs(t, prec=3 if prec is None else prec) 

70 if sep: 

71 t = sep.join(t) 

72 if fmt: 

73 t = fmt(t) 

74 return a, t 

75 

76 

77def _4Tuple2Cls(inst, Cls, Cls_kwds): 

78 '''(INTERNAL) Convert 4-Tuple to C{Cls} instance. 

79 ''' 

80 if Cls is None: 

81 return inst 

82 elif issubclassof(Cls, Aer): 

83 return inst.xyzLocal.toAer(Aer=Cls, **Cls_kwds) 

84 elif issubclassof(Cls, Enu): # PYCHOK no cover 

85 return inst.xyzLocal.toEnu(Enu=Cls, **Cls_kwds) 

86 elif issubclassof(Cls, Ned): 

87 return inst.xyzLocal.toNed(Ned=Cls, **Cls_kwds) 

88 elif issubclassof(Cls, XyzLocal): # PYCHOK no cover 

89 return inst.xyzLocal.toXyz(Xyz=Cls, **Cls_kwds) 

90 elif Cls is Local9Tuple: # PYCHOK no cover 

91 return inst.xyzLocal.toLocal9Tuple(**Cls_kwds) 

92 n = inst.__class__.__name__[:3] # PYCHOK no cover 

93 raise _TypesError(n, Cls, Aer, Enu, Ned, XyzLocal) 

94 

95 

96def _xyz2aer4(inst): 

97 '''(INTERNAL) Convert C{(x, y, z}) to C{(A, E, R)}. 

98 ''' 

99 x, y, z, _ = inst.xyz4 

100 A = Bearing(azimuth=atan2b(x, y)) 

101 E = Degrees(elevation=atan2d(z, hypot(x, y))) 

102 R = Meter(slantrange=hypot_(x, y, z)) 

103 return Aer4Tuple(A, E, R, inst.ltp, name=inst.name) 

104 

105 

106def _xyzLocal(*Types, **name_inst): 

107 '''(INTERNAL) Get C{inst} or C{inst.xyzLocal}. 

108 ''' 

109 n, inst = _xkwds_item2(name_inst) 

110 if isinstance(inst, Types): 

111 return None 

112 try: 

113 return inst.xyzLocal 

114 except (AttributeError, TypeError): 

115 raise _TypeError(n, inst, txt_not_=_local_) 

116 

117 

118class _NamedAerNed(_NamedBase): 

119 '''(INTERNAL) Base class for classes C{Aer} and C{Ned}. 

120 ''' 

121 _ltp = None # local tangent plane (C{Ltp}), origin 

122 

123 @Property_RO 

124 def ltp(self): 

125 '''Get the I{local tangent plane} (L{Ltp}). 

126 ''' 

127 return self._ltp 

128 

129 def toAer(self, Aer=None, **name_Aer_kwds): 

130 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components. 

131 

132 @kwarg Aer: Class to return AER (L{Aer}) or C{None}. 

133 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and 

134 optional, additional B{L{Aer}} keyword arguments, 

135 ignored if B{C{Aer}} is C{None}. 

136 

137 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, 

138 an L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}. 

139 

140 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}. 

141 ''' 

142 return self.xyz4._toXyz(Aer, name_Aer_kwds) 

143 

144 def toEnu(self, Enu=None, **name_Enu_kwds): 

145 '''Get the I{local} I{East, North, Up} (ENU) components. 

146 

147 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}. 

148 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and 

149 optional, additional B{L{Enu}} keyword arguments, 

150 ignored if C{B{Enu} is None}. 

151 

152 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, 

153 an L{Enu4Tuple}C{(east, north, up, ltp)}. 

154 

155 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}. 

156 ''' 

157 return self.xyz4._toXyz(Enu, name_Enu_kwds) 

158 

159 def toNed(self, Ned=None, **name_Ned_kwds): 

160 '''Get the I{local} I{North, East, Down} (NED) components. 

161 

162 @kwarg Ned: Class to return NED (L{Ned}) or C{None}. 

163 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and 

164 optional, additional B{L{Ned}} keyword arguments, 

165 ignored if B{C{Ned}} is C{None}. 

166 

167 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, 

168 an L{Ned4Tuple}C{(north, east, down, ltp)}. 

169 

170 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}. 

171 ''' 

172 return self.xyz4._toXyz(Ned, name_Ned_kwds) 

173 

174 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

175 '''Get the local I{X, Y, Z} (XYZ) components. 

176 

177 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, 

178 L{Ned}, L{Aer}) or C{None}. 

179 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and 

180 optional, additional B{C{Xyz}} keyword arguments, 

181 ignored if C{B{Xyz} is None}. 

182 

183 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None}, 

184 an L{Xyz4Tuple}C{(x, y, z, ltp)}. 

185 

186 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}. 

187 ''' 

188 return self.xyz4._toXyz(Xyz, name_Xyz_kwds) 

189 

190 @Property_RO 

191 def xyz(self): 

192 '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}). 

193 ''' 

194 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz 

195 

196 @property_RO 

197 def xyz4(self): # PYCHOK no cover 

198 '''I{Must be overloaded}.''' 

199 self._notOverloaded() 

200 

201 @Property_RO 

202 def xyzLocal(self): 

203 '''Get this AER or NED as an L{XyzLocal}. 

204 ''' 

205 return XyzLocal(self.xyz4, name=self.name) 

206 

207 

208class Aer(_NamedAerNed): 

209 '''Local C{Azimuth-Elevation-Range} (AER) in a I{local tangent plane}. 

210 ''' 

211 _azimuth = _0_0 # bearing from North (C{degrees360}) 

212 _elevation = _0_0 # tilt, pitch from horizon (C{degrees}). 

213# _ltp = None # local tangent plane (C{Ltp}), origin 

214 _slantrange = _0_0 # distance (C{Meter}) 

215 _toStr = _aer_ 

216 

217 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name): 

218 '''New L{Aer}. 

219 

220 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees}) 

221 or a previous I{local} instance (L{Aer}, L{Aer4Tuple}, 

222 L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, L{Ned}, 

223 L{Ned4Tuple}, L{XyzLocal} or L{Xyz4Tuple}). 

224 @kwarg elevation: Scalar angle I{above} the horizon, I{above} B{C{ltp}} 

225 (C{degrees}, horizon is 0, zenith +90 and nadir -90), 

226 only used with scalar B{C{azimuth_aer}}. 

227 @kwarg slantrange: Scalar distance (C{meter}), only used with scalar 

228 B{C{azimuth_aer}}. 

229 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp}, 

230 L{LocalCartesian}). 

231 @kwarg name: Optional C{B{name}=NN} (C{str}). 

232 

233 @raise TypeError: Invalid B{C{azimuth_aer}} or B{C{ltp}}. 

234 

235 @raise UnitError: Invalid B{C{azimuth_aer}}, B{C{elevation}} or 

236 or B{C{slantrange}}. 

237 ''' 

238 if _isDegrees(azimuth_aer): 

239 self._azimuth = Bearing(azimuth=azimuth_aer) 

240 self._elevation = Degrees_(elevation=elevation, low=_N_90_0, high=_90_0) 

241 self._slantrange = Meter_(slantrange=slantrange) 

242 p, n = ltp, _name__(**name) 

243 else: # PYCHOK no cover 

244 p = _xyzLocal(Aer, Aer4Tuple, Ned, azimuth_aer=azimuth_aer) 

245 aer = p.toAer() if p else azimuth_aer 

246 self._azimuth, self._elevation, self._slantrange = \ 

247 aer.azimuth, aer.elevation, aer.slantrange 

248 p = _xattr(aer, ltp=ltp) 

249 n = aer._name__(name) 

250 

251 if p: 

252 self._ltp = _MODS.ltp._xLtp(p) 

253 if n: 

254 self.name = n 

255 

256 @Property_RO 

257 def aer4(self): 

258 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}). 

259 ''' 

260 return Aer4Tuple(self.azimuth, self.elevation, self.slantrange, self.ltp, name=self.name) 

261 

262 @Property_RO 

263 def azimuth(self): 

264 '''Get the Azimuth, bearing from North (C{degrees360}). 

265 ''' 

266 return self._azimuth 

267 

268 @Property_RO 

269 def down(self): 

270 '''Get the Down component (C{meter}). 

271 ''' 

272 return self.xyzLocal.down 

273 

274 @Property_RO 

275 def east(self): 

276 '''Get the East component (C{meter}). 

277 ''' 

278 return self.xyzLocal.east 

279 

280 @Property_RO 

281 def elevation(self): 

282 '''Get the Elevation, tilt above horizon (C{degrees90}). 

283 ''' 

284 return self._elevation 

285 

286 @Property_RO 

287 def groundrange(self): 

288 '''Get the I{ground range}, distance (C{meter}). 

289 ''' 

290 return _er2gr(self._elevation, self._slantrange) 

291 

292 @Property_RO 

293 def north(self): 

294 '''Get the North component (C{meter}). 

295 ''' 

296 return self.xyzLocal.north 

297 

298 @Property_RO 

299 def slantrange(self): 

300 '''Get the I{slant Range}, distance (C{meter}). 

301 ''' 

302 return self._slantrange 

303 

304 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected 

305 '''Return a string representation of this AER as azimuth 

306 (bearing), elevation and slant range. 

307 

308 @kwarg prec: Number of (decimal) digits, unstripped (C{int}). 

309 @kwarg fmt: Enclosing backets format (C{str}). 

310 @kwarg sep: Optional separator between AERs (C{str}). 

311 

312 @return: This AER as "[A:degrees360, E:degrees90, R:meter]" (C{str}). 

313 ''' 

314 t = (toDMS(self.azimuth, form=F_D, prec=prec, ddd=0), 

315 toDMS(self.elevation, form=F_D, prec=prec, ddd=0), 

316 fstr( self.slantrange, prec=3 if prec is None else prec)) 

317 return _xzipairs(self._toStr.upper(), t, sep=sep, fmt=fmt) 

318 

319 def toStr(self, **prec_fmt_sep): # PYCHOK expected 

320 '''Return a string representation of this AER. 

321 

322 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the 

323 number of (decimal) digits, unstripped 

324 (C{int}), C{B{fmt}='[]'} the enclosing 

325 backets format (C{str}) and separator 

326 C{B{sep}=', '} to join (C{str}). 

327 

328 @return: This AER as "[degrees360, degrees90, meter]" (C{str}). 

329 ''' 

330 _, t = _toStr2(self, **prec_fmt_sep) 

331 return t 

332 

333 @Property_RO 

334 def up(self): 

335 '''Get the Up component (C{meter}). 

336 ''' 

337 return self.xyzLocal.up 

338 

339 @Property_RO 

340 def x(self): 

341 '''Get the X component (C{meter}). 

342 ''' 

343 return self.xyz4.x 

344 

345 @Property_RO 

346 def xyz4(self): 

347 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}). 

348 ''' 

349 sA, cA, sE, cE = sincos2d_(self._azimuth, self._elevation) 

350 R = self._slantrange 

351 r = cE * R # ground range 

352 return Xyz4Tuple(sA * r, cA * r, sE * R, self.ltp, name=self.name) 

353 

354 @Property_RO 

355 def y(self): 

356 '''Get the Y component (C{meter}). 

357 ''' 

358 return self.xyz4.y 

359 

360 @Property_RO 

361 def z(self): 

362 '''Get the Z component (C{meter}). 

363 ''' 

364 return self.xyz4.z 

365 

366 

367class Aer4Tuple(_NamedTuple): 

368 '''4-Tuple C{(azimuth, elevation, slantrange, ltp)}, 

369 all in C{meter} except C{ltp}. 

370 ''' 

371 _Names_ = (_azimuth_, _elevation_, _slantrange_, _ltp_) 

372 _Units_ = ( Meter, Meter, Meter, _Pass) 

373 

374 def _toAer(self, Cls, Cls_kwds): 

375 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance. 

376 ''' 

377 if issubclassof(Cls, Aer): 

378 return Cls(*self, **_xkwds(Cls_kwds, name=self.name)) 

379 else: 

380 return _4Tuple2Cls(self, Cls, Cls_kwds) 

381 

382 @Property_RO 

383 def groundrange(self): 

384 '''Get the I{ground range}, distance (C{meter}). 

385 ''' 

386 return _er2gr(self.elevation, self.slantrange) # PYCHOK _Tuple 

387 

388 @Property_RO 

389 def xyzLocal(self): 

390 '''Get this L{Aer4Tuple} as an L{XyzLocal}. 

391 ''' 

392 return Aer(self).xyzLocal 

393 

394 

395class Attitude4Tuple(_NamedTuple): 

396 '''4-Tuple C{(alt, tilt, yaw, roll)} with C{altitude} in (positive) 

397 C{meter} and C{tilt}, C{yaw} and C{roll} in C{degrees} representing 

398 the attitude of a plane or camera. 

399 ''' 

400 _Names_ = (_alt_, _tilt_, _yaw_, _roll_) 

401 _Units_ = ( Meter, Bearing, Degrees, Degrees) 

402 

403 @Property_RO 

404 def atyr(self): 

405 '''Return this attitude (L{Attitude4Tuple}). 

406 ''' 

407 return self 

408 

409 @Property_RO 

410 def tyr3d(self): 

411 '''Get this attitude's (3-D) directional vector (L{Vector3d}). 

412 ''' 

413 return _MODS.ltp.Attitude(self).tyr3d 

414 

415 

416class Ned(_NamedAerNed): 

417 '''Local C{North-Eeast-Down} (NED) location in a I{local tangent plane}. 

418 

419 @see: L{Enu} and L{Ltp}. 

420 ''' 

421 _down = _0_0 # down, -XyzLocal.z (C{meter}). 

422 _east = _0_0 # east, XyzLocal.y (C{meter}). 

423# _ltp = None # local tangent plane (C{Ltp}), origin 

424 _north = _0_0 # north, XyzLocal.x (C{meter}) 

425 _toStr = _ned_ 

426 

427 def __init__(self, north_ned, east=0, down=0, ltp=None, **name): 

428 '''New L{Ned} vector. 

429 

430 @arg north_ned: Scalar North component (C{meter}) or a previous 

431 I{local} instance (L{Ned}, L{Ned4Tuple}, L{Aer}, 

432 L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, L{Local9Tuple}, 

433 L{XyzLocal} or L{Xyz4Tuple}). 

434 @kwarg east: Scalar East component (C{meter}), only used with 

435 scalar B{C{north_ned}}. 

436 @kwarg down: Scalar Down component, normal to I{inside} surface 

437 of the ellipsoid or sphere (C{meter}), only used with 

438 scalar B{C{north_ned}}. 

439 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp}, 

440 L{LocalCartesian}). 

441 @kwarg name: Optional C{B{name}=NN} (C{str}). 

442 

443 @raise TypeError: Invalid B{C{north_ned}} or B{C{ltp}}. 

444 

445 @raise UnitError: Invalid B{C{north_ned}}, B{C{east}} or B{C{down}}. 

446 ''' 

447 if _isMeter(north_ned): 

448 self._north = Meter(north=north_ned or _0_0) 

449 self._east = Meter(east=east or _0_0) 

450 self._down = Meter(down=down or _0_0) 

451 p, n = ltp, _name__(**name) 

452 else: # PYCHOK no cover 

453 p = _xyzLocal(Ned, Ned4Tuple, Aer, north_ned=north_ned) 

454 ned = p.toNed() if p else north_ned 

455 self._north, self._east, self._down = ned.north, ned.east, ned.down 

456 p = _xattr(ned, ltp=ltp) 

457 n = ned._name__(name) 

458 

459 if p: 

460 self._ltp = _MODS.ltp._xLtp(p) 

461 if n: 

462 self.name = n 

463 

464 @Property_RO 

465 def aer4(self): 

466 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}). 

467 ''' 

468 return _xyz2aer4(self) 

469 

470 @Property_RO 

471 def azimuth(self): 

472 '''Get the Azimuth, bearing from North (C{degrees360}). 

473 ''' 

474 return self.aer4.azimuth 

475 

476 @deprecated_Property_RO 

477 def bearing(self): 

478 '''DEPRECATED, use C{azimuth}.''' 

479 return self.azimuth 

480 

481 @Property_RO 

482 def down(self): 

483 '''Get the Down component (C{meter}). 

484 ''' 

485 return self._down 

486 

487 @Property_RO 

488 def east(self): 

489 '''Get the East component (C{meter}). 

490 ''' 

491 return self._east 

492 

493 @Property_RO 

494 def elevation(self): 

495 '''Get the Elevation, tilt above horizon (C{degrees90}). 

496 ''' 

497 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length)))) 

498 

499 @Property_RO 

500 def groundrange(self): 

501 '''Get the I{ground range}, distance (C{meter}). 

502 ''' 

503 return Meter(groundrange=hypot(self.north, self.east)) 

504 

505 @deprecated_Property_RO 

506 def length(self): 

507 '''DEPRECATED, use C{slantrange}.''' 

508 return self.slantrange 

509 

510 @deprecated_Property_RO 

511 def ned(self): 

512 '''DEPRECATED, use property C{ned4}.''' 

513 return _MODS.deprecated.classes.Ned3Tuple(self.north, self.east, self.down, name=self.name) 

514 

515 @Property_RO 

516 def ned4(self): 

517 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}). 

518 ''' 

519 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name) 

520 

521 @Property_RO 

522 def north(self): 

523 '''Get the North component (C{meter}). 

524 ''' 

525 return self._north 

526 

527 @Property_RO 

528 def slantrange(self): 

529 '''Get the I{slant Range}, distance (C{meter}). 

530 ''' 

531 return self.aer4.slantrange 

532 

533 @deprecated_method 

534 def to3ned(self): # PYCHOK no cover 

535 '''DEPRECATED, use property L{ned4}.''' 

536 return self.ned # XXX deprecated too 

537 

538 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected 

539 '''Return a string representation of this NED. 

540 

541 @kwarg prec: Number of (decimal) digits, unstripped (C{int}). 

542 @kwarg fmt: Enclosing backets format (C{str}). 

543 @kwarg sep: Separator to join (C{str}). 

544 

545 @return: This NED as "[N:meter, E:meter, D:meter]" (C{str}). 

546 ''' 

547 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN) 

548 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt) 

549 

550 def toStr(self, **prec_fmt_sep): # PYCHOK expected 

551 '''Return a string representation of this NED. 

552 

553 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the 

554 number of (decimal) digits, unstripped 

555 (C{int}), C{B{fmt}='[]'} the enclosing 

556 backets format (C{str}) and separator 

557 C{B{sep}=', '} to join (C{str}). 

558 

559 @return: This NED as "[meter, meter, meter]" (C{str}). 

560 ''' 

561 _, t = _toStr2(self, **prec_fmt_sep) 

562 return t 

563 

564 @deprecated_method 

565 def toVector3d(self): 

566 '''DEPRECATED, use property L{xyz}.''' 

567 return self.xyz 

568 

569 @Property_RO 

570 def up(self): 

571 '''Get the Up component (C{meter}). 

572 ''' 

573 return Meter(up=-self._down) # negated 

574 

575 @Property_RO 

576 def x(self): 

577 '''Get the X component (C{meter}). 

578 ''' 

579 return Meter(x=self._east) # 2nd arg, E 

580 

581 @Property_RO 

582 def xyz4(self): 

583 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}). 

584 ''' 

585 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name) 

586 

587 @Property_RO 

588 def y(self): 

589 '''Get the Y component (C{meter}). 

590 ''' 

591 return Meter(y=self._north) # 1st arg N 

592 

593 @Property_RO 

594 def z(self): 

595 '''Get the Z component (C{meter}). 

596 ''' 

597 return Meter(z=-self._down) # negated 

598 

599 

600class Ned4Tuple(_NamedTuple): 

601 '''4-Tuple C{(north, east, down, ltp)}, all in C{meter} except C{ltp}. 

602 ''' 

603 _Names_ = (_north_, _east_, _down_, _ltp_) 

604 _Units_ = ( Meter, Meter, Meter, _Pass) 

605 

606 def _toNed(self, Cls, Cls_kwds): 

607 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance. 

608 ''' 

609 if issubclassof(Cls, Ned): 

610 return Cls(*self, **_xkwds(Cls_kwds, name=self.name)) 

611 else: 

612 return _4Tuple2Cls(self, Cls, Cls_kwds) 

613 

614 @Property_RO 

615 def xyzLocal(self): 

616 '''Get this L{Ned4Tuple} as an L{XyzLocal}. 

617 ''' 

618 return Ned(self).xyzLocal 

619 

620 

621class _Vector3d(Vector3d): 

622 

623 _toStr = _xyz_ 

624 

625 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused): # PYCHOK expected 

626 '''Return a string representation of this ENU/NED/XYZ. 

627 

628 @kwarg prec: Number of (decimal) digits, unstripped (C{int}). 

629 @kwarg fmt: Enclosing backets format (C{str}). 

630 @kwarg sep: Separator to join (C{str}). 

631 

632 @return: This XYZ/ENU as "[E:meter, N:meter, U:meter]", 

633 "[N:meter, E:meter, D:meter]", 

634 "[U:meter, V:meter, W:meter]" respectively 

635 "[X:meter, Y:meter, Z:meter]" (C{str}). 

636 ''' 

637 a, t = _toStr2(self, prec=prec, fmt=NN, sep=NN) 

638 return _xzipairs(a.upper(), t, sep=sep, fmt=fmt) 

639 

640 def toStr(self, **prec_fmt_sep): # PYCHOK expected 

641 '''Return a string representation of this XYZ. 

642 

643 @kwarg prec_fmt_sep: Keyword arguments C{B{prec}=3} for the 

644 number of (decimal) digits, unstripped 

645 (C{int}), C{B{fmt}='[]'} the enclosing 

646 backets format (C{str}) and separator 

647 C{B{sep}=', '} to join (C{str}). 

648 

649 @return: This XYZ as "[meter, meter, meter]" (C{str}). 

650 ''' 

651 _, t = _toStr2(self, **prec_fmt_sep) 

652 return t 

653 

654 

655class XyzLocal(_Vector3d): 

656 '''Local C{(x, y, z)} in a I{local tangent plane} (LTP), 

657 also base class for local L{Enu}. 

658 ''' 

659 _ltp = None # local tangent plane (C{Ltp}), origin 

660 

661 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name): 

662 '''New L{XyzLocal}. 

663 

664 @arg x_xyz: Scalar X component (C{meter}), C{positive east} or a 

665 previous I{local} instance (L{XyzLocal}, L{Xyz4Tuple}, 

666 L{Aer}, L{Aer4Tuple}, L{Enu}, L{Enu4Tuple}, 

667 L{Local9Tuple}, L{Ned} or L{Ned4Tuple}). 

668 @kwarg y: Scalar Y component (C{meter}), only used with scalar 

669 B{C{x_xyz}}, C{positive north}. 

670 @kwarg z: Scalar Z component, normal C{positive up} from the 

671 surface of the ellipsoid or sphere (C{meter}), only 

672 used with scalar B{C{x_xyz}}. 

673 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp}, 

674 L{LocalCartesian}). 

675 @kwarg name: Optional C{B{name}=NN} (C{str}). 

676 

677 @raise TypeError: Invalid B{C{x_xyz}} or B{C{ltp}}. 

678 

679 @raise UnitError: Invalid scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}. 

680 ''' 

681 if _isMeter(x_xyz): 

682 self._x = Meter(x=x_xyz or _0_0) 

683 self._y = Meter(y=y or _0_0) 

684 self._z = Meter(z=z or _0_0) 

685 p, n = ltp, _name__(**name) 

686 else: 

687 xyz = _xyzLocal(XyzLocal, Xyz4Tuple, Local9Tuple, x_xyz=x_xyz) or x_xyz 

688 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z 

689 p = _xattr(xyz, ltp=ltp) 

690 n = xyz._name__(name) 

691 

692 if p: 

693 self._ltp = _MODS.ltp._xLtp(p) 

694 if n: 

695 self.name = n 

696 

697 def __str__(self): 

698 return self.toStr() 

699 

700 @Property_RO 

701 def aer4(self): 

702 '''Get the C{(azimuth, elevation, slantrange, ltp)} components (L{Aer4Tuple}). 

703 ''' 

704 return _xyz2aer4(self) 

705 

706 @Property_RO 

707 def azimuth(self): 

708 '''Get the Azimuth, bearing from North (C{degrees360}). 

709 

710 @see: U{Azimuth<https://GSSC.ESA.int/navipedia/index.php/ 

711 Transformations_between_ECEF_and_ENU_coordinates>}. 

712 ''' 

713 return self.aer4.azimuth 

714 

715 def classof(self, *args, **kwds): # PYCHOK no cover 

716 '''Create another instance of this very class. 

717 

718 @arg args: Optional, positional arguments. 

719 @kwarg kwds: Optional, keyword arguments. 

720 

721 @return: New instance (C{self.__class__}). 

722 ''' 

723 kwds = _name1__(kwds, _or_nameof=self) 

724 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp)) 

725 

726 @Property_RO 

727 def down(self): 

728 '''Get the Down component (C{meter}). 

729 ''' 

730 return Meter(down=-self.z) 

731 

732 @property_RO 

733 def ecef(self): 

734 '''Get this LTP's ECEF converter (C{Ecef...} I{instance}). 

735 ''' 

736 return self.ltp.ecef 

737 

738 @Property_RO 

739 def east(self): 

740 '''Get the East component (C{meter}). 

741 ''' 

742 return Meter(east=self.x) 

743 

744 @Property_RO 

745 def elevation(self): 

746 '''Get the Elevation, tilt above horizon (C{degrees90}). 

747 

748 @see: U{Elevation<https://GSSC.ESA.int/navipedia/index.php/ 

749 Transformations_between_ECEF_and_ENU_coordinates>}. 

750 ''' 

751 return self.aer4.elevation # neg(degrees90(asin1(self.down / self.length)))) 

752 

753 @Property_RO 

754 def enu4(self): 

755 '''Get the C{(east, north, up, ltp)} components (L{Enu4Tuple}). 

756 ''' 

757 return Enu4Tuple(self.east, self.north, self.up, self.ltp, name=self.name) 

758 

759 @Property_RO 

760 def groundrange(self): 

761 '''Get the I{ground range}, distance (C{meter}). 

762 ''' 

763 return Meter(groundrange=hypot(self.x, self.y)) 

764 

765 @Property_RO 

766 def ltp(self): 

767 '''Get the I{local tangent plane} (L{Ltp}). 

768 ''' 

769 return self._ltp 

770 

771 def _ltp_kwds_name3(self, ltp, kwds): 

772 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}. 

773 ''' 

774 ltp = _MODS.ltp._xLtp(ltp, self.ltp) 

775 kwds = _name1__(kwds, _or_nameof=self) 

776 kwds = _name1__(kwds, _or_nameof=ltp) 

777 return ltp, kwds, kwds.get(_name_, NN) 

778 

779 @Property_RO 

780 def ned4(self): 

781 '''Get the C{(north, east, down, ltp)} components (L{Ned4Tuple}). 

782 ''' 

783 return Ned4Tuple(self.north, self.east, self.down, self.ltp, name=self.name) 

784 

785 @Property_RO 

786 def north(self): 

787 '''Get the North component (C{meter}). 

788 ''' 

789 return Meter(north=self.y) 

790 

791 @Property_RO 

792 def slantrange(self): 

793 '''Get the I{slant Range}, distance (C{meter}). 

794 ''' 

795 return self.aer4.slantrange 

796 

797 def toAer(self, Aer=None, **name_Aer_kwds): 

798 '''Get the local I{Azimuth, Elevation, slantRange} components. 

799 

800 @kwarg Aer: Class to return AER (L{Aer}) or C{None}. 

801 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and 

802 optional, additional B{C{Aer}} keyword arguments, 

803 ignored if C{B{Aer} is None}. 

804 

805 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an 

806 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}. 

807 

808 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}. 

809 ''' 

810 return self.aer4._toAer(Aer, name_Aer_kwds) 

811 

812 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds): 

813 '''Get the geocentric C{(x, y, z)} (ECEF) coordinates of this local. 

814 

815 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian}) 

816 or C{None}. 

817 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), 

818 overriding this C{ltp}. 

819 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and 

820 optional, additional B{C{Cartesian}} keyword arguments, 

821 ignored if C{B{Cartesian} is None}. 

822 

823 @return: A B{C{Cartesian}} instance of if C{B{Cartesian} is None}, an 

824 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} 

825 with C{M=None}, always. 

826 

827 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or 

828 B{C{name_Cartesian_kwds}}. 

829 ''' 

830 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds) 

831 if Cartesian is None: 

832 t = ltp._local2ecef(self, nine=True) 

833 r = _xnamed(t, n) if n else t 

834 else: 

835 kwds = _xkwds(kwds, datum=ltp.datum) 

836 x, y, z = ltp._local2ecef(self) 

837 r = Cartesian(x, y, z, **kwds) 

838 return r 

839 

840 def toEnu(self, Enu=None, **name_Enu_kwds): 

841 '''Get the local I{East, North, Up} (ENU) components. 

842 

843 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}. 

844 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and 

845 optional, additional B{C{Enu}} keyword arguments, 

846 ignored if C{B{Enu} is None}. 

847 

848 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, 

849 an L{Enu4Tuple}C{(east, north, up, ltp)}. 

850 

851 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}. 

852 ''' 

853 return self.enu4._toEnu(Enu, name_Enu_kwds) 

854 

855 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds): 

856 '''Get the geodetic C{(lat, lon, height)} coordinates if this local. 

857 

858 @kwarg LatLon: Optional class to return C{(x, y, z)} (C{LatLon}) 

859 or C{None}. 

860 @kwarg ltp: Optional I{local tangent plane} (LTP) (L{Ltp}), 

861 overriding this ENU/NED/AER/XYZ's LTP. 

862 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and 

863 optional, additional B{C{LatLon}} keyword arguments, 

864 ignored if C{B{LatLon} is None}. 

865 

866 @return: An B{C{LatLon}} instance of if C{B{LatLon} is None}, an 

867 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, 

868 datum)} with C{M=None}, always. 

869 

870 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or 

871 B{C{name_LatLon_kwds}}. 

872 ''' 

873 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds) 

874 t = ltp._local2ecef(self, nine=True) 

875 if LatLon is None: 

876 r = _xnamed(t, n) if n else t 

877 else: 

878 kwds = _xkwds(kwds, height=t.height, datum=t.datum) 

879 r = LatLon(t.lat, t.lon, **kwds) # XXX ltp? 

880 return r 

881 

882 def toLocal9Tuple(self, M=False, **name): 

883 '''Get this local as a C{Local9Tuple}. 

884 

885 @kwarg M: Optionally include the rotation matrix (C{bool}). 

886 @kwarg name: Optional C{B{name}=NN} (C{str}). 

887 

888 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, 

889 ecef, M)} with C{ltp} this C{Ltp}, C{ecef} an 

890 L{Ecef9Tuple} and C{M} L{EcefMatrix} or C{None}. 

891 ''' 

892 ltp = self.ltp # see C{self.toLatLon} 

893 t = ltp._local2ecef(self, nine=True, M=M) 

894 return Local9Tuple(self.x, self.y, self.z, t.lat, t.lon, t.height, 

895 ltp, t, t.M, name=t._name__(name)) 

896 

897 def toNed(self, Ned=None, **name_Ned_kwds): 

898 '''Get the local I{North, East, Down} (Ned) components. 

899 

900 @kwarg Ned: Class to return NED (L{Ned}) or C{None}. 

901 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and 

902 optional, additional B{C{Ned}} keyword arguments, 

903 ignored if C{B{Ned} is None}. 

904 

905 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, 

906 an L{Ned4Tuple}C{(north, east, down, ltp)}. 

907 

908 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}. 

909 ''' 

910 return self.ned4._toNed(Ned, name_Ned_kwds) 

911 

912 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

913 '''Get the local I{X, Y, Z} (XYZ) components. 

914 

915 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, 

916 L{Ned}, L{Aer}) or C{None}. 

917 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and 

918 optional, additional B{C{Xyz}} keyword arguments, 

919 ignored if C{B{Xyz} is None}. 

920 

921 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None}, 

922 an L{Xyz4Tuple}C{(x, y, z, ltp)}. 

923 

924 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_EXyz_kwds}}. 

925 ''' 

926 return self.xyz4._toXyz(Xyz, name_Xyz_kwds) 

927 

928 @Property_RO 

929 def up(self): 

930 '''Get the Up component (C{meter}). 

931 ''' 

932 return Meter(up=self.z) 

933 

934# @Property_RO 

935# def x(self): # see: Vector3d.x 

936# '''Get the X component (C{meter}). 

937# ''' 

938# return self._x 

939 

940# @Property_RO 

941# def xyz(self): # see: Vector3d.xyz 

942# '''Get the I{local} C{(X, Y, Z)} coordinates (L{Vector3Tuple}C{(x, y, z)}). 

943# ''' 

944# return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz, Local6tuple.xyz 

945 

946 @Property_RO 

947 def xyz4(self): 

948 '''Get the C{(x, y, z, ltp)} components (L{Xyz4Tuple}). 

949 ''' 

950 return Xyz4Tuple(self.x, self.y, self.z, self.ltp, name=self.name) 

951 

952 @Property_RO 

953 def xyzLocal(self): 

954 '''Get this L{XyzLocal}. 

955 ''' 

956 return self 

957 

958# @Property_RO 

959# def y(self): # see: Vector3d.y 

960# '''Get the Y component (C{meter}). 

961# ''' 

962# return self._y 

963 

964# @Property_RO 

965# def z(self): # see: Vector3d.z 

966# '''Get the Z component (C{meter}). 

967# ''' 

968# return self._z 

969 

970 

971class Xyz4Tuple(_NamedTuple): 

972 '''4-Tuple C{(x, y, z, ltp)}, all in C{meter} except C{ltp}. 

973 ''' 

974 _Names_ = (_x_, _y_, _z_, _ltp_) 

975 _Units_ = ( Meter, Meter, Meter, _Pass) 

976 

977 def _toXyz(self, Cls, Cls_kwds): 

978 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance. 

979 ''' 

980 kwds = _name1__(Cls_kwds, _or_nameof=self) 

981 if issubclassof(Cls, XyzLocal): 

982 return Cls(*self, **kwds) 

983 else: 

984 return _4Tuple2Cls(self, Cls, kwds) 

985 

986 @Property_RO 

987 def xyzLocal(self): 

988 '''Get this L{Xyz4Tuple} as an L{XyzLocal}. 

989 ''' 

990 return XyzLocal(*self, name=self.name) 

991 

992 

993class Enu(XyzLocal): 

994 '''Local C{Eeast-North-Up} (ENU) location in a I{local tangent plane}. 

995 

996 @see: U{East, North, Up (ENU)<https://GSSC.ESA.int/navipedia/index.php/ 

997 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

998 ''' 

999 _toStr = _enu_ 

1000 

1001 def __init__(self, east_enu, north=0, up=0, ltp=None, **name): 

1002 '''New L{Enu}. 

1003 

1004 @arg east_enu: Scalar East component (C{meter}) or a previous 

1005 I{local} instance (L{Enu}, L{Enu4Tuple}, L{Aer}, 

1006 L{Aer4Tuple}, L{Local9Tuple}, L{Ned}, L{Ned4Tuple}, 

1007 L{XyzLocal} or L{Xyz4Tuple}). 

1008 @kwarg north: Scalar North component (C{meter}) only used with 

1009 scalar B{C{east_enu}}. 

1010 @kwarg up: Scalar Up component only used with scalar B{C{east_enu}}, 

1011 normal from the surface of the ellipsoid or sphere (C{meter}). 

1012 @kwarg ltp: The I{local tangent plane}, (geodetic) origin (L{Ltp}, 

1013 L{LocalCartesian}). 

1014 @kwarg name: Optional C{B{name}=NN} (C{str}). 

1015 

1016 @raise TypeError: Invalid B{C{east_enu}} or B{C{ltp}}. 

1017 

1018 @raise UnitError: Invalid B{C{east_enu}}, B{C{north}} or B{C{up}}. 

1019 ''' 

1020 XyzLocal.__init__(self, east_enu, north, up, ltp=ltp, **name) 

1021 

1022 def toUvw(self, location, Uvw=None, **name_Uvw_kwds): 

1023 '''Get the I{u, v, w} (UVW) components at a location. 

1024 

1025 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian}, 

1026 L{Vector3d}) location, like a Point-Of-View. 

1027 @kwarg Uvw: Class to return UWV (L{Uvw}) or C{None}. 

1028 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1029 additional B{L{Uvw}} keyword arguments, ignored if 

1030 C{B{Uvw} is None}. 

1031 

1032 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a 

1033 L{Uvw3Tuple}C{(u, v, w)}. 

1034 

1035 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}}. 

1036 

1037 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}. 

1038 ''' 

1039 try: 

1040 sa, ca, sb, cb = sincos2_(*location.philam) 

1041 except Exception as x: 

1042 raise _TypeError(location=location, cause=x) 

1043 e, n, u, _ = self.enu4 

1044 

1045 t = ca * u - sa * n 

1046 U = cb * t - sb * e 

1047 V = cb * e + sb * t 

1048 W = ca * n + sa * u 

1049 

1050 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self) 

1051 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \ 

1052 Uvw( U, V, W, name=n, **kwds) 

1053 

1054 @Property_RO 

1055 def xyzLocal(self): 

1056 '''Get this ENU as an L{XyzLocal}. 

1057 ''' 

1058 return XyzLocal(*self.xyz4, name=self.name) 

1059 

1060 

1061class Enu4Tuple(_NamedTuple): 

1062 '''4-Tuple C{(east, north, up, ltp)}, in C{meter} except C{ltp}. 

1063 ''' 

1064 _Names_ = (_east_, _north_, _up_, _ltp_) 

1065 _Units_ = ( Meter, Meter, Meter, _Pass) 

1066 

1067 def _toEnu(self, Cls, Cls_kwds): 

1068 '''(INTERNAL) Return C{Cls(..., **Cls_kwds)} instance. 

1069 ''' 

1070 kwds = _name1__(Cls_kwds, _or_nameof=self) 

1071 if issubclassof(Cls, XyzLocal): 

1072 return Cls(*self, **kwds) 

1073 else: 

1074 return _4Tuple2Cls(self, Cls, kwds) 

1075 

1076 @Property_RO 

1077 def xyzLocal(self): 

1078 '''Get this L{Enu4Tuple} as an L{XyzLocal}. 

1079 ''' 

1080 return XyzLocal(*self, name=self.name) 

1081 

1082 

1083class Local9Tuple(_NamedTuple): 

1084 '''9-Tuple C{(x, y, z, lat, lon, height, ltp, ecef, M)} with I{local} C{x}, 

1085 C{y}, C{z} all in C{meter}, I{geodetic} C{lat}, C{lon}, C{height}, I{local 

1086 tangent plane} C{ltp} (L{Ltp}), C{ecef} (L{Ecef9Tuple}) with I{geocentric} 

1087 C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height} and I{concatenated} 

1088 rotation matrix C{M} (L{EcefMatrix}) or C{None}. 

1089 ''' 

1090 _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _ltp_, _ecef_, _M_) 

1091 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass, _Pass, _Pass) 

1092 

1093 @Property_RO 

1094 def azimuth(self): 

1095 '''Get the I{local} Azimuth, bearing from North (C{degrees360}). 

1096 ''' 

1097 return self.xyzLocal.aer4.azimuth 

1098 

1099 @Property_RO 

1100 def down(self): 

1101 '''Get the I{local} Down, C{-z} component (C{meter}). 

1102 ''' 

1103 return -self.z 

1104 

1105 @Property_RO 

1106 def east(self): 

1107 '''Get the I{local} East, C{x} component (C{meter}). 

1108 ''' 

1109 return self.x 

1110 

1111 @Property_RO 

1112 def elevation(self): 

1113 '''Get the I{local} Elevation, tilt I{above} horizon (C{degrees90}). 

1114 ''' 

1115 return self.xyzLocal.aer4.elevation 

1116 

1117 @Property_RO 

1118 def groundrange(self): 

1119 '''Get the I{local} ground range, distance (C{meter}). 

1120 ''' 

1121 return self.xyzLocal.aer4.groundrange 

1122 

1123 @Property_RO 

1124 def lam(self): 

1125 '''Get the I{geodetic} longitude in C{radians} (C{float}). 

1126 ''' 

1127 return self.philam.lam 

1128 

1129 @Property_RO 

1130 def latlon(self): 

1131 '''Get the I{geodetic} lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}). 

1132 ''' 

1133 return LatLon2Tuple(self.lat, self.lon, name=self.name) 

1134 

1135 @Property_RO 

1136 def latlonheight(self): 

1137 '''Get the I{geodetic} lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}). 

1138 ''' 

1139 return self.latlon.to3Tuple(self.height) 

1140 

1141 @Property_RO 

1142 def north(self): 

1143 '''Get the I{local} North, C{y} component (C{meter}). 

1144 ''' 

1145 return self.y 

1146 

1147 @Property_RO 

1148 def phi(self): 

1149 '''Get the I{geodetic} latitude in C{radians} (C{float}). 

1150 ''' 

1151 return self.philam.phi 

1152 

1153 @Property_RO 

1154 def philam(self): 

1155 '''Get the I{geodetic} lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}). 

1156 ''' 

1157 return PhiLam2Tuple(radians(self.lat), radians(self.lon), name=self.name) 

1158 

1159 @Property_RO 

1160 def philamheight(self): 

1161 '''Get the I{geodetic} lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}). 

1162 ''' 

1163 return self.philam.to3Tuple(self.height) 

1164 

1165 @Property_RO 

1166 def slantrange(self): 

1167 '''Get the I{local} slant Range, distance (C{meter}). 

1168 ''' 

1169 return self.xyzLocal.aer4.slantrange 

1170 

1171 def toAer(self, Aer=None, **name_Aer_kwds): 

1172 '''Get the I{local} I{Azimuth, Elevation, slant Range} (AER) components. 

1173 

1174 @kwarg Aer: Class to return AER (L{Aer}) or C{None}. 

1175 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1176 additional B{L{Aer}} keyword arguments, ignored if 

1177 B{C{Aer}} is C{None}. 

1178 

1179 @return: AER as an L{Aer} instance or if C{B{Aer} is None}, an 

1180 L{Aer4Tuple}C{(azimuth, elevation, slantrange, ltp)}. 

1181 

1182 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}. 

1183 ''' 

1184 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds) 

1185 

1186 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds): 

1187 '''Convert this I{local} to I{geocentric} C{(x, y, z)} (ECEF). 

1188 

1189 @kwarg Cartesian: Optional class to return C{(x, y, z)} (C{Cartesian}) 

1190 or C{None}. 

1191 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1192 additional B{C{Cartesian}} keyword arguments, ignored if 

1193 C{B{Cartesian} is None}. 

1194 

1195 @return: A C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})} instance or 

1196 if C{B{Cartesian} is None}, a L{Vector4Tuple}C{(x, y, z, h)} . 

1197 

1198 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}}. 

1199 ''' 

1200 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple 

1201 

1202 def toEnu(self, Enu=None, **name_Enu_kwds): 

1203 '''Get the I{local} I{East, North, Up} (ENU) components. 

1204 

1205 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}. 

1206 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1207 additional B{L{Enu}} keyword arguments, ignored if 

1208 C{B{Enu} is None}. 

1209 

1210 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an 

1211 L{Enu4Tuple}C{(east, north, up, ltp)}. 

1212 

1213 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}. 

1214 ''' 

1215 return self.xyzLocal.toEnu(Enu=Enu, **name_Enu_kwds) 

1216 

1217 def toLatLon(self, LatLon=None, **name_LatLon_kwds): 

1218 '''Convert this I{local} to I{geodetic} C{(lat, lon, height)}. 

1219 

1220 @kwarg LatLon: Optional class to return C{(lat, lon, height)} 

1221 (C{LatLon}) or C{None}. 

1222 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1223 additional B{C{LatLon}} keyword arguments, ignored if 

1224 C{B{LatLon} is None}. 

1225 

1226 @return: An instance of C{B{LatLon}(lat, lon, **B{LatLon_kwds})} 

1227 or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon, 

1228 height)} respectively L{LatLon4Tuple}C{(lat, lon, height, 

1229 datum)} depending on whether C{datum} is un-/specified. 

1230 

1231 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}. 

1232 ''' 

1233 return self.ecef.toLatLon(LatLon=LatLon, **name_LatLon_kwds) # PYCHOK _Tuple 

1234 

1235 def toNed(self, Ned=None, **name_Ned_kwds): 

1236 '''Get the I{local} I{North, East, Down} (NED) components. 

1237 

1238 @kwarg Ned: Class to return NED (L{Ned}) or C{None}. 

1239 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1240 additional B{L{Ned}} keyword arguments, ignored if 

1241 B{C{Ned}} is C{None}. 

1242 

1243 @return: NED as an L{Ned} instance or if C{B{Ned} is None}, an 

1244 L{Ned4Tuple}C{(north, east, down, ltp)}. 

1245 

1246 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}. 

1247 ''' 

1248 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds) 

1249 

1250 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

1251 '''Get the I{local} I{X, Y, Z} (XYZ) components. 

1252 

1253 @kwarg Xyz: Class to return XYZ (L{XyzLocal}) or C{None}. 

1254 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1255 additional B{C{Xyz}} keyword arguments, ignored if 

1256 C{B{Xyz} is None}. 

1257 

1258 @return: XYZ as an B{C{Xyz}} instance or if C{B{Xyz} is None}, 

1259 an L{Xyz4Tuple}C{(x, y, z, ltp)}. 

1260 

1261 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}. 

1262 ''' 

1263 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds) 

1264 

1265 @Property_RO 

1266 def up(self): 

1267 '''Get the I{local} Up, C{z} component (C{meter}). 

1268 ''' 

1269 return self.z 

1270 

1271 @Property_RO 

1272 def xyz(self): 

1273 '''Get the I{local} C{(X, Y, Z)} components (L{Vector3Tuple}C{(x, y, z)}). 

1274 ''' 

1275 return Vector3Tuple(self.x, self.y, self.z, name=self.name) # like Ecef9Tuple.xyz 

1276 

1277 @Property_RO 

1278 def xyzLocal(self): 

1279 '''Get this L{Local9Tuple} as an L{XyzLocal}. 

1280 ''' 

1281 return XyzLocal(*self.xyz, ltp=self.ltp, name=self.name) # PYCHOK .ltp 

1282 

1283 

1284_XyzLocals4 = XyzLocal, Enu, Ned, Aer # PYCHOK in .ltp 

1285_XyzLocals5 = _XyzLocals4 + (Local9Tuple,) # PYCHOK in .ltp 

1286 

1287 

1288class Uvw(_Vector3d): 

1289 '''3-D C{u-v-w} (UVW) components. 

1290 ''' 

1291 _toStr = _uvw_ 

1292 

1293 def __init__(self, u_uvw, v=0, w=0, **name): 

1294 '''New L{Uvw}. 

1295 

1296 @arg u_uvw: Scalar U component (C{meter}) or a previous instance 

1297 (L{Uvw}, L{Uvw3Tuple}, L{Vector3d}). 

1298 @kwarg v: V component (C{meter}) only used with scalar B{C{u_uvw}}. 

1299 @kwarg w: W component (C{meter}) only used with scalar B{C{u_uvw}}. 

1300 @kwarg name: Optional C{B{name}=NN} (C{str}). 

1301 

1302 @raise TypeError: Invalid B{C{east_enu}}. 

1303 

1304 @raise UnitError: Invalid B{C{east_enu}}, B{C{v}} or B{C{w}}. 

1305 ''' 

1306 Vector3d.__init__(self, u_uvw, v, w, **name) 

1307 

1308 def toEnu(self, location, Enu=Enu, **name_Enu_kwds): 

1309 '''Get the I{East, North, Up} (ENU) components at a location. 

1310 

1311 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian}, 

1312 L{Vector3d}) location from where to cast the L{Los}. 

1313 @kwarg Enu: Class to return ENU (L{Enu}) or C{None}. 

1314 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1315 additional B{L{Enu}} keyword arguments, ignored if 

1316 C{B{Enu} is None}. 

1317 

1318 @return: ENU as an L{Enu} instance or if C{B{Enu} is None}, an 

1319 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}. 

1320 

1321 @raise TypeError: Invalid B{C{location}} or B{C{name_Enu_kwds}}. 

1322 

1323 @see: Function U{lookAtSpheroid<https://PyPI.org/project/pymap3d>}. 

1324 ''' 

1325 try: 

1326 sa, ca, sb, cb = sincos2_(*location.philam) 

1327 except Exception as x: 

1328 raise _TypeError(location=location, cause=x) 

1329 u, v, w = self.uvw 

1330 

1331 t = cb * u + sb * v 

1332 E = cb * v - sb * u 

1333 N = ca * w - sa * t 

1334 U = ca * t + sa * w 

1335 

1336 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self) 

1337 return Enu4Tuple(E, N, U, name=n) if Enu is None else \ 

1338 Enu( E, N, U, name=n, **kwds) 

1339 

1340 u = Vector3d.x 

1341 

1342 @Property_RO 

1343 def uvw(self): 

1344 '''Get the C{(U, V, W)} components (L{Uvw3Tuple}C{(u, v, w)}). 

1345 ''' 

1346 return Uvw3Tuple(self.u, self.v, self.w, name=self.name) 

1347 

1348 v = Vector3d.y 

1349 w = Vector3d.z 

1350 

1351 

1352class Uvw3Tuple(_NamedTuple): 

1353 '''3-Tuple C{(u, v, w)}, in C{meter}. 

1354 ''' 

1355 _Names_ = ('u', 'v', 'w') 

1356 _Units_ = ( Meter, Meter, Meter) 

1357 

1358 

1359class Los(Aer): 

1360 '''A Line-Of-Sight (LOS) from a C{LatLon} or C{Cartesian} location. 

1361 ''' 

1362 

1363 def __init__(self, azimuth_aer, elevation=0, **name): 

1364 '''New L{Los}. 

1365 

1366 @arg azimuth_aer: Scalar azimuth, bearing from North (compass C{degrees}) 

1367 or a previous instance (L{Aer}, L{Aer4Tuple}, L{Enu}, 

1368 L{Enu4Tuple} or L{Los}). 

1369 @kwarg elevation: Scalar angle I{above} the horizon (C{degrees}, horizon 

1370 is 0, zenith +90, nadir -90), only used with scalar 

1371 B{C{azimuth_aer}}. 

1372 @kwarg name: Optional C{B{name}=NN} (C{str}). 

1373 

1374 @raise TypeError: Invalid B{C{azimuth_aer}}. 

1375 

1376 @raise UnitError: Invalid B{C{azimuth_aer}} or B{C{elevation}}. 

1377 ''' 

1378 t = Aer(azimuth_aer, elevation) 

1379 Aer.__init__(self, t.azimuth, t.elevation, slantrange=_1_0, **name) 

1380 

1381 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds): 

1382 '''Get this LOS' I{target} (UVW) components from a location. 

1383 

1384 @arg location: The geodetic (C{LatLon}) or geocentric (C{Cartesian}, 

1385 L{Vector3d}) location from where to cast the L{Los}. 

1386 

1387 @see: Method L{Enu.toUvw} for further details. 

1388 ''' 

1389 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds) 

1390 

1391 def toEnu(self, Enu=Enu, **name_Enu_kwds): 

1392 '''Get this LOS as I{East, North, Up} (ENU) components. 

1393 

1394 @see: Method L{Aer.toEnu} for further details. 

1395 ''' 

1396 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds) 

1397 

1398 

1399class ChLV9Tuple(Local9Tuple): 

1400 '''9-Tuple C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with I{B{unfalsed} Swiss 

1401 (Y, X, h_)} coordinates and height, all in C{meter}, C{ltp} either a L{ChLV}, 

1402 L{ChLVa} or L{ChLVe} instance and C{ecef} (L{EcefKarney} I{at Bern, Ch}), 

1403 otherwise like L{Local9Tuple}. 

1404 ''' 

1405 _Names_ = (_Y_, _X_, _h__) + Local9Tuple._Names_[3:] 

1406 

1407 @Property_RO 

1408 def E_LV95(self): 

1409 '''Get the B{falsed} I{Swiss E_LV95} easting (C{meter}). 

1410 ''' 

1411 return self.EN2_LV95.E_LV95 

1412 

1413 @Property_RO 

1414 def EN2_LV95(self): 

1415 '''Get the I{falsed Swiss (E_LV95, N_LV95)} easting and northing (L{ChLVEN2Tuple}). 

1416 ''' 

1417 return ChLVEN2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, True), name=self.name) 

1418 

1419 @Property_RO 

1420 def h_LV03(self): 

1421 '''Get the I{Swiss h_} height (C{meter}). 

1422 ''' 

1423 return self.h_ 

1424 

1425 @Property_RO 

1426 def h_LV95(self): 

1427 '''Get the I{Swiss h_} height (C{meter}). 

1428 ''' 

1429 return self.h_ 

1430 

1431 @property_RO 

1432 def isChLV(self): 

1433 '''Is this a L{ChLV}-generated L{ChLV9Tuple}?. 

1434 ''' 

1435 return self.ltp.__class__ is _MODS.ltp.ChLV 

1436 

1437 @property_RO 

1438 def isChLVa(self): 

1439 '''Is this a L{ChLVa}-generated L{ChLV9Tuple}?. 

1440 ''' 

1441 return self.ltp.__class__ is _MODS.ltp.ChLVa 

1442 

1443 @property_RO 

1444 def isChLVe(self): 

1445 '''Is this a L{ChLVe}-generated L{ChLV9Tuple}?. 

1446 ''' 

1447 return self.ltp.__class__ is _MODS.ltp.ChLVe 

1448 

1449 @Property_RO 

1450 def N_LV95(self): 

1451 '''Get the B{falsed} I{Swiss N_LV95} northing (C{meter}). 

1452 ''' 

1453 return self.EN2_LV95.N_LV95 

1454 

1455 @Property_RO 

1456 def x(self): 

1457 '''Get the I{local x, Swiss Y} easting (C{meter}). 

1458 ''' 

1459 return self.Y 

1460 

1461 @Property_RO 

1462 def x_LV03(self): 

1463 '''Get the B{falsed} I{Swiss x_LV03} northing (C{meter}). 

1464 ''' 

1465 return self.yx2_LV03.x_LV03 

1466 

1467 @Property_RO 

1468 def y(self): 

1469 '''Get the I{local y, Swiss X} northing (C{meter}). 

1470 ''' 

1471 return self.X 

1472 

1473 @Property_RO 

1474 def y_LV03(self): 

1475 '''Get the B{falsed} I{Swisss y_LV03} easting (C{meter}). 

1476 ''' 

1477 return self.yx2_LV03.y_LV03 

1478 

1479 @Property_RO 

1480 def YX(self): 

1481 '''Get the B{unfalsed} easting and northing (L{ChLVYX2Tuple}). 

1482 ''' 

1483 return ChLVYX2Tuple(self.Y, self.X, name=self.name) 

1484 

1485 @Property_RO 

1486 def yx2_LV03(self): 

1487 '''Get the B{falsed} I{Swiss (y_LV03, x_LV03)} easting and northing (L{ChLVyx2Tuple}). 

1488 ''' 

1489 return ChLVyx2Tuple(*_MODS.ltp.ChLV.false2(self.Y, self.X, False), name=self.name) 

1490 

1491 @Property_RO 

1492 def z(self): 

1493 '''Get the I{local z, Swiss h_} height (C{meter}). 

1494 ''' 

1495 return self.h_ 

1496 

1497 

1498class ChLVYX2Tuple(_NamedTuple): 

1499 '''2-Tuple C{(Y, X)} with B{unfalsed} I{Swiss LV95} easting and norting 

1500 in C{meter}. 

1501 ''' 

1502 _Names_ = (_Y_, _X_) 

1503 _Units_ = ( Meter, Meter) 

1504 

1505 def false2(self, LV95=True): 

1506 '''Return the falsed C{Swiss LV95} or C{LV03} version of the projection. 

1507 

1508 @see: Function L{ChLV.false2} for more information. 

1509 ''' 

1510 return _MODS.ltp.ChLV.false2(*self, LV95=LV95, name=self.name) 

1511 

1512 

1513class ChLVEN2Tuple(_NamedTuple): 

1514 '''2-Tuple C{(E_LV95, N_LV95)} with B{falsed} I{Swiss LV95} easting and 

1515 norting in C{meter (2_600_000, 1_200_000)} and origin at C{Bern, Ch}. 

1516 ''' 

1517 _Names_ = ('E_LV95', 'N_LV95') 

1518 _Units_ = ChLVYX2Tuple._Units_ 

1519 

1520 def unfalse2(self): 

1521 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}. 

1522 

1523 @see: Function L{ChLV.unfalse2} for more information. 

1524 ''' 

1525 return _MODS.ltp.ChLV.unfalse2(*self, LV95=True, name=self.name) 

1526 

1527 

1528class ChLVyx2Tuple(_NamedTuple): 

1529 '''2-Tuple C{(y_LV03, x_LV03)} with B{falsed} I{Swiss LV03} easting and 

1530 norting in C{meter (600_000, 200_000)} and origin at C{Bern, Ch}. 

1531 ''' 

1532 _Names_ = ('y_LV03', 'x_LV03') 

1533 _Units_ = ChLVYX2Tuple._Units_ 

1534 

1535 def unfalse2(self): 

1536 '''Return this projection as an B{unfalsed} L{ChLVYX2Tuple}. 

1537 

1538 @see: Function L{ChLV.unfalse2} for more information. 

1539 ''' 

1540 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name) 

1541 

1542 

1543class Footprint5Tuple(_NamedTuple): 

1544 '''5-Tuple C{(center, upperleft, upperight, loweright, lowerleft)} 

1545 with the C{center} and 4 corners of the I{local} projection of 

1546 a C{Frustum}, each an L{Xyz4Tuple}, L{XyzLocal}, C{LatLon}, etc. 

1547 

1548 @note: Misspelling of C{upperight} and C{loweright} is I{intentional}. 

1549 ''' 

1550 _Names_ = (_center_, 'upperleft', 'upperight', 'loweright', 'lowerleft') 

1551 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1552 

1553 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds): 

1554 '''Convert this footprint's C{center} and 4 corners to I{geodetic} 

1555 C{LatLon(lat, lon, height)}s or C{LatLon3-} or C{-4Tuple}s. 

1556 

1557 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this 

1558 footprint's C{center} or C{frustrum} C{ltp}. 

1559 @kwarg LatLon: Optional I{geodetic} class (C{LatLon}) or C{None}. 

1560 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optional, 

1561 additional B{C{LatLon}} keyword arguments, ignored if 

1562 C{B{LatLon} is None}. 

1563 

1564 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, 

1565 **B{name_LatLon_kwds})} instances or if C{B{LatLon} is None}, 

1566 5 L{LatLon3Tuple}C{(lat, lon, height)}s respectively 

1567 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s depending 

1568 on whether keyword argument C{datum} is un-/specified. 

1569 

1570 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}}. 

1571 

1572 @see: Methods L{XyzLocal.toLatLon} and L{Footprint5Tuple.xyzLocal5}. 

1573 ''' 

1574 ltp = _MODS.ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center 

1575 kwds = _name1__(name_LatLon_kwds, _or_nameof=self) 

1576 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon) 

1577 return Footprint5Tuple(t.toLatLon(**kwds) for t in self.xyzLocal5()) 

1578 

1579 def xyzLocal5(self, ltp=None): 

1580 '''Return this footprint's C{center} and 4 corners as 5 L{XyzLocal}s. 

1581 

1582 @kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding 

1583 the {center} and corner C{ltp}s. 

1584 

1585 @return: A L{Footprint5Tuple} of 5 L{XyzLocal} instances. 

1586 

1587 @raise TypeError: Invalid B{C{ltp}}. 

1588 ''' 

1589 if ltp is None: 

1590 p = self 

1591 else: 

1592 p = _MODS.ltp._xLtp(ltp) 

1593 p = tuple(Xyz4Tuple(t.x, t.y, t.z, p) for t in self) 

1594 return Footprint5Tuple(t.xyzLocal for t in p) 

1595 

1596 

1597__all__ += _ALL_DOCS(_NamedAerNed) 

1598 

1599# **) MIT License 

1600# 

1601# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved. 

1602# 

1603# Permission is hereby granted, free of charge, to any person obtaining a 

1604# copy of this software and associated documentation files (the "Software"), 

1605# to deal in the Software without restriction, including without limitation 

1606# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

1607# and/or sell copies of the Software, and to permit persons to whom the 

1608# Software is furnished to do so, subject to the following conditions: 

1609# 

1610# The above copyright notice and this permission notice shall be included 

1611# in all copies or substantial portions of the Software. 

1612# 

1613# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

1614# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

1615# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

1616# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

1617# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

1618# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

1619# OTHER DEALINGS IN THE SOFTWARE.