Coverage for pygeodesy/ltpTuples.py: 94%

558 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-25 12:04 -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_, _up_, _X_, _x_, _xyz_, _Y_, _y_, _z_ 

23from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS 

24from pygeodesy.named import _NamedBase, _NamedTuple, _Pass, _xnamed 

25from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple 

26from pygeodesy.props import deprecated_method, deprecated_Property_RO, \ 

27 Property_RO, property_RO 

28from pygeodesy.streprs import Fmt, fstr, strs, _xzipairs 

29from pygeodesy.units import Bearing, Degrees, Degrees_, Height, _isDegrees, \ 

30 _isMeter, Lat, Lon, Meter, Meter_, issubclassof 

31from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_ 

32from pygeodesy.vector3d import Vector3d 

33 

34from math import cos, radians 

35 

36__all__ = _ALL_LAZY.ltpTuples 

37__version__ = '24.05.24' 

38 

39_aer_ = 'aer' 

40_alt_ = 'alt' 

41_down_ = 'down' 

42_east_ = 'east' 

43_enu_ = 'enu' 

44_h__ = 'h_' 

45_ned_ = 'ned' 

46_north_ = 'north' 

47_local_ = 'local' 

48_roll_ = 'roll' 

49_slantrange_ = 'slantrange' 

50_tilt_ = 'tilt' 

51_uvw_ = 'uvw' 

52_yaw_ = 'yaw' 

53 

54 

55def _er2gr(e, r): 

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

57 ''' 

58 c = cos(radians(e)) 

59 return Meter_(groundrange=r * c) 

60 

61 

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

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

64 ''' 

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

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

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

68 if sep: 

69 t = sep.join(t) 

70 if fmt: 

71 t = fmt(t) 

72 return a, t 

73 

74 

75def _4Tuple2Cls(inst, Cls, Cls_kwds): 

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

77 ''' 

78 if Cls is None: 

79 return inst 

80 elif issubclassof(Cls, Aer): 

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

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

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

84 elif issubclassof(Cls, Ned): 

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

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

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

88 elif Cls is Local9Tuple: # PYCHOK no cover 

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

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

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

92 

93 

94def _xyz2aer4(inst): 

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

96 ''' 

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

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

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

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

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

102 

103 

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

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

106 ''' 

107 n, inst = _xkwds_item2(name_inst) 

108 if isinstance(inst, Types): 

109 return None 

110 try: 

111 return inst.xyzLocal 

112 except (AttributeError, TypeError): 

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

114 

115 

116class _NamedAerNed(_NamedBase): 

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

118 ''' 

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

120 

121 @Property_RO 

122 def ltp(self): 

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

124 ''' 

125 return self._ltp 

126 

127 def toAer(self, Aer=None, **Aer_kwds): 

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

129 

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

131 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword 

132 arguments, ignored if B{C{Aer}} is C{None}. 

133 

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

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

136 ''' 

137 return self.xyz4._toXyz(Aer, Aer_kwds) 

138 

139 def toEnu(self, Enu=None, **Enu_kwds): 

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

141 

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

143 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword 

144 arguments, ignored if C{B{Enu} is None}. 

145 

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

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

148 ''' 

149 return self.xyz4._toXyz(Enu, Enu_kwds) 

150 

151 def toNed(self, Ned=None, **Ned_kwds): 

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

153 

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

155 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword 

156 arguments, ignored if B{C{Ned}} is C{None}. 

157 

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

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

160 ''' 

161 return self.xyz4._toXyz(Ned, Ned_kwds) 

162 

163 def toXyz(self, Xyz=None, **Xyz_kwds): 

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

165 

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

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

168 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword 

169 arguments, ignored if C{B{Xyz} is None}. 

170 

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

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

173 

174 @raise TypeError: Invalid B{C{Xyz}}. 

175 ''' 

176 return self.xyz4._toXyz(Xyz, Xyz_kwds) 

177 

178 @Property_RO 

179 def xyz(self): 

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

181 ''' 

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

183 

184 @property_RO 

185 def xyz4(self): # PYCHOK no cover 

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

187 self._notOverloaded() 

188 

189 @Property_RO 

190 def xyzLocal(self): 

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

192 ''' 

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

194 

195 

196class Aer(_NamedAerNed): 

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

198 ''' 

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

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

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

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

203 _toStr = _aer_ 

204 

205 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, name=NN): 

206 '''New L{Aer}. 

207 

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

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

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

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

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

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

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

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

216 B{C{azimuth_aer}}. 

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

218 L{LocalCartesian}). 

219 @kwarg name: Optional name (C{str}). 

220 

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

222 

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

224 or B{C{slantrange}}. 

225 ''' 

226 if _isDegrees(azimuth_aer): 

227 self._azimuth = Bearing(azimuth=azimuth_aer) 

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

229 self._slantrange = Meter_(slantrange=slantrange) 

230 p, n = ltp, name 

231 else: # PYCHOK no cover 

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

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

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

235 aer.azimuth, aer.elevation, aer.slantrange 

236 p = _xattr(aer, ltp=ltp) 

237 n = name or _xattr(aer, name=name) 

238 

239 if p: 

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

241 if name: 

242 self.name = n 

243 

244 @Property_RO 

245 def aer4(self): 

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

247 ''' 

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

249 

250 @Property_RO 

251 def azimuth(self): 

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

253 ''' 

254 return self._azimuth 

255 

256 @Property_RO 

257 def down(self): 

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

259 ''' 

260 return self.xyzLocal.down 

261 

262 @Property_RO 

263 def east(self): 

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

265 ''' 

266 return self.xyzLocal.east 

267 

268 @Property_RO 

269 def elevation(self): 

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

271 ''' 

272 return self._elevation 

273 

274 @Property_RO 

275 def groundrange(self): 

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

277 ''' 

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

279 

280 @Property_RO 

281 def north(self): 

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

283 ''' 

284 return self.xyzLocal.north 

285 

286 @Property_RO 

287 def slantrange(self): 

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

289 ''' 

290 return self._slantrange 

291 

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

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

294 (bearing), elevation and slant range. 

295 

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

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

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

299 

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

301 ''' 

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

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

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

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

306 

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

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

309 

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

311 number of (decimal) digits, unstripped 

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

313 backets format (C{str}) and separator 

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

315 

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

317 ''' 

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

319 return t 

320 

321 @Property_RO 

322 def up(self): 

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

324 ''' 

325 return self.xyzLocal.up 

326 

327 @Property_RO 

328 def x(self): 

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

330 ''' 

331 return self.xyz4.x 

332 

333 @Property_RO 

334 def xyz4(self): 

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

336 ''' 

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

338 R = self._slantrange 

339 r = cE * R # ground range 

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

341 

342 @Property_RO 

343 def y(self): 

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

345 ''' 

346 return self.xyz4.y 

347 

348 @Property_RO 

349 def z(self): 

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

351 ''' 

352 return self.xyz4.z 

353 

354 

355class Aer4Tuple(_NamedTuple): 

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

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

358 ''' 

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

360 _Units_ = ( Meter, Meter, Meter, _Pass) 

361 

362 def _toAer(self, Cls, Cls_kwds): 

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

364 ''' 

365 if issubclassof(Cls, Aer): 

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

367 else: 

368 return _4Tuple2Cls(self, Cls, Cls_kwds) 

369 

370 @Property_RO 

371 def groundrange(self): 

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

373 ''' 

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

375 

376 @Property_RO 

377 def xyzLocal(self): 

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

379 ''' 

380 return Aer(self).xyzLocal 

381 

382 

383class Attitude4Tuple(_NamedTuple): 

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

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

386 the attitude of a plane or camera. 

387 ''' 

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

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

390 

391 @Property_RO 

392 def atyr(self): 

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

394 ''' 

395 return self 

396 

397 @Property_RO 

398 def tyr3d(self): 

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

400 ''' 

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

402 

403 

404class Ned(_NamedAerNed): 

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

406 

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

408 ''' 

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

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

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

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

413 _toStr = _ned_ 

414 

415 def __init__(self, north_ned, east=0, down=0, ltp=None, name=NN): 

416 '''New L{Ned} vector. 

417 

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

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

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

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

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

423 scalar B{C{north_ned}}. 

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

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

426 scalar B{C{north_ned}}. 

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

428 L{LocalCartesian}). 

429 @kwarg name: Optional name (C{str}). 

430 

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

432 

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

434 ''' 

435 if _isMeter(north_ned): 

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

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

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

439 p, n = ltp, name 

440 else: # PYCHOK no cover 

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

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

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

444 p = _xattr(ned, ltp=ltp) 

445 n = name or _xattr(ned, name=name) 

446 

447 if p: 

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

449 if n: 

450 self.name = n 

451 

452 @Property_RO 

453 def aer4(self): 

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

455 ''' 

456 return _xyz2aer4(self) 

457 

458 @Property_RO 

459 def azimuth(self): 

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

461 ''' 

462 return self.aer4.azimuth 

463 

464 @deprecated_Property_RO 

465 def bearing(self): 

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

467 return self.azimuth 

468 

469 @Property_RO 

470 def down(self): 

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

472 ''' 

473 return self._down 

474 

475 @Property_RO 

476 def east(self): 

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

478 ''' 

479 return self._east 

480 

481 @Property_RO 

482 def elevation(self): 

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

484 ''' 

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

486 

487 @Property_RO 

488 def groundrange(self): 

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

490 ''' 

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

492 

493 @deprecated_Property_RO 

494 def length(self): 

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

496 return self.slantrange 

497 

498 @deprecated_Property_RO 

499 def ned(self): 

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

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

502 

503 @Property_RO 

504 def ned4(self): 

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

506 ''' 

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

508 

509 @Property_RO 

510 def north(self): 

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

512 ''' 

513 return self._north 

514 

515 @Property_RO 

516 def slantrange(self): 

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

518 ''' 

519 return self.aer4.slantrange 

520 

521 @deprecated_method 

522 def to3ned(self): # PYCHOK no cover 

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

524 return self.ned # XXX deprecated too 

525 

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

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

528 

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

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

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

532 

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

534 ''' 

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

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

537 

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

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

540 

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

542 number of (decimal) digits, unstripped 

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

544 backets format (C{str}) and separator 

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

546 

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

548 ''' 

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

550 return t 

551 

552 @deprecated_method 

553 def toVector3d(self): 

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

555 return self.xyz 

556 

557 @Property_RO 

558 def up(self): 

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

560 ''' 

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

562 

563 @Property_RO 

564 def x(self): 

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

566 ''' 

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

568 

569 @Property_RO 

570 def xyz4(self): 

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

572 ''' 

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

574 

575 @Property_RO 

576 def y(self): 

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

578 ''' 

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

580 

581 @Property_RO 

582 def z(self): 

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

584 ''' 

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

586 

587 

588class Ned4Tuple(_NamedTuple): 

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

590 ''' 

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

592 _Units_ = ( Meter, Meter, Meter, _Pass) 

593 

594 def _toNed(self, Cls, Cls_kwds): 

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

596 ''' 

597 if issubclassof(Cls, Ned): 

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

599 else: 

600 return _4Tuple2Cls(self, Cls, Cls_kwds) 

601 

602 @Property_RO 

603 def xyzLocal(self): 

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

605 ''' 

606 return Ned(self).xyzLocal 

607 

608 

609class _Vector3d(Vector3d): 

610 

611 _toStr = _xyz_ 

612 

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

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

615 

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

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

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

619 

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

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

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

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

624 ''' 

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

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

627 

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

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

630 

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

632 number of (decimal) digits, unstripped 

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

634 backets format (C{str}) and separator 

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

636 

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

638 ''' 

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

640 return t 

641 

642 

643class XyzLocal(_Vector3d): 

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

645 also base class for local L{Enu}. 

646 ''' 

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

648 

649 def __init__(self, x_xyz, y=0, z=0, ltp=None, name=NN): 

650 '''New L{XyzLocal}. 

651 

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

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

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

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

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

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

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

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

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

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

662 L{LocalCartesian}). 

663 

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

665 

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

667 ''' 

668 if _isMeter(x_xyz): 

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

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

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

672 p, n = ltp, name 

673 else: 

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

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

676 p = _xattr(xyz, ltp=ltp) 

677 n = name or _xattr(xyz, name=NN) 

678 

679 if p: 

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

681 if n: 

682 self.name = n 

683 

684 def __str__(self): 

685 return self.toStr() 

686 

687 @Property_RO 

688 def aer4(self): 

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

690 ''' 

691 return _xyz2aer4(self) 

692 

693 @Property_RO 

694 def azimuth(self): 

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

696 

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

698 Transformations_between_ECEF_and_ENU_coordinates>}. 

699 ''' 

700 return self.aer4.azimuth 

701 

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

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

704 

705 @arg args: Optional, positional arguments. 

706 @kwarg kwds: Optional, keyword arguments. 

707 

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

709 ''' 

710 kwds = _xkwds(kwds, ltp=self.ltp, name=self.name) 

711 return self.__class__(*args, **kwds) 

712 

713 @Property_RO 

714 def down(self): 

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

716 ''' 

717 return Meter(down=-self.z) 

718 

719 @property_RO 

720 def ecef(self): 

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

722 ''' 

723 return self.ltp.ecef 

724 

725 @Property_RO 

726 def east(self): 

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

728 ''' 

729 return Meter(east=self.x) 

730 

731 @Property_RO 

732 def elevation(self): 

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

734 

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

736 Transformations_between_ECEF_and_ENU_coordinates>}. 

737 ''' 

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

739 

740 @Property_RO 

741 def enu4(self): 

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

743 ''' 

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

745 

746 @Property_RO 

747 def groundrange(self): 

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

749 ''' 

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

751 

752 @Property_RO 

753 def ltp(self): 

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

755 ''' 

756 return self._ltp 

757 

758 @Property_RO 

759 def ned4(self): 

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

761 ''' 

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

763 

764 @Property_RO 

765 def north(self): 

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

767 ''' 

768 return Meter(north=self.y) 

769 

770 @Property_RO 

771 def slantrange(self): 

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

773 ''' 

774 return self.aer4.slantrange 

775 

776 def toAer(self, Aer=None, **Aer_kwds): 

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

778 

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

780 @kwarg Aer_kwds: Optional, additional B{C{Aer}} keyword 

781 arguments, ignored if C{B{Aer} is None}. 

782 

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

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

785 

786 @raise TypeError: Invalid B{C{Aer}}. 

787 ''' 

788 return self.aer4._toAer(Aer, Aer_kwds) 

789 

790 def toCartesian(self, Cartesian=None, ltp=None, **Cartesian_kwds): 

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

792 

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

794 or C{None}. 

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

796 overriding this C{ltp}. 

797 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword 

798 arguments, ignored if C{B{Cartesian} is None}. 

799 

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

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

802 with C{M=None}, always. 

803 

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

805 B{C{Cartesian_kwds}} argument. 

806 ''' 

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

808 if Cartesian is None: 

809 r = ltp._local2ecef(self, nine=True) 

810 else: 

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

812 kwds = _xkwds(Cartesian_kwds, datum=ltp.datum) 

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

814 return _xnamed(r, self.name or ltp.name) 

815 

816 def toEnu(self, Enu=None, **Enu_kwds): 

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

818 

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

820 @kwarg Enu_kwds: Optional, additional B{C{Enu}} keyword 

821 arguments, ignored if C{B{Enu} is None}. 

822 

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

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

825 ''' 

826 return self.enu4._toEnu(Enu, Enu_kwds) 

827 

828 def toLatLon(self, LatLon=None, ltp=None, **LatLon_kwds): 

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

830 

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

832 or C{None}. 

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

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

835 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword 

836 arguments, ignored if C{B{LatLon} is None}. 

837 

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

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

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

841 

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

843 B{C{LatLon_kwds}} argument. 

844 ''' 

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

846 r = ltp._local2ecef(self, nine=True) 

847 if LatLon is None: 

848 r = _xnamed(r, self.name or ltp.name) 

849 else: 

850 kwds = _xkwds(LatLon_kwds, height=r.height, datum=r.datum, 

851 name=self.name or ltp.name) 

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

853 return r 

854 

855 def toLocal9Tuple(self, M=False, name=NN): 

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

857 

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

859 @kwarg name: Optional name (C{str}). 

860 

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

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

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

864 ''' 

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

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

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

868 ltp, t, t.M, name=name or t.name) 

869 

870 def toNed(self, Ned=None, **Ned_kwds): 

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

872 

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

874 @kwarg Ned_kwds: Optional, additional B{C{Ned}} keyword 

875 arguments, ignored if C{B{Ned} is None}. 

876 

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

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

879 ''' 

880 return self.ned4._toNed(Ned, Ned_kwds) 

881 

882 def toXyz(self, Xyz=None, **Xyz_kwds): 

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

884 

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

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

887 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword 

888 arguments, ignored if C{B{Xyz} is None}. 

889 

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

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

892 ''' 

893 return self.xyz4._toXyz(Xyz, Xyz_kwds) 

894 

895 @Property_RO 

896 def up(self): 

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

898 ''' 

899 return Meter(up=self.z) 

900 

901# @Property_RO 

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

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

904# ''' 

905# return self._x 

906 

907# @Property_RO 

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

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

910# ''' 

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

912 

913 @Property_RO 

914 def xyz4(self): 

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

916 ''' 

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

918 

919 @Property_RO 

920 def xyzLocal(self): 

921 '''Get this L{XyzLocal}. 

922 ''' 

923 return self 

924 

925# @Property_RO 

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

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

928# ''' 

929# return self._y 

930 

931# @Property_RO 

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

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

934# ''' 

935# return self._z 

936 

937 

938class Xyz4Tuple(_NamedTuple): 

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

940 ''' 

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

942 _Units_ = ( Meter, Meter, Meter, _Pass) 

943 

944 def _toXyz(self, Cls, Cls_kwds): 

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

946 ''' 

947 if issubclassof(Cls, XyzLocal): 

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

949 else: 

950 return _4Tuple2Cls(self, Cls, Cls_kwds) 

951 

952 @Property_RO 

953 def xyzLocal(self): 

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

955 ''' 

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

957 

958 

959class Enu(XyzLocal): 

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

961 

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

963 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

964 ''' 

965 _toStr = _enu_ 

966 

967 def __init__(self, east_enu, north=0, up=0, ltp=None, name=NN): 

968 '''New L{Enu}. 

969 

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

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

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

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

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

975 scalar B{C{east_enu}}. 

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

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

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

979 L{LocalCartesian}). 

980 @kwarg name: Optional name (C{str}). 

981 

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

983 

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

985 ''' 

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

987 

988 def toUvw(self, location, Uvw=None, **Uvw_kwds): 

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

990 

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

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

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

994 @kwarg Uvw_kwds: Optional, additional B{L{Uvw}} keyword 

995 arguments, ignored if C{B{Uvw} is None}. 

996 

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

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

999 

1000 @raise TypeError: InvalidB{C{location}}. 

1001 

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

1003 ''' 

1004 try: 

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

1006 except Exception as x: 

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

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

1009 

1010 t = ca * u - sa * n 

1011 U = cb * t - sb * e 

1012 V = cb * e + sb * t 

1013 W = ca * n + sa * u 

1014 return Uvw3Tuple(U, V, W, name=self.name) if Uvw is None else \ 

1015 Uvw( U, V, W, **_xkwds(Uvw_kwds, name=self.name)) 

1016 

1017 @Property_RO 

1018 def xyzLocal(self): 

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

1020 ''' 

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

1022 

1023 

1024class Enu4Tuple(_NamedTuple): 

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

1026 ''' 

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

1028 _Units_ = ( Meter, Meter, Meter, _Pass) 

1029 

1030 def _toEnu(self, Cls, Cls_kwds): 

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

1032 ''' 

1033 if issubclassof(Cls, XyzLocal): 

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

1035 else: 

1036 return _4Tuple2Cls(self, Cls, Cls_kwds) 

1037 

1038 @Property_RO 

1039 def xyzLocal(self): 

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

1041 ''' 

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

1043 

1044 

1045class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1051 ''' 

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

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

1054 

1055 @Property_RO 

1056 def azimuth(self): 

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

1058 ''' 

1059 return self.xyzLocal.aer4.azimuth 

1060 

1061 @Property_RO 

1062 def down(self): 

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

1064 ''' 

1065 return -self.z 

1066 

1067 @Property_RO 

1068 def east(self): 

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

1070 ''' 

1071 return self.x 

1072 

1073 @Property_RO 

1074 def elevation(self): 

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

1076 ''' 

1077 return self.xyzLocal.aer4.elevation 

1078 

1079 @Property_RO 

1080 def groundrange(self): 

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

1082 ''' 

1083 return self.xyzLocal.aer4.groundrange 

1084 

1085 @Property_RO 

1086 def lam(self): 

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

1088 ''' 

1089 return self.philam.lam 

1090 

1091 @Property_RO 

1092 def latlon(self): 

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

1094 ''' 

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

1096 

1097 @Property_RO 

1098 def latlonheight(self): 

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

1100 ''' 

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

1102 

1103 @Property_RO 

1104 def north(self): 

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

1106 ''' 

1107 return self.y 

1108 

1109 @Property_RO 

1110 def phi(self): 

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

1112 ''' 

1113 return self.philam.phi 

1114 

1115 @Property_RO 

1116 def philam(self): 

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

1118 ''' 

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

1120 

1121 @Property_RO 

1122 def philamheight(self): 

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

1124 ''' 

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

1126 

1127 @Property_RO 

1128 def slantrange(self): 

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

1130 ''' 

1131 return self.xyzLocal.aer4.slantrange 

1132 

1133 def toAer(self, Aer=None, **Aer_kwds): 

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

1135 

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

1137 @kwarg Aer_kwds: Optional, additional B{L{Aer}} keyword 

1138 arguments, ignored if B{C{Aer}} is C{None}. 

1139 

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

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

1142 ''' 

1143 return self.xyzLocal.toAer(Aer=Aer, **Aer_kwds) 

1144 

1145 def toCartesian(self, Cartesian=None, **Cartesian_kwds): 

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

1147 

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

1149 or C{None}. 

1150 @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword 

1151 arguments, ignored if C{B{Cartesian} is None}. 

1152 

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

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

1155 

1156 @raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}} 

1157 argument. 

1158 ''' 

1159 return self.ecef.toCartesian(Cartesian=Cartesian, **Cartesian_kwds) # PYCHOK _Tuple 

1160 

1161 def toEnu(self, Enu=None, **Enu_kwds): 

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

1163 

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

1165 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword 

1166 arguments, ignored if C{B{Enu} is None}. 

1167 

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

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

1170 ''' 

1171 return self.xyzLocal.toEnu(Enu=Enu, **Enu_kwds) 

1172 

1173 def toLatLon(self, LatLon=None, **LatLon_kwds): 

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

1175 

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

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

1178 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword 

1179 arguments, ignored if C{B{LatLon} is None}. 

1180 

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

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

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

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

1185 

1186 @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}} 

1187 argument. 

1188 ''' 

1189 return self.ecef.toLatLon(LatLon=LatLon, **LatLon_kwds) # PYCHOK _Tuple 

1190 

1191 def toNed(self, Ned=None, **Ned_kwds): 

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

1193 

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

1195 @kwarg Ned_kwds: Optional, additional B{L{Ned}} keyword 

1196 arguments, ignored if B{C{Ned}} is C{None}. 

1197 

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

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

1200 ''' 

1201 return self.xyzLocal.toNed(Ned=Ned, **Ned_kwds) 

1202 

1203 def toXyz(self, Xyz=None, **Xyz_kwds): 

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

1205 

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

1207 @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword 

1208 arguments, ignored if C{B{Xyz} is None}. 

1209 

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

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

1212 ''' 

1213 return self.xyzLocal.toXyz(Xyz=Xyz, **Xyz_kwds) 

1214 

1215 @Property_RO 

1216 def up(self): 

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

1218 ''' 

1219 return self.z 

1220 

1221 @Property_RO 

1222 def xyz(self): 

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

1224 ''' 

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

1226 

1227 @Property_RO 

1228 def xyzLocal(self): 

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

1230 ''' 

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

1232 

1233 

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

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

1236 

1237 

1238class Uvw(_Vector3d): 

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

1240 ''' 

1241 _toStr = _uvw_ 

1242 

1243 def __init__(self, u_uvw, v=0, w=0, name=NN): 

1244 '''New L{Uvw}. 

1245 

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

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

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

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

1250 @kwarg name: Optional name (C{str}). 

1251 

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

1253 

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

1255 ''' 

1256 Vector3d.__init__(self, u_uvw, v, w, name=name) 

1257 

1258 def toEnu(self, location, Enu=Enu, **Enu_kwds): 

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

1260 

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

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

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

1264 @kwarg Enu_kwds: Optional, additional B{L{Enu}} keyword 

1265 arguments, ignored if C{B{Enu} is None}. 

1266 

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

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

1269 

1270 @raise TypeError: InvalidB{C{location}}. 

1271 

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

1273 ''' 

1274 try: 

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

1276 except Exception as x: 

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

1278 u, v, w = self.uvw 

1279 

1280 t = cb * u + sb * v 

1281 E = cb * v - sb * u 

1282 N = ca * w - sa * t 

1283 U = ca * t + sa * w 

1284 return Enu4Tuple(E, N, U, name=self.name) if Enu is None else \ 

1285 Enu( E, N, U, **_xkwds(Enu_kwds, name=self.name)) 

1286 

1287 u = Vector3d.x 

1288 

1289 @Property_RO 

1290 def uvw(self): 

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

1292 ''' 

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

1294 

1295 v = Vector3d.y 

1296 w = Vector3d.z 

1297 

1298 

1299class Uvw3Tuple(_NamedTuple): 

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

1301 ''' 

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

1303 _Units_ = ( Meter, Meter, Meter) 

1304 

1305 

1306class Los(Aer): 

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

1308 ''' 

1309 

1310 def __init__(self, azimuth_aer, elevation=0, name=NN): 

1311 '''New L{Los}. 

1312 

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

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

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

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

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

1318 B{C{azimuth_aer}}. 

1319 @kwarg name: Optional name (C{str}). 

1320 

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

1322 

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

1324 ''' 

1325 t = Aer(azimuth_aer, elevation) 

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

1327 

1328 def toUvw(self, location, Uvw=Uvw, **Uvw_kwds): 

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

1330 

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

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

1333 

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

1335 ''' 

1336 return self.toEnu().toUvw(location, Uvw=Uvw, **Uvw_kwds) 

1337 

1338 def toEnu(self, Enu=Enu, **Enu_kwds): 

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

1340 

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

1342 ''' 

1343 return Aer.toEnu(self, Enu=Enu, **Enu_kwds) 

1344 

1345 

1346class ChLV9Tuple(Local9Tuple): 

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

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

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

1350 otherwise like L{Local9Tuple}. 

1351 ''' 

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

1353 

1354 @Property_RO 

1355 def E_LV95(self): 

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

1357 ''' 

1358 return self.EN2_LV95.E_LV95 

1359 

1360 @Property_RO 

1361 def EN2_LV95(self): 

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

1363 ''' 

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

1365 

1366 @Property_RO 

1367 def h_LV03(self): 

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

1369 ''' 

1370 return self.h_ 

1371 

1372 @Property_RO 

1373 def h_LV95(self): 

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

1375 ''' 

1376 return self.h_ 

1377 

1378 @property_RO 

1379 def isChLV(self): 

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

1381 ''' 

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

1383 

1384 @property_RO 

1385 def isChLVa(self): 

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

1387 ''' 

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

1389 

1390 @property_RO 

1391 def isChLVe(self): 

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

1393 ''' 

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

1395 

1396 @Property_RO 

1397 def N_LV95(self): 

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

1399 ''' 

1400 return self.EN2_LV95.N_LV95 

1401 

1402 @Property_RO 

1403 def x(self): 

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

1405 ''' 

1406 return self.Y 

1407 

1408 @Property_RO 

1409 def x_LV03(self): 

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

1411 ''' 

1412 return self.yx2_LV03.x_LV03 

1413 

1414 @Property_RO 

1415 def y(self): 

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

1417 ''' 

1418 return self.X 

1419 

1420 @Property_RO 

1421 def y_LV03(self): 

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

1423 ''' 

1424 return self.yx2_LV03.y_LV03 

1425 

1426 @Property_RO 

1427 def YX(self): 

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

1429 ''' 

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

1431 

1432 @Property_RO 

1433 def yx2_LV03(self): 

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

1435 ''' 

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

1437 

1438 @Property_RO 

1439 def z(self): 

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

1441 ''' 

1442 return self.h_ 

1443 

1444 

1445class ChLVYX2Tuple(_NamedTuple): 

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

1447 in C{meter}. 

1448 ''' 

1449 _Names_ = (_Y_, _X_) 

1450 _Units_ = ( Meter, Meter) 

1451 

1452 def false2(self, LV95=True): 

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

1454 

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

1456 ''' 

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

1458 

1459 

1460class ChLVEN2Tuple(_NamedTuple): 

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

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

1463 ''' 

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

1465 _Units_ = ChLVYX2Tuple._Units_ 

1466 

1467 def unfalse2(self): 

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

1469 

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

1471 ''' 

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

1473 

1474 

1475class ChLVyx2Tuple(_NamedTuple): 

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

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

1478 ''' 

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

1480 _Units_ = ChLVYX2Tuple._Units_ 

1481 

1482 def unfalse2(self): 

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

1484 

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

1486 ''' 

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

1488 

1489 

1490class Footprint5Tuple(_NamedTuple): 

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

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

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

1494 

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

1496 ''' 

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

1498 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1499 

1500 def toLatLon5(self, ltp=None, LatLon=None, **LatLon_kwds): 

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

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

1503 

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

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

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

1507 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword 

1508 arguments, ignored if C{B{LatLon} is None}. 

1509 

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

1511 **B{LatLon_kwds})} instances or if C{B{LatLon} is None}, 

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

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

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

1515 

1516 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{LatLon_kwds}}. 

1517 

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

1519 ''' 

1520 kwds = _xkwds(LatLon_kwds, ltp=_MODS.ltp._xLtp(ltp, self.center.ltp), # PYCHOK .center 

1521 LatLon=LatLon, name=self.name,) 

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

1523 

1524 def xyzLocal5(self, ltp=None): 

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

1526 

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

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

1529 

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

1531 

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

1533 ''' 

1534 if ltp is None: 

1535 p = self 

1536 else: 

1537 p = _MODS.ltp._xLtp(ltp) 

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

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

1540 

1541 

1542__all__ += _ALL_DOCS(_NamedAerNed) 

1543 

1544# **) MIT License 

1545# 

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

1547# 

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

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

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

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

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

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

1554# 

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

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

1557# 

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

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

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

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

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

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

1564# OTHER DEALINGS IN THE SOFTWARE.