Coverage for pygeodesy/ltpTuples.py: 95%

565 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-11-12 16:17 -0500

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 # _MODS 

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_ 

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 Azimuth, Bearing, Degrees, Degrees_, Height, \ 

32 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_ 

33from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians 

34from pygeodesy.vector3d import Vector3d 

35 

36# from math import cos, radians # from .utily 

37 

38__all__ = _ALL_LAZY.ltpTuples 

39__version__ = '24.11.07' 

40 

41_aer_ = 'aer' 

42_alt_ = 'alt' 

43_down_ = 'down' 

44_east_ = 'east' 

45_enu_ = 'enu' 

46_h__ = 'h_' 

47_ltp = _MODS.into(ltp=__name__) 

48_ned_ = 'ned' 

49_north_ = 'north' 

50_local_ = 'local' 

51_roll_ = 'roll' 

52_slantrange_ = 'slantrange' 

53_tilt_ = 'tilt' 

54_uvw_ = 'uvw' 

55_yaw_ = 'yaw' 

56 

57 

58def _er2gr(e, r): 

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

60 ''' 

61 c = cos(radians(e)) 

62 return Meter_(groundrange=r * c) 

63 

64 

65def _init(inst, abc, ltp, name): 

66 '''(INTERNAL) Complete C{__init__}. 

67 ''' 

68 if abc is None: 

69 n = _name__(**name) 

70 else: 

71 n = abc._name__(name) 

72 ltp = _xattr(abc, ltp=ltp) 

73 if ltp: 

74 inst._ltp = _ltp._xLtp(ltp) 

75 if n: 

76 inst.name = n 

77 

78 

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

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

81 ''' 

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

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

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

85 if sep: 

86 t = sep.join(t) 

87 if fmt: 

88 t = fmt(t) 

89 return a, t 

90 

91 

92def _xyz2aer4(inst): 

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

94 ''' 

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

96 A = Azimuth(atan2b(x, y)) 

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

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

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

100 

101 

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

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

104 ''' 

105 n, inst = _xkwds_item2(name_inst) 

106 if isinstance(inst, Types): 

107 return None 

108 try: 

109 return inst.xyzLocal 

110 except (AttributeError, TypeError): 

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

112 

113 

114class _AbcBase(_NamedBase): 

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

116 ''' 

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

118 

119 @Property_RO 

120 def ltp(self): 

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

122 ''' 

123 return self._ltp 

124 

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

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

127 

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

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

130 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer} 

131 is None}. 

132 

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

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

135 

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

137 ''' 

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

139 

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

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

142 

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

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

145 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

146 is None}. 

147 

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

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

150 

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

152 ''' 

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

154 

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

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

157 

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

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

160 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned} 

161 is None}. 

162 

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

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

165 

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

167 ''' 

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

169 

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

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

172 

173 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer}) 

174 or C{None}. 

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

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

177 is None}. 

178 

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

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

181 

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

183 ''' 

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

185 

186 @Property_RO 

187 def xyz(self): 

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

189 ''' 

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

191 

192 @property_RO 

193 def xyz3(self): 

194 '''Get the I{local} C{(X, Y, Z)} coordinates as C{3-tuple}. 

195 ''' 

196 return tuple(self.xyz) 

197 

198 @property_RO 

199 def xyz4(self): # PYCHOK no cover 

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

201 self._notOverloaded() 

202 

203 @Property_RO 

204 def xyzLocal(self): 

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

206 ''' 

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

208 

209 

210class _Abc4Tuple(_NamedTuple): 

211 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple}, 

212 C{Ned4Tuple} and C{Xyz4Tuple}. 

213 ''' 

214 def _2Cls(self, Abc, Cls, Cls_kwds): 

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

216 ''' 

217 kwds = _name1__(Cls_kwds, _or_nameof=self) 

218 _is = _MODS.basics.issubclassof 

219 if Cls is None: 

220 n, _ = _name2__(Cls_kwds) 

221 r = self.copy(name=n) if n else self 

222 elif _is(Cls, Abc): 

223 r = Cls(*self, **kwds) 

224 elif _is(Cls, Aer): 

225 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls)) 

226 elif _is(Cls, Enu): # PYCHOK no cover 

227 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls)) 

228 elif _is(Cls, Ned): 

229 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls)) 

230 elif _is(Cls, XyzLocal): # PYCHOK no cover 

231 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls)) 

232 elif Cls is Local9Tuple: # PYCHOK no cover 

233 r = self.xyzLocal.toLocal9Tuple(**kwds) 

234 else: # PYCHOK no cover 

235 n = Abc.__name__[:3] 

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

237 return r 

238 

239 @property_RO 

240 def xyzLocal(self): # PYCHOK no cover 

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

242 self._notOverloaded() 

243 

244 

245class Aer(_AbcBase): 

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

247 ''' 

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

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

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

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

252 _toStr = _aer_ 

253 

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

255 '''New L{Aer}. 

256 

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

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

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

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

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

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

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

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

265 B{C{azimuth_aer}}. 

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

267 L{LocalCartesian}). 

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

269 

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

271 

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

273 or B{C{slantrange}}. 

274 ''' 

275 if _isDegrees(azimuth_aer): 

276 aer = None 

277 t = (Azimuth(azimuth_aer), 

278 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0), 

279 Meter_(slantrange=slantrange), ltp) 

280 else: # PYCHOK no cover 

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

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

283 t = aer.aer4 

284 self._azimuth, self._elevation, self._slantrange, _ = t 

285 _init(self, aer, ltp, name) 

286 

287 @Property_RO 

288 def aer4(self): 

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

290 ''' 

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

292 

293 @Property_RO 

294 def azimuth(self): 

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

296 ''' 

297 return self._azimuth 

298 

299 @Property_RO 

300 def down(self): 

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

302 ''' 

303 return self.xyzLocal.down 

304 

305 @Property_RO 

306 def east(self): 

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

308 ''' 

309 return self.xyzLocal.east 

310 

311 @Property_RO 

312 def elevation(self): 

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

314 ''' 

315 return self._elevation 

316 

317 @Property_RO 

318 def groundrange(self): 

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

320 ''' 

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

322 

323 @Property_RO 

324 def north(self): 

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

326 ''' 

327 return self.xyzLocal.north 

328 

329 @Property_RO 

330 def slantrange(self): 

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

332 ''' 

333 return self._slantrange 

334 

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

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

337 (bearing), elevation and slant range. 

338 

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

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

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

342 

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

344 ''' 

345 m = _MODS.dms 

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

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

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

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

350 

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

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

353 

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

355 number of (decimal) digits, unstripped 

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

357 backets format (C{str}) and separator 

358 C{B{sep}=", "} to join (C{str}). 

359 

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

361 ''' 

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

363 return t 

364 

365 @Property_RO 

366 def up(self): 

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

368 ''' 

369 return self.xyzLocal.up 

370 

371 @Property_RO 

372 def x(self): 

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

374 ''' 

375 return self.xyz4.x 

376 

377 @Property_RO 

378 def xyz4(self): 

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

380 ''' 

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

382 R = self._slantrange 

383 r = cE * R # ground range 

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

385 

386 @Property_RO 

387 def y(self): 

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

389 ''' 

390 return self.xyz4.y 

391 

392 @Property_RO 

393 def z(self): 

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

395 ''' 

396 return self.xyz4.z 

397 

398 

399class Aer4Tuple(_Abc4Tuple): 

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

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

402 ''' 

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

404 _Units_ = ( Meter, Meter, Meter, _Pass) 

405 

406 def _toAer(self, Cls, Cls_kwds): 

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

408 ''' 

409 return self._2Cls(Aer, Cls, Cls_kwds) 

410 

411 @Property_RO 

412 def groundrange(self): 

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

414 ''' 

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

416 

417 @Property_RO 

418 def xyzLocal(self): 

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

420 ''' 

421 return Aer(self).xyzLocal 

422 

423 

424class Attitude4Tuple(_NamedTuple): 

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

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

427 the attitude of a plane or camera. 

428 ''' 

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

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

431 

432 @Property_RO 

433 def atyr(self): 

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

435 ''' 

436 return self 

437 

438 @Property_RO 

439 def tyr3d(self): 

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

441 ''' 

442 return _ltp.Attitude(self).tyr3d 

443 

444 

445class Ned(_AbcBase): 

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

447 

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

449 ''' 

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

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

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

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

454 _toStr = _ned_ 

455 

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

457 '''New L{Ned} vector. 

458 

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

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

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

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

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

464 scalar B{C{north_ned}}. 

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

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

467 scalar B{C{north_ned}}. 

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

469 L{LocalCartesian}). 

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

471 

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

473 

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

475 ''' 

476 if _isMeter(north_ned): 

477 ned = None 

478 t = (Meter(north=north_ned or _0_0), 

479 Meter(east=east or _0_0), 

480 Meter(down=down or _0_0), ltp) 

481 else: # PYCHOK no cover 

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

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

484 t = ned.ned4 

485 self._north, self._east, self._down, _ = t 

486 _init(self, ned, ltp, name) 

487 

488 @Property_RO 

489 def aer4(self): 

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

491 ''' 

492 return _xyz2aer4(self) 

493 

494 @Property_RO 

495 def azimuth(self): 

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

497 ''' 

498 return self.aer4.azimuth 

499 

500 @deprecated_Property_RO 

501 def bearing(self): 

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

503 return self.azimuth 

504 

505 @Property_RO 

506 def down(self): 

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

508 ''' 

509 return self._down 

510 

511 @Property_RO 

512 def east(self): 

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

514 ''' 

515 return self._east 

516 

517 @Property_RO 

518 def elevation(self): 

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

520 ''' 

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

522 

523 @Property_RO 

524 def groundrange(self): 

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

526 ''' 

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

528 

529 @deprecated_Property_RO 

530 def length(self): 

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

532 return self.slantrange 

533 

534 @deprecated_Property_RO 

535 def ned(self): 

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

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

538 

539 @Property_RO 

540 def ned4(self): 

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

542 ''' 

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

544 

545 @Property_RO 

546 def north(self): 

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

548 ''' 

549 return self._north 

550 

551 @Property_RO 

552 def slantrange(self): 

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

554 ''' 

555 return self.aer4.slantrange 

556 

557 @deprecated_method 

558 def to3ned(self): # PYCHOK no cover 

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

560 return self.ned # XXX deprecated too 

561 

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

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

564 

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

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

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

568 

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

570 ''' 

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

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

573 

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

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

576 

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

578 number of (decimal) digits, unstripped 

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

580 backets format (C{str}) and separator 

581 C{B{sep}=", "} to join (C{str}). 

582 

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

584 ''' 

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

586 return t 

587 

588 @deprecated_method 

589 def toVector3d(self): 

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

591 return self.xyz 

592 

593 @Property_RO 

594 def up(self): 

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

596 ''' 

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

598 

599 @Property_RO 

600 def x(self): 

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

602 ''' 

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

604 

605 @Property_RO 

606 def xyz4(self): 

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

608 ''' 

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

610 

611 @Property_RO 

612 def y(self): 

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

614 ''' 

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

616 

617 @Property_RO 

618 def z(self): 

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

620 ''' 

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

622 

623 

624class Ned4Tuple(_Abc4Tuple): 

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

626 ''' 

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

628 _Units_ = ( Meter, Meter, Meter, _Pass) 

629 

630 def _toNed(self, Cls, Cls_kwds): 

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

632 ''' 

633 return self._2Cls(Ned, Cls, Cls_kwds) 

634 

635 @Property_RO 

636 def xyzLocal(self): 

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

638 ''' 

639 return Ned(self).xyzLocal 

640 

641 

642class _Vector3d(Vector3d): 

643 

644 _toStr = _xyz_ 

645 

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

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

648 

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

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

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

652 

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

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

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

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

657 ''' 

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

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

660 

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

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

663 

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

665 number of (decimal) digits, unstripped 

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

667 backets format (C{str}) and separator 

668 C{B{sep}=", "} to join (C{str}). 

669 

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

671 ''' 

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

673 return t 

674 

675 

676class XyzLocal(_Vector3d): 

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

678 also base class for local L{Enu}. 

679 ''' 

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

681 

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

683 '''New L{XyzLocal}. 

684 

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

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

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

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

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

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

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

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

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

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

695 L{LocalCartesian}). 

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

697 

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

699 

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

701 ''' 

702 if _isMeter(x_xyz): 

703 xyz = None 

704 t = (Meter(x=x_xyz or _0_0), 

705 Meter(y=y or _0_0), 

706 Meter(z=z or _0_0), ltp) 

707 else: 

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

709 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp 

710 self._x, self._y, self._z, _ = t 

711 _init(self, xyz, ltp, name) 

712 

713 def __str__(self): 

714 return self.toStr() 

715 

716 @Property_RO 

717 def aer4(self): 

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

719 ''' 

720 return _xyz2aer4(self) 

721 

722 @Property_RO 

723 def azimuth(self): 

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

725 

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

727 Transformations_between_ECEF_and_ENU_coordinates>}. 

728 ''' 

729 return self.aer4.azimuth 

730 

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

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

733 

734 @arg args: Optional, positional arguments. 

735 @kwarg kwds: Optional, keyword arguments. 

736 

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

738 ''' 

739 kwds = _name1__(kwds, _or_nameof=self) 

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

741 

742 @Property_RO 

743 def down(self): 

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

745 ''' 

746 return Meter(down=-self.z) 

747 

748 @property_RO 

749 def ecef(self): 

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

751 ''' 

752 return self.ltp.ecef 

753 

754 @Property_RO 

755 def east(self): 

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

757 ''' 

758 return Meter(east=self.x) 

759 

760 @Property_RO 

761 def elevation(self): 

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

763 

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

765 Transformations_between_ECEF_and_ENU_coordinates>}. 

766 ''' 

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

768 

769 @Property_RO 

770 def enu4(self): 

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

772 ''' 

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

774 

775 @Property_RO 

776 def groundrange(self): 

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

778 ''' 

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

780 

781 @Property_RO 

782 def ltp(self): 

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

784 ''' 

785 return self._ltp 

786 

787 def _ltp_kwds_name3(self, ltp, kwds): 

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

789 ''' 

790 ltp = _ltp._xLtp(ltp, self.ltp) 

791 kwds = _name1__(kwds, _or_nameof=self) 

792 kwds = _name1__(kwds, _or_nameof=ltp) 

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

794 

795 @Property_RO 

796 def ned4(self): 

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

798 ''' 

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

800 

801 @Property_RO 

802 def north(self): 

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

804 ''' 

805 return Meter(north=self.y) 

806 

807 @Property_RO 

808 def slantrange(self): 

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

810 ''' 

811 return self.aer4.slantrange 

812 

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

814 '''Get the local I{Azimuth, Elevation, slant Range} components. 

815 

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

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

818 additional B{C{Aer}} keyword arguments, ignored if C{B{Aer} 

819 is None}. 

820 

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

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

823 

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

825 ''' 

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

827 

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

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

830 

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

832 or C{None}. 

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

834 this C{ltp}. 

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

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

837 C{B{Cartesian} is None}. 

838 

839 @return: A B{C{Cartesian}} instance or if C{B{Cartesian} is None}, an 

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

841 C{M=None}, always. 

842 

843 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or B{C{name_Cartesian_kwds}}. 

844 ''' 

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

846 if Cartesian is None: 

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

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

849 else: 

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

851 xyz = ltp._local2ecef(self) # [:3] 

852 r = Cartesian(*xyz, **kwds) 

853 return r 

854 

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

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

857 

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

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

860 additional B{C{Enu}} keyword arguments, ignored if C{B{Enu} 

861 is None}. 

862 

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

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

865 

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

867 ''' 

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

869 

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

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

872 

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

874 C{None}. 

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

876 this ENU/NED/AER/XYZ's LTP. 

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

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

879 is None}. 

880 

881 @return: An B{C{LatLon}} instance or if C{B{LatLon} is None}, an 

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

883 C{M=None}, always. 

884 

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

886 ''' 

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

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

889 if LatLon is None: 

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

891 else: 

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

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

894 return r 

895 

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

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

898 

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

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

901 

902 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)} 

903 with C{ltp} this C{Ltp}, C{ecef} an L{Ecef9Tuple} and C{M} 

904 an L{EcefMatrix} or C{None}. 

905 ''' 

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

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

908 return Local9Tuple(self.x, self.y, self.z, 

909 t.lat, t.lon, t.height, 

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

911 

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

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

914 

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

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

917 additional B{C{Ned}} keyword arguments, ignored if C{B{Ned} 

918 is None}. 

919 

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

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

922 

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

924 ''' 

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

926 

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

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

929 

930 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer}) 

931 or C{None}. 

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

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

934 is None}. 

935 

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

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

938 

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

940 ''' 

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

942 

943 @Property_RO 

944 def up(self): 

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

946 ''' 

947 return Meter(up=self.z) 

948 

949# @Property_RO 

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

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

952# ''' 

953# return self._x 

954 

955# @Property_RO 

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

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

958# ''' 

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

960 

961 @Property_RO 

962 def xyz4(self): 

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

964 ''' 

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

966 

967 @Property_RO 

968 def xyzLocal(self): 

969 '''Get this L{XyzLocal}. 

970 ''' 

971 return self 

972 

973# @Property_RO 

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

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

976# ''' 

977# return self._y 

978 

979# @Property_RO 

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

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

982# ''' 

983# return self._z 

984 

985 

986class Xyz4Tuple(_Abc4Tuple): 

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

988 ''' 

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

990 _Units_ = ( Meter, Meter, Meter, _Pass) 

991 

992 def _toXyz(self, Cls, Cls_kwds): 

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

994 ''' 

995 return self._2Cls(XyzLocal, Cls, Cls_kwds) 

996 

997 @property_RO 

998 def xyz4(self): 

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

1000 ''' 

1001 return self 

1002 

1003 @Property_RO 

1004 def xyzLocal(self): 

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

1006 ''' 

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

1008 

1009 

1010class Enu(XyzLocal): 

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

1012 

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

1014 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

1015 ''' 

1016 _toStr = _enu_ 

1017 

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

1019 '''New L{Enu}. 

1020 

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

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

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

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

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

1026 scalar B{C{east_enu}}. 

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

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

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

1030 L{LocalCartesian}). 

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

1032 

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

1034 

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

1036 ''' 

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

1038 

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

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

1041 

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

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

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

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

1046 additional B{L{Uvw}} keyword arguments, ignored if C{B{Uvw} 

1047 is None}. 

1048 

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

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

1051 

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

1053 

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

1055 ''' 

1056 try: 

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

1058 except Exception as x: 

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

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

1061 

1062 t = fdot_(ca, u, -sa, n) 

1063 U = fdot_(cb, t, -sb, e) 

1064 V = fdot_(cb, e, sb, t) 

1065 W = fdot_(ca, n, sa, u) 

1066 

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

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

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

1070 

1071 @Property_RO 

1072 def xyzLocal(self): 

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

1074 ''' 

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

1076 

1077 

1078class Enu4Tuple(_Abc4Tuple): 

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

1080 ''' 

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

1082 _Units_ = ( Meter, Meter, Meter, _Pass) 

1083 

1084 def _toEnu(self, Cls, Cls_kwds): 

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

1086 ''' 

1087 return self._2Cls(Enu, Cls, Cls_kwds) 

1088 

1089 @Property_RO 

1090 def xyzLocal(self): 

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

1092 ''' 

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

1094 

1095 

1096class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1102 ''' 

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

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

1105 

1106 @Property_RO 

1107 def azimuth(self): 

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

1109 ''' 

1110 return self.xyzLocal.aer4.azimuth 

1111 

1112 @Property_RO 

1113 def down(self): 

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

1115 ''' 

1116 return -self.z 

1117 

1118 @Property_RO 

1119 def east(self): 

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

1121 ''' 

1122 return self.x 

1123 

1124 @Property_RO 

1125 def elevation(self): 

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

1127 ''' 

1128 return self.xyzLocal.aer4.elevation 

1129 

1130 @Property_RO 

1131 def groundrange(self): 

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

1133 ''' 

1134 return self.xyzLocal.aer4.groundrange 

1135 

1136 @Property_RO 

1137 def lam(self): 

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

1139 ''' 

1140 return self.philam.lam 

1141 

1142 @Property_RO 

1143 def latlon(self): 

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

1145 ''' 

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

1147 

1148 @Property_RO 

1149 def latlonheight(self): 

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

1151 ''' 

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

1153 

1154 @Property_RO 

1155 def north(self): 

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

1157 ''' 

1158 return self.y 

1159 

1160 @Property_RO 

1161 def phi(self): 

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

1163 ''' 

1164 return self.philam.phi 

1165 

1166 @Property_RO 

1167 def philam(self): 

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

1169 ''' 

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

1171 

1172 @Property_RO 

1173 def philamheight(self): 

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

1175 ''' 

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

1177 

1178 @Property_RO 

1179 def slantrange(self): 

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

1181 ''' 

1182 return self.xyzLocal.aer4.slantrange 

1183 

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

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

1186 

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

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

1189 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer} 

1190 is None}. 

1191 

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

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

1194 

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

1196 ''' 

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

1198 

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

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

1201 

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

1203 or C{None}. 

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

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

1206 C{B{Cartesian} is None}. 

1207 

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

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

1210 

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

1212 ''' 

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

1214 

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

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

1217 

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

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

1220 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

1221 is None}. 

1222 

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

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

1225 

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

1227 ''' 

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

1229 

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

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

1232 

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

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

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

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

1237 is None}. 

1238 

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

1240 C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat, lon, height)} 

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

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

1243 

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

1245 ''' 

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

1247 

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

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

1250 

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

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

1253 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned} 

1254 is None}. 

1255 

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

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

1258 

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

1260 ''' 

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

1262 

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

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

1265 

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

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

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

1269 is None}. 

1270 

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

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

1273 

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

1275 ''' 

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

1277 

1278 @Property_RO 

1279 def up(self): 

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

1281 ''' 

1282 return self.z 

1283 

1284 @Property_RO 

1285 def xyz(self): 

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

1287 ''' 

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

1289 

1290 @Property_RO 

1291 def xyzLocal(self): 

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

1293 ''' 

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

1295 

1296 

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

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

1299 

1300 

1301class Uvw(_Vector3d): 

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

1303 ''' 

1304 _toStr = _uvw_ 

1305 

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

1307 '''New L{Uvw}. 

1308 

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

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

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

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

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

1314 

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

1316 

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

1318 ''' 

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

1320 

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

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

1323 

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

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

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

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

1328 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

1329 is None}. 

1330 

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

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

1333 

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

1335 

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

1337 ''' 

1338 try: 

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

1340 except Exception as x: 

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

1342 u, v, w = self.uvw 

1343 

1344 t = fdot_(cb, u, sb, v) 

1345 E = fdot_(cb, v, -sb, u) 

1346 N = fdot_(ca, w, -sa, t) 

1347 U = fdot_(ca, t, sa, w) 

1348 

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

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

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

1352 

1353 u = Vector3d.x 

1354 

1355 @Property_RO 

1356 def uvw(self): 

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

1358 ''' 

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

1360 

1361 v = Vector3d.y 

1362 w = Vector3d.z 

1363 

1364 

1365class Uvw3Tuple(_NamedTuple): 

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

1367 ''' 

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

1369 _Units_ = ( Meter, Meter, Meter) 

1370 

1371 

1372class Los(Aer): 

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

1374 ''' 

1375 

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

1377 '''New L{Los}. 

1378 

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

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

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

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

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

1384 B{C{azimuth_aer}}. 

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

1386 

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

1388 

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

1390 ''' 

1391 t = Aer(azimuth_aer, elevation) 

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

1393 

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

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

1396 

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

1398 L{Vector3d}) location from where to cast this LOS. 

1399 

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

1401 ''' 

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

1403 

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

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

1406 

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

1408 ''' 

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

1410 

1411 

1412class ChLV9Tuple(Local9Tuple): 

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

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

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

1416 otherwise like L{Local9Tuple}. 

1417 ''' 

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

1419 

1420 @Property_RO 

1421 def E_LV95(self): 

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

1423 ''' 

1424 return self.EN2_LV95.E_LV95 

1425 

1426 @Property_RO 

1427 def EN2_LV95(self): 

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

1429 ''' 

1430 return ChLVEN2Tuple(*_ltp.ChLV.false2(self.Y, self.X, True), name=self.name) 

1431 

1432 @Property_RO 

1433 def h_LV03(self): 

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

1435 ''' 

1436 return self.h_ 

1437 

1438 @Property_RO 

1439 def h_LV95(self): 

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

1441 ''' 

1442 return self.h_ 

1443 

1444 @property_RO 

1445 def isChLV(self): 

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

1447 ''' 

1448 return self.ltp.__class__ is _ltp.ChLV 

1449 

1450 @property_RO 

1451 def isChLVa(self): 

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

1453 ''' 

1454 return self.ltp.__class__ is _ltp.ChLVa 

1455 

1456 @property_RO 

1457 def isChLVe(self): 

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

1459 ''' 

1460 return self.ltp.__class__ is _ltp.ChLVe 

1461 

1462 @Property_RO 

1463 def N_LV95(self): 

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

1465 ''' 

1466 return self.EN2_LV95.N_LV95 

1467 

1468 @Property_RO 

1469 def x(self): 

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

1471 ''' 

1472 return self.Y 

1473 

1474 @Property_RO 

1475 def x_LV03(self): 

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

1477 ''' 

1478 return self.yx2_LV03.x_LV03 

1479 

1480 @Property_RO 

1481 def y(self): 

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

1483 ''' 

1484 return self.X 

1485 

1486 @Property_RO 

1487 def y_LV03(self): 

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

1489 ''' 

1490 return self.yx2_LV03.y_LV03 

1491 

1492 @Property_RO 

1493 def YX(self): 

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

1495 ''' 

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

1497 

1498 @Property_RO 

1499 def yx2_LV03(self): 

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

1501 ''' 

1502 return ChLVyx2Tuple(*_ltp.ChLV.false2(self.Y, self.X, False), name=self.name) 

1503 

1504 @Property_RO 

1505 def z(self): 

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

1507 ''' 

1508 return self.h_ 

1509 

1510 

1511class ChLVYX2Tuple(_NamedTuple): 

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

1513 in C{meter}. 

1514 ''' 

1515 _Names_ = (_Y_, _X_) 

1516 _Units_ = ( Meter, Meter) 

1517 

1518 def false2(self, LV95=True): 

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

1520 

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

1522 ''' 

1523 return _ltp.ChLV.false2(*self, LV95=LV95, name=self.name) 

1524 

1525 

1526class ChLVEN2Tuple(_NamedTuple): 

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

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

1529 ''' 

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

1531 _Units_ = ChLVYX2Tuple._Units_ 

1532 

1533 def unfalse2(self): 

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

1535 

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

1537 ''' 

1538 return _ltp.ChLV.unfalse2(*self, LV95=True, name=self.name) 

1539 

1540 

1541class ChLVyx2Tuple(_NamedTuple): 

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

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

1544 ''' 

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

1546 _Units_ = ChLVYX2Tuple._Units_ 

1547 

1548 def unfalse2(self): 

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

1550 

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

1552 ''' 

1553 return _ltp.ChLV.unfalse2(*self, LV95=False, name=self.name) 

1554 

1555 

1556class Footprint5Tuple(_NamedTuple): 

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

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

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

1560 

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

1562 ''' 

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

1564 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1565 

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

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

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

1569 

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

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

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

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

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

1575 is None}. 

1576 

1577 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, **B{name_LatLon_kwds})} 

1578 instances or if C{B{LatLon} is None}, 5 L{LatLon3Tuple}C{(lat, lon, 

1579 height)}s respectively 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s 

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

1581 

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

1583 

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

1585 ''' 

1586 ltp = _ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center 

1587 kwds = _name1__(name_LatLon_kwds, _or_nameof=self) 

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

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

1590 

1591 def xyzLocal5(self, ltp=None): 

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

1593 

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

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

1596 

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

1598 

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

1600 ''' 

1601 if ltp is None: 

1602 p = self 

1603 else: 

1604 p = _ltp._xLtp(ltp) 

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

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

1607 

1608 

1609__all__ += _ALL_DOCS(_AbcBase) 

1610 

1611# **) MIT License 

1612# 

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

1614# 

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

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

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

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

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

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

1621# 

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

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

1624# 

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

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

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

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

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

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

1631# OTHER DEALINGS IN THE SOFTWARE.