Coverage for pygeodesy/ltpTuples.py: 94%

555 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-04-04 14:33 -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 _down_, _east_, _ecef_, _elevation_, _height_, \ 

22 _lat_, _lon_, _ltp_, _M_, _north_, _not_, _up_, \ 

23 _X_, _x_, _xyz_, _Y_, _y_, _z_ 

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

25from pygeodesy.named import _NamedBase, _NamedTuple, notOverloaded, \ 

26 _Pass, _xnamed 

27from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple 

28from pygeodesy.props import deprecated_method, deprecated_Property_RO, \ 

29 Property_RO, property_RO 

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

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

32 _isMeter, Lat, Lon, Meter, Meter_, issubclassof 

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

34from pygeodesy.vector3d import Vector3d 

35 

36from math import cos, radians 

37 

38__all__ = _ALL_LAZY.ltpTuples 

39__version__ = '24.03.15' 

40 

41_aer_ = 'aer' 

42_alt_ = 'alt' 

43_enu_ = 'enu' 

44_h__ = 'h_' 

45_ned_ = 'ned' 

46_local_ = 'local' 

47_roll_ = 'roll' 

48_slantrange_ = 'slantrange' 

49_tilt_ = 'tilt' 

50_uvw_ = 'uvw' 

51_yaw_ = 'yaw' 

52 

53 

54def _er2gr(e, r): 

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

56 ''' 

57 c = cos(radians(e)) 

58 return Meter_(groundrange=r * c) 

59 

60 

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

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

63 ''' 

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

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

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

67 if sep: 

68 t = sep.join(t) 

69 if fmt: 

70 t = fmt(t) 

71 return a, t 

72 

73 

74def _4Tuple2Cls(inst, Cls, Cls_kwds): 

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

76 ''' 

77 if Cls is None: 

78 return inst 

79 elif issubclassof(Cls, Aer): 

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

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

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

83 elif issubclassof(Cls, Ned): 

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

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

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

87 elif Cls is Local9Tuple: # PYCHOK no cover 

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

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

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

91 

92 

93def _xyz2aer4(inst): 

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

95 ''' 

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

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

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

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

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

101 

102 

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

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

105 ''' 

106 n, inst = _xkwds_item2(name_inst) 

107 if isinstance(inst, Types): 

108 return None 

109 try: 

110 return inst.xyzLocal 

111 except (AttributeError, TypeError): 

112 raise _TypeError(n, inst, txt=_not_(_local_)) 

113 

114 

115class _NamedAerNed(_NamedBase): 

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

117 ''' 

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

119 

120 @Property_RO 

121 def ltp(self): 

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

123 ''' 

124 return self._ltp 

125 

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

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

128 

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

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

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

132 

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

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

135 ''' 

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

137 

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

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

140 

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

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

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

144 

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

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

147 ''' 

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

149 

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

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

152 

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

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

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

156 

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

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

159 ''' 

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

161 

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

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

164 

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

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

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

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

169 

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

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

172 

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

174 ''' 

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

176 

177 @Property_RO 

178 def xyz(self): 

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

180 ''' 

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

182 

183 @property_RO 

184 def xyz4(self): # PYCHOK no cover 

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

186 notOverloaded(self) 

187 

188 @Property_RO 

189 def xyzLocal(self): 

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

191 ''' 

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

193 

194 

195class Aer(_NamedAerNed): 

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

197 ''' 

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

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

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

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

202 _toStr = _aer_ 

203 

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

205 '''New L{Aer}. 

206 

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

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

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

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

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

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

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

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

215 B{C{azimuth_aer}}. 

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

217 L{LocalCartesian}). 

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

219 

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

221 

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

223 or B{C{slantrange}}. 

224 ''' 

225 if _isDegrees(azimuth_aer): 

226 self._azimuth = Bearing(azimuth=azimuth_aer) 

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

228 self._slantrange = Meter_(slantrange=slantrange) 

229 p, n = ltp, name 

230 else: # PYCHOK no cover 

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

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

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

234 aer.azimuth, aer.elevation, aer.slantrange 

235 p = _xattr(aer, ltp=ltp) 

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

237 

238 if p: 

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

240 if name: 

241 self.name = n 

242 

243 @Property_RO 

244 def aer4(self): 

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

246 ''' 

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

248 

249 @Property_RO 

250 def azimuth(self): 

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

252 ''' 

253 return self._azimuth 

254 

255 @Property_RO 

256 def down(self): 

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

258 ''' 

259 return self.xyzLocal.down 

260 

261 @Property_RO 

262 def east(self): 

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

264 ''' 

265 return self.xyzLocal.east 

266 

267 @Property_RO 

268 def elevation(self): 

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

270 ''' 

271 return self._elevation 

272 

273 @Property_RO 

274 def groundrange(self): 

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

276 ''' 

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

278 

279 @Property_RO 

280 def north(self): 

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

282 ''' 

283 return self.xyzLocal.north 

284 

285 @Property_RO 

286 def slantrange(self): 

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

288 ''' 

289 return self._slantrange 

290 

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

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

293 (bearing), elevation and slant range. 

294 

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

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

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

298 

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

300 ''' 

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

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

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

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

305 

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

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

308 

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

310 number of (decimal) digits, unstripped 

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

312 backets format (C{str}) and separator 

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

314 

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

316 ''' 

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

318 return t 

319 

320 @Property_RO 

321 def up(self): 

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

323 ''' 

324 return self.xyzLocal.up 

325 

326 @Property_RO 

327 def x(self): 

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

329 ''' 

330 return self.xyz4.x 

331 

332 @Property_RO 

333 def xyz4(self): 

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

335 ''' 

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

337 R = self._slantrange 

338 r = cE * R # ground range 

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

340 

341 @Property_RO 

342 def y(self): 

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

344 ''' 

345 return self.xyz4.y 

346 

347 @Property_RO 

348 def z(self): 

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

350 ''' 

351 return self.xyz4.z 

352 

353 

354class Aer4Tuple(_NamedTuple): 

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

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

357 ''' 

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

359 _Units_ = ( Meter, Meter, Meter, _Pass) 

360 

361 def _toAer(self, Cls, Cls_kwds): 

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

363 ''' 

364 if issubclassof(Cls, Aer): 

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

366 else: 

367 return _4Tuple2Cls(self, Cls, Cls_kwds) 

368 

369 @Property_RO 

370 def groundrange(self): 

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

372 ''' 

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

374 

375 @Property_RO 

376 def xyzLocal(self): 

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

378 ''' 

379 return Aer(self).xyzLocal 

380 

381 

382class Attitude4Tuple(_NamedTuple): 

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

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

385 the attitude of a plane or camera. 

386 ''' 

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

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

389 

390 @Property_RO 

391 def atyr(self): 

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

393 ''' 

394 return self 

395 

396 @Property_RO 

397 def tyr3d(self): 

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

399 ''' 

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

401 

402 

403class Ned(_NamedAerNed): 

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

405 

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

407 ''' 

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

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

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

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

412 _toStr = _ned_ 

413 

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

415 '''New L{Ned} vector. 

416 

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

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

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

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

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

422 scalar B{C{north_ned}}. 

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

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

425 scalar B{C{north_ned}}. 

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

427 L{LocalCartesian}). 

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

429 

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

431 

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

433 ''' 

434 if _isMeter(north_ned): 

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

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

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

438 p, n = ltp, name 

439 else: # PYCHOK no cover 

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

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

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

443 p = _xattr(ned, ltp=ltp) 

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

445 

446 if p: 

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

448 if n: 

449 self.name = n 

450 

451 @Property_RO 

452 def aer4(self): 

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

454 ''' 

455 return _xyz2aer4(self) 

456 

457 @Property_RO 

458 def azimuth(self): 

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

460 ''' 

461 return self.aer4.azimuth 

462 

463 @deprecated_Property_RO 

464 def bearing(self): 

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

466 return self.azimuth 

467 

468 @Property_RO 

469 def down(self): 

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

471 ''' 

472 return self._down 

473 

474 @Property_RO 

475 def east(self): 

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

477 ''' 

478 return self._east 

479 

480 @Property_RO 

481 def elevation(self): 

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

483 ''' 

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

485 

486 @Property_RO 

487 def groundrange(self): 

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

489 ''' 

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

491 

492 @deprecated_Property_RO 

493 def length(self): 

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

495 return self.slantrange 

496 

497 @deprecated_Property_RO 

498 def ned(self): 

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

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

501 

502 @Property_RO 

503 def ned4(self): 

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

505 ''' 

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

507 

508 @Property_RO 

509 def north(self): 

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

511 ''' 

512 return self._north 

513 

514 @Property_RO 

515 def slantrange(self): 

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

517 ''' 

518 return self.aer4.slantrange 

519 

520 @deprecated_method 

521 def to3ned(self): # PYCHOK no cover 

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

523 return self.ned # XXX deprecated too 

524 

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

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

527 

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

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

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

531 

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

533 ''' 

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

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

536 

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

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

539 

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

541 number of (decimal) digits, unstripped 

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

543 backets format (C{str}) and separator 

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

545 

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

547 ''' 

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

549 return t 

550 

551 @deprecated_method 

552 def toVector3d(self): 

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

554 return self.xyz 

555 

556 @Property_RO 

557 def up(self): 

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

559 ''' 

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

561 

562 @Property_RO 

563 def x(self): 

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

565 ''' 

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

567 

568 @Property_RO 

569 def xyz4(self): 

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

571 ''' 

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

573 

574 @Property_RO 

575 def y(self): 

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

577 ''' 

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

579 

580 @Property_RO 

581 def z(self): 

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

583 ''' 

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

585 

586 

587class Ned4Tuple(_NamedTuple): 

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

589 ''' 

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

591 _Units_ = ( Meter, Meter, Meter, _Pass) 

592 

593 def _toNed(self, Cls, Cls_kwds): 

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

595 ''' 

596 if issubclassof(Cls, Ned): 

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

598 else: 

599 return _4Tuple2Cls(self, Cls, Cls_kwds) 

600 

601 @Property_RO 

602 def xyzLocal(self): 

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

604 ''' 

605 return Ned(self).xyzLocal 

606 

607 

608class _Vector3d(Vector3d): 

609 

610 _toStr = _xyz_ 

611 

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

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

614 

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

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

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

618 

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

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

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

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

623 ''' 

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

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

626 

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

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

629 

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

631 number of (decimal) digits, unstripped 

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

633 backets format (C{str}) and separator 

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

635 

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

637 ''' 

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

639 return t 

640 

641 

642class XyzLocal(_Vector3d): 

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

644 also base class for local L{Enu}. 

645 ''' 

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

647 

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

649 '''New L{XyzLocal}. 

650 

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

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

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

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

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

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

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

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

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

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

661 L{LocalCartesian}). 

662 

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

664 

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

666 ''' 

667 if _isMeter(x_xyz): 

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

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

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

671 p, n = ltp, name 

672 else: 

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

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

675 p = _xattr(xyz, ltp=ltp) 

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

677 

678 if p: 

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

680 if n: 

681 self.name = n 

682 

683 def __str__(self): 

684 return self.toStr() 

685 

686 @Property_RO 

687 def aer4(self): 

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

689 ''' 

690 return _xyz2aer4(self) 

691 

692 @Property_RO 

693 def azimuth(self): 

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

695 

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

697 Transformations_between_ECEF_and_ENU_coordinates>}. 

698 ''' 

699 return self.aer4.azimuth 

700 

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

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

703 

704 @arg args: Optional, positional arguments. 

705 @kwarg kwds: Optional, keyword arguments. 

706 

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

708 ''' 

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

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

711 

712 @Property_RO 

713 def down(self): 

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

715 ''' 

716 return Meter(down=-self.z) 

717 

718 @property_RO 

719 def ecef(self): 

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

721 ''' 

722 return self.ltp.ecef 

723 

724 @Property_RO 

725 def east(self): 

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

727 ''' 

728 return Meter(east=self.x) 

729 

730 @Property_RO 

731 def elevation(self): 

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

733 

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

735 Transformations_between_ECEF_and_ENU_coordinates>}. 

736 ''' 

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

738 

739 @Property_RO 

740 def enu4(self): 

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

742 ''' 

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

744 

745 @Property_RO 

746 def groundrange(self): 

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

748 ''' 

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

750 

751 @Property_RO 

752 def ltp(self): 

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

754 ''' 

755 return self._ltp 

756 

757 @Property_RO 

758 def ned4(self): 

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

760 ''' 

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

762 

763 @Property_RO 

764 def north(self): 

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

766 ''' 

767 return Meter(north=self.y) 

768 

769 @Property_RO 

770 def slantrange(self): 

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

772 ''' 

773 return self.aer4.slantrange 

774 

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

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

777 

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

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

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

781 

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

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

784 

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

786 ''' 

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

788 

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

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

791 

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

793 or C{None}. 

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

795 overriding this C{ltp}. 

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

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

798 

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

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

801 with C{M=None}, always. 

802 

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

804 B{C{Cartesian_kwds}} argument. 

805 ''' 

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

807 if Cartesian is None: 

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

809 else: 

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

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

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

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

814 

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

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

817 

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

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

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

821 

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

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

824 ''' 

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

826 

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

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

829 

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

831 or C{None}. 

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

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

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

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

836 

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

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

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

840 

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

842 B{C{LatLon_kwds}} argument. 

843 ''' 

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

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

846 if LatLon is None: 

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

848 else: 

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

850 name=self.name or ltp.name) 

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

852 return r 

853 

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

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

856 

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

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

859 

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

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

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

863 ''' 

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

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

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

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

868 

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

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

871 

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

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

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

875 

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

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

878 ''' 

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

880 

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

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

883 

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

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

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

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

888 

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

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

891 ''' 

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

893 

894 @Property_RO 

895 def up(self): 

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

897 ''' 

898 return Meter(up=self.z) 

899 

900# @Property_RO 

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

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

903# ''' 

904# return self._x 

905 

906# @Property_RO 

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

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

909# ''' 

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

911 

912 @Property_RO 

913 def xyz4(self): 

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

915 ''' 

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

917 

918 @Property_RO 

919 def xyzLocal(self): 

920 '''Get this L{XyzLocal}. 

921 ''' 

922 return self 

923 

924# @Property_RO 

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

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

927# ''' 

928# return self._y 

929 

930# @Property_RO 

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

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

933# ''' 

934# return self._z 

935 

936 

937class Xyz4Tuple(_NamedTuple): 

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

939 ''' 

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

941 _Units_ = ( Meter, Meter, Meter, _Pass) 

942 

943 def _toXyz(self, Cls, Cls_kwds): 

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

945 ''' 

946 if issubclassof(Cls, XyzLocal): 

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

948 else: 

949 return _4Tuple2Cls(self, Cls, Cls_kwds) 

950 

951 @Property_RO 

952 def xyzLocal(self): 

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

954 ''' 

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

956 

957 

958class Enu(XyzLocal): 

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

960 

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

962 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

963 ''' 

964 _toStr = _enu_ 

965 

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

967 '''New L{Enu}. 

968 

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

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

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

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

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

974 scalar B{C{east_enu}}. 

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

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

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

978 L{LocalCartesian}). 

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

980 

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

982 

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

984 ''' 

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

986 

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

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

989 

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

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

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

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

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

995 

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

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

998 

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

1000 

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

1002 ''' 

1003 try: 

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

1005 except Exception as x: 

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

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

1008 

1009 t = ca * u - sa * n 

1010 U = cb * t - sb * e 

1011 V = cb * e + sb * t 

1012 W = ca * n + sa * u 

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

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

1015 

1016 @Property_RO 

1017 def xyzLocal(self): 

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

1019 ''' 

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

1021 

1022 

1023class Enu4Tuple(_NamedTuple): 

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

1025 ''' 

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

1027 _Units_ = ( Meter, Meter, Meter, _Pass) 

1028 

1029 def _toEnu(self, Cls, Cls_kwds): 

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

1031 ''' 

1032 if issubclassof(Cls, XyzLocal): 

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

1034 else: 

1035 return _4Tuple2Cls(self, Cls, Cls_kwds) 

1036 

1037 @Property_RO 

1038 def xyzLocal(self): 

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

1040 ''' 

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

1042 

1043 

1044class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1050 ''' 

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

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

1053 

1054 @Property_RO 

1055 def azimuth(self): 

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

1057 ''' 

1058 return self.xyzLocal.aer4.azimuth 

1059 

1060 @Property_RO 

1061 def down(self): 

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

1063 ''' 

1064 return -self.z 

1065 

1066 @Property_RO 

1067 def east(self): 

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

1069 ''' 

1070 return self.x 

1071 

1072 @Property_RO 

1073 def elevation(self): 

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

1075 ''' 

1076 return self.xyzLocal.aer4.elevation 

1077 

1078 @Property_RO 

1079 def groundrange(self): 

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

1081 ''' 

1082 return self.xyzLocal.aer4.groundrange 

1083 

1084 @Property_RO 

1085 def lam(self): 

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

1087 ''' 

1088 return self.philam.lam 

1089 

1090 @Property_RO 

1091 def latlon(self): 

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

1093 ''' 

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

1095 

1096 @Property_RO 

1097 def latlonheight(self): 

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

1099 ''' 

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

1101 

1102 @Property_RO 

1103 def north(self): 

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

1105 ''' 

1106 return self.y 

1107 

1108 @Property_RO 

1109 def phi(self): 

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

1111 ''' 

1112 return self.philam.phi 

1113 

1114 @Property_RO 

1115 def philam(self): 

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

1117 ''' 

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

1119 

1120 @Property_RO 

1121 def philamheight(self): 

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

1123 ''' 

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

1125 

1126 @Property_RO 

1127 def slantrange(self): 

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

1129 ''' 

1130 return self.xyzLocal.aer4.slantrange 

1131 

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

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

1134 

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

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

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

1138 

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

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

1141 ''' 

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

1143 

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

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

1146 

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

1148 or C{None}. 

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

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

1151 

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

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

1154 

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

1156 argument. 

1157 ''' 

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

1159 

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

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

1162 

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

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

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

1166 

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

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

1169 ''' 

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

1171 

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

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

1174 

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

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

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

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

1179 

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

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

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

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

1184 

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

1186 argument. 

1187 ''' 

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

1189 

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

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

1192 

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

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

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

1196 

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

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

1199 ''' 

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

1201 

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

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

1204 

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

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

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

1208 

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

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

1211 ''' 

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

1213 

1214 @Property_RO 

1215 def up(self): 

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

1217 ''' 

1218 return self.z 

1219 

1220 @Property_RO 

1221 def xyz(self): 

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

1223 ''' 

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

1225 

1226 @Property_RO 

1227 def xyzLocal(self): 

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

1229 ''' 

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

1231 

1232 

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

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

1235 

1236 

1237class Uvw(_Vector3d): 

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

1239 ''' 

1240 _toStr = _uvw_ 

1241 

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

1243 '''New L{Uvw}. 

1244 

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

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

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

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

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

1250 

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

1252 

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

1254 ''' 

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

1256 

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

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

1259 

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

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

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

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

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

1265 

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

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

1268 

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

1270 

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

1272 ''' 

1273 try: 

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

1275 except Exception as x: 

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

1277 u, v, w = self.uvw 

1278 

1279 t = cb * u + sb * v 

1280 E = cb * v - sb * u 

1281 N = ca * w - sa * t 

1282 U = ca * t + sa * w 

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

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

1285 

1286 u = Vector3d.x 

1287 

1288 @Property_RO 

1289 def uvw(self): 

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

1291 ''' 

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

1293 

1294 v = Vector3d.y 

1295 w = Vector3d.z 

1296 

1297 

1298class Uvw3Tuple(_NamedTuple): 

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

1300 ''' 

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

1302 _Units_ = ( Meter, Meter, Meter) 

1303 

1304 

1305class Los(Aer): 

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

1307 ''' 

1308 

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

1310 '''New L{Los}. 

1311 

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

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

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

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

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

1317 B{C{azimuth_aer}}. 

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

1319 

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

1321 

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

1323 ''' 

1324 t = Aer(azimuth_aer, elevation) 

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

1326 

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

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

1329 

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

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

1332 

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

1334 ''' 

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

1336 

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

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

1339 

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

1341 ''' 

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

1343 

1344 

1345class ChLV9Tuple(Local9Tuple): 

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

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

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

1349 otherwise like L{Local9Tuple}. 

1350 ''' 

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

1352 

1353 @Property_RO 

1354 def E_LV95(self): 

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

1356 ''' 

1357 return self.EN2_LV95.E_LV95 

1358 

1359 @Property_RO 

1360 def EN2_LV95(self): 

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

1362 ''' 

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

1364 

1365 @Property_RO 

1366 def h_LV03(self): 

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

1368 ''' 

1369 return self.h_ 

1370 

1371 @Property_RO 

1372 def h_LV95(self): 

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

1374 ''' 

1375 return self.h_ 

1376 

1377 @property_RO 

1378 def isChLV(self): 

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

1380 ''' 

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

1382 

1383 @property_RO 

1384 def isChLVa(self): 

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

1386 ''' 

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

1388 

1389 @property_RO 

1390 def isChLVe(self): 

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

1392 ''' 

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

1394 

1395 @Property_RO 

1396 def N_LV95(self): 

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

1398 ''' 

1399 return self.EN2_LV95.N_LV95 

1400 

1401 @Property_RO 

1402 def x(self): 

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

1404 ''' 

1405 return self.Y 

1406 

1407 @Property_RO 

1408 def x_LV03(self): 

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

1410 ''' 

1411 return self.yx2_LV03.x_LV03 

1412 

1413 @Property_RO 

1414 def y(self): 

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

1416 ''' 

1417 return self.X 

1418 

1419 @Property_RO 

1420 def y_LV03(self): 

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

1422 ''' 

1423 return self.yx2_LV03.y_LV03 

1424 

1425 @Property_RO 

1426 def YX(self): 

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

1428 ''' 

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

1430 

1431 @Property_RO 

1432 def yx2_LV03(self): 

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

1434 ''' 

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

1436 

1437 @Property_RO 

1438 def z(self): 

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

1440 ''' 

1441 return self.h_ 

1442 

1443 

1444class ChLVYX2Tuple(_NamedTuple): 

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

1446 in C{meter}. 

1447 ''' 

1448 _Names_ = (_Y_, _X_) 

1449 _Units_ = ( Meter, Meter) 

1450 

1451 def false2(self, LV95=True): 

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

1453 

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

1455 ''' 

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

1457 

1458 

1459class ChLVEN2Tuple(_NamedTuple): 

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

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

1462 ''' 

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

1464 _Units_ = ChLVYX2Tuple._Units_ 

1465 

1466 def unfalse2(self): 

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

1468 

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

1470 ''' 

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

1472 

1473 

1474class ChLVyx2Tuple(_NamedTuple): 

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

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

1477 ''' 

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

1479 _Units_ = ChLVYX2Tuple._Units_ 

1480 

1481 def unfalse2(self): 

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

1483 

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

1485 ''' 

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

1487 

1488 

1489class Footprint5Tuple(_NamedTuple): 

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

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

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

1493 

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

1495 ''' 

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

1497 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1498 

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

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

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

1502 

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

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

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

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

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

1508 

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

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

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

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

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

1514 

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

1516 

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

1518 ''' 

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

1520 LatLon=LatLon, name=self.name,) 

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

1522 

1523 def xyzLocal5(self, ltp=None): 

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

1525 

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

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

1528 

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

1530 

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

1532 ''' 

1533 if ltp is None: 

1534 p = self 

1535 else: 

1536 p = _MODS.ltp._xLtp(ltp) 

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

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

1539 

1540 

1541__all__ += _ALL_DOCS(_NamedAerNed) 

1542 

1543# **) MIT License 

1544# 

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

1546# 

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

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

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

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

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

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

1553# 

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

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

1556# 

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

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

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

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

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

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

1563# OTHER DEALINGS IN THE SOFTWARE.