Coverage for pygeodesy/ltpTuples.py: 94%

555 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-15 16:36 -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, _Pass, _xnamed 

26from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple 

27from pygeodesy.props import deprecated_method, deprecated_Property_RO, \ 

28 Property_RO, property_RO 

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

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

31 _isMeter, Lat, Lon, Meter, Meter_, issubclassof 

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

33from pygeodesy.vector3d import Vector3d 

34 

35from math import cos, radians 

36 

37__all__ = _ALL_LAZY.ltpTuples 

38__version__ = '24.04.07' 

39 

40_aer_ = 'aer' 

41_alt_ = 'alt' 

42_enu_ = 'enu' 

43_h__ = 'h_' 

44_ned_ = 'ned' 

45_local_ = 'local' 

46_roll_ = 'roll' 

47_slantrange_ = 'slantrange' 

48_tilt_ = 'tilt' 

49_uvw_ = 'uvw' 

50_yaw_ = 'yaw' 

51 

52 

53def _er2gr(e, r): 

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

55 ''' 

56 c = cos(radians(e)) 

57 return Meter_(groundrange=r * c) 

58 

59 

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

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

62 ''' 

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

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

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

66 if sep: 

67 t = sep.join(t) 

68 if fmt: 

69 t = fmt(t) 

70 return a, t 

71 

72 

73def _4Tuple2Cls(inst, Cls, Cls_kwds): 

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

75 ''' 

76 if Cls is None: 

77 return inst 

78 elif issubclassof(Cls, Aer): 

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

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

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

82 elif issubclassof(Cls, Ned): 

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

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

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

86 elif Cls is Local9Tuple: # PYCHOK no cover 

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

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

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

90 

91 

92def _xyz2aer4(inst): 

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

94 ''' 

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

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

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

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

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

100 

101 

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

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

104 ''' 

105 n, inst = _xkwds_item2(name_inst) 

106 if isinstance(inst, Types): 

107 return None 

108 try: 

109 return inst.xyzLocal 

110 except (AttributeError, TypeError): 

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

112 

113 

114class _NamedAerNed(_NamedBase): 

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

116 ''' 

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

118 

119 @Property_RO 

120 def ltp(self): 

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

122 ''' 

123 return self._ltp 

124 

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

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

127 

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

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

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

131 

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

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

134 ''' 

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

136 

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

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

139 

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

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

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

143 

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

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

146 ''' 

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

148 

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

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

151 

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

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

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

155 

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

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

158 ''' 

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

160 

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

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

163 

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

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

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

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

168 

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

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

171 

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

173 ''' 

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

175 

176 @Property_RO 

177 def xyz(self): 

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

179 ''' 

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

181 

182 @property_RO 

183 def xyz4(self): # PYCHOK no cover 

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

185 self._notOverloaded() 

186 

187 @Property_RO 

188 def xyzLocal(self): 

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

190 ''' 

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

192 

193 

194class Aer(_NamedAerNed): 

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

196 ''' 

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

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

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

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

201 _toStr = _aer_ 

202 

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

204 '''New L{Aer}. 

205 

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

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

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

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

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

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

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

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

214 B{C{azimuth_aer}}. 

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

216 L{LocalCartesian}). 

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

218 

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

220 

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

222 or B{C{slantrange}}. 

223 ''' 

224 if _isDegrees(azimuth_aer): 

225 self._azimuth = Bearing(azimuth=azimuth_aer) 

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

227 self._slantrange = Meter_(slantrange=slantrange) 

228 p, n = ltp, name 

229 else: # PYCHOK no cover 

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

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

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

233 aer.azimuth, aer.elevation, aer.slantrange 

234 p = _xattr(aer, ltp=ltp) 

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

236 

237 if p: 

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

239 if name: 

240 self.name = n 

241 

242 @Property_RO 

243 def aer4(self): 

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

245 ''' 

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

247 

248 @Property_RO 

249 def azimuth(self): 

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

251 ''' 

252 return self._azimuth 

253 

254 @Property_RO 

255 def down(self): 

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

257 ''' 

258 return self.xyzLocal.down 

259 

260 @Property_RO 

261 def east(self): 

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

263 ''' 

264 return self.xyzLocal.east 

265 

266 @Property_RO 

267 def elevation(self): 

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

269 ''' 

270 return self._elevation 

271 

272 @Property_RO 

273 def groundrange(self): 

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

275 ''' 

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

277 

278 @Property_RO 

279 def north(self): 

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

281 ''' 

282 return self.xyzLocal.north 

283 

284 @Property_RO 

285 def slantrange(self): 

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

287 ''' 

288 return self._slantrange 

289 

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

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

292 (bearing), elevation and slant range. 

293 

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

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

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

297 

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

299 ''' 

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

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

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

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

304 

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

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

307 

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

309 number of (decimal) digits, unstripped 

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

311 backets format (C{str}) and separator 

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

313 

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

315 ''' 

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

317 return t 

318 

319 @Property_RO 

320 def up(self): 

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

322 ''' 

323 return self.xyzLocal.up 

324 

325 @Property_RO 

326 def x(self): 

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

328 ''' 

329 return self.xyz4.x 

330 

331 @Property_RO 

332 def xyz4(self): 

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

334 ''' 

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

336 R = self._slantrange 

337 r = cE * R # ground range 

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

339 

340 @Property_RO 

341 def y(self): 

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

343 ''' 

344 return self.xyz4.y 

345 

346 @Property_RO 

347 def z(self): 

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

349 ''' 

350 return self.xyz4.z 

351 

352 

353class Aer4Tuple(_NamedTuple): 

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

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

356 ''' 

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

358 _Units_ = ( Meter, Meter, Meter, _Pass) 

359 

360 def _toAer(self, Cls, Cls_kwds): 

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

362 ''' 

363 if issubclassof(Cls, Aer): 

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

365 else: 

366 return _4Tuple2Cls(self, Cls, Cls_kwds) 

367 

368 @Property_RO 

369 def groundrange(self): 

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

371 ''' 

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

373 

374 @Property_RO 

375 def xyzLocal(self): 

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

377 ''' 

378 return Aer(self).xyzLocal 

379 

380 

381class Attitude4Tuple(_NamedTuple): 

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

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

384 the attitude of a plane or camera. 

385 ''' 

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

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

388 

389 @Property_RO 

390 def atyr(self): 

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

392 ''' 

393 return self 

394 

395 @Property_RO 

396 def tyr3d(self): 

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

398 ''' 

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

400 

401 

402class Ned(_NamedAerNed): 

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

404 

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

406 ''' 

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

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

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

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

411 _toStr = _ned_ 

412 

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

414 '''New L{Ned} vector. 

415 

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

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

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

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

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

421 scalar B{C{north_ned}}. 

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

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

424 scalar B{C{north_ned}}. 

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

426 L{LocalCartesian}). 

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

428 

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

430 

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

432 ''' 

433 if _isMeter(north_ned): 

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

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

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

437 p, n = ltp, name 

438 else: # PYCHOK no cover 

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

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

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

442 p = _xattr(ned, ltp=ltp) 

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

444 

445 if p: 

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

447 if n: 

448 self.name = n 

449 

450 @Property_RO 

451 def aer4(self): 

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

453 ''' 

454 return _xyz2aer4(self) 

455 

456 @Property_RO 

457 def azimuth(self): 

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

459 ''' 

460 return self.aer4.azimuth 

461 

462 @deprecated_Property_RO 

463 def bearing(self): 

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

465 return self.azimuth 

466 

467 @Property_RO 

468 def down(self): 

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

470 ''' 

471 return self._down 

472 

473 @Property_RO 

474 def east(self): 

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

476 ''' 

477 return self._east 

478 

479 @Property_RO 

480 def elevation(self): 

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

482 ''' 

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

484 

485 @Property_RO 

486 def groundrange(self): 

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

488 ''' 

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

490 

491 @deprecated_Property_RO 

492 def length(self): 

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

494 return self.slantrange 

495 

496 @deprecated_Property_RO 

497 def ned(self): 

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

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

500 

501 @Property_RO 

502 def ned4(self): 

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

504 ''' 

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

506 

507 @Property_RO 

508 def north(self): 

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

510 ''' 

511 return self._north 

512 

513 @Property_RO 

514 def slantrange(self): 

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

516 ''' 

517 return self.aer4.slantrange 

518 

519 @deprecated_method 

520 def to3ned(self): # PYCHOK no cover 

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

522 return self.ned # XXX deprecated too 

523 

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

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

526 

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

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

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

530 

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

532 ''' 

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

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

535 

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

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

538 

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

540 number of (decimal) digits, unstripped 

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

542 backets format (C{str}) and separator 

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

544 

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

546 ''' 

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

548 return t 

549 

550 @deprecated_method 

551 def toVector3d(self): 

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

553 return self.xyz 

554 

555 @Property_RO 

556 def up(self): 

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

558 ''' 

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

560 

561 @Property_RO 

562 def x(self): 

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

564 ''' 

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

566 

567 @Property_RO 

568 def xyz4(self): 

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

570 ''' 

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

572 

573 @Property_RO 

574 def y(self): 

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

576 ''' 

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

578 

579 @Property_RO 

580 def z(self): 

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

582 ''' 

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

584 

585 

586class Ned4Tuple(_NamedTuple): 

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

588 ''' 

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

590 _Units_ = ( Meter, Meter, Meter, _Pass) 

591 

592 def _toNed(self, Cls, Cls_kwds): 

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

594 ''' 

595 if issubclassof(Cls, Ned): 

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

597 else: 

598 return _4Tuple2Cls(self, Cls, Cls_kwds) 

599 

600 @Property_RO 

601 def xyzLocal(self): 

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

603 ''' 

604 return Ned(self).xyzLocal 

605 

606 

607class _Vector3d(Vector3d): 

608 

609 _toStr = _xyz_ 

610 

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

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

613 

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

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

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

617 

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

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

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

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

622 ''' 

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

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

625 

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

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

628 

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

630 number of (decimal) digits, unstripped 

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

632 backets format (C{str}) and separator 

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

634 

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

636 ''' 

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

638 return t 

639 

640 

641class XyzLocal(_Vector3d): 

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

643 also base class for local L{Enu}. 

644 ''' 

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

646 

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

648 '''New L{XyzLocal}. 

649 

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

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

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

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

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

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

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

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

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

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

660 L{LocalCartesian}). 

661 

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

663 

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

665 ''' 

666 if _isMeter(x_xyz): 

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

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

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

670 p, n = ltp, name 

671 else: 

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

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

674 p = _xattr(xyz, ltp=ltp) 

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

676 

677 if p: 

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

679 if n: 

680 self.name = n 

681 

682 def __str__(self): 

683 return self.toStr() 

684 

685 @Property_RO 

686 def aer4(self): 

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

688 ''' 

689 return _xyz2aer4(self) 

690 

691 @Property_RO 

692 def azimuth(self): 

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

694 

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

696 Transformations_between_ECEF_and_ENU_coordinates>}. 

697 ''' 

698 return self.aer4.azimuth 

699 

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

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

702 

703 @arg args: Optional, positional arguments. 

704 @kwarg kwds: Optional, keyword arguments. 

705 

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

707 ''' 

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

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

710 

711 @Property_RO 

712 def down(self): 

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

714 ''' 

715 return Meter(down=-self.z) 

716 

717 @property_RO 

718 def ecef(self): 

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

720 ''' 

721 return self.ltp.ecef 

722 

723 @Property_RO 

724 def east(self): 

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

726 ''' 

727 return Meter(east=self.x) 

728 

729 @Property_RO 

730 def elevation(self): 

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

732 

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

734 Transformations_between_ECEF_and_ENU_coordinates>}. 

735 ''' 

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

737 

738 @Property_RO 

739 def enu4(self): 

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

741 ''' 

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

743 

744 @Property_RO 

745 def groundrange(self): 

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

747 ''' 

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

749 

750 @Property_RO 

751 def ltp(self): 

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

753 ''' 

754 return self._ltp 

755 

756 @Property_RO 

757 def ned4(self): 

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

759 ''' 

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

761 

762 @Property_RO 

763 def north(self): 

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

765 ''' 

766 return Meter(north=self.y) 

767 

768 @Property_RO 

769 def slantrange(self): 

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

771 ''' 

772 return self.aer4.slantrange 

773 

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

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

776 

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

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

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

780 

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

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

783 

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

785 ''' 

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

787 

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

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

790 

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

792 or C{None}. 

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

794 overriding this C{ltp}. 

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

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

797 

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

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

800 with C{M=None}, always. 

801 

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

803 B{C{Cartesian_kwds}} argument. 

804 ''' 

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

806 if Cartesian is None: 

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

808 else: 

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

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

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

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

813 

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

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

816 

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

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

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

820 

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

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

823 ''' 

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

825 

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

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

828 

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

830 or C{None}. 

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

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

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

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

835 

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

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

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

839 

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

841 B{C{LatLon_kwds}} argument. 

842 ''' 

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

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

845 if LatLon is None: 

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

847 else: 

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

849 name=self.name or ltp.name) 

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

851 return r 

852 

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

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

855 

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

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

858 

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

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

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

862 ''' 

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

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

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

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

867 

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

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

870 

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

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

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

874 

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

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

877 ''' 

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

879 

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

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

882 

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

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

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

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

887 

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

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

890 ''' 

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

892 

893 @Property_RO 

894 def up(self): 

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

896 ''' 

897 return Meter(up=self.z) 

898 

899# @Property_RO 

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

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

902# ''' 

903# return self._x 

904 

905# @Property_RO 

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

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

908# ''' 

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

910 

911 @Property_RO 

912 def xyz4(self): 

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

914 ''' 

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

916 

917 @Property_RO 

918 def xyzLocal(self): 

919 '''Get this L{XyzLocal}. 

920 ''' 

921 return self 

922 

923# @Property_RO 

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

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

926# ''' 

927# return self._y 

928 

929# @Property_RO 

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

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

932# ''' 

933# return self._z 

934 

935 

936class Xyz4Tuple(_NamedTuple): 

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

938 ''' 

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

940 _Units_ = ( Meter, Meter, Meter, _Pass) 

941 

942 def _toXyz(self, Cls, Cls_kwds): 

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

944 ''' 

945 if issubclassof(Cls, XyzLocal): 

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

947 else: 

948 return _4Tuple2Cls(self, Cls, Cls_kwds) 

949 

950 @Property_RO 

951 def xyzLocal(self): 

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

953 ''' 

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

955 

956 

957class Enu(XyzLocal): 

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

959 

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

961 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

962 ''' 

963 _toStr = _enu_ 

964 

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

966 '''New L{Enu}. 

967 

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

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

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

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

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

973 scalar B{C{east_enu}}. 

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

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

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

977 L{LocalCartesian}). 

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

979 

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

981 

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

983 ''' 

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

985 

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

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

988 

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

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

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

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

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

994 

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

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

997 

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

999 

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

1001 ''' 

1002 try: 

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

1004 except Exception as x: 

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

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

1007 

1008 t = ca * u - sa * n 

1009 U = cb * t - sb * e 

1010 V = cb * e + sb * t 

1011 W = ca * n + sa * u 

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

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

1014 

1015 @Property_RO 

1016 def xyzLocal(self): 

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

1018 ''' 

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

1020 

1021 

1022class Enu4Tuple(_NamedTuple): 

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

1024 ''' 

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

1026 _Units_ = ( Meter, Meter, Meter, _Pass) 

1027 

1028 def _toEnu(self, Cls, Cls_kwds): 

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

1030 ''' 

1031 if issubclassof(Cls, XyzLocal): 

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

1033 else: 

1034 return _4Tuple2Cls(self, Cls, Cls_kwds) 

1035 

1036 @Property_RO 

1037 def xyzLocal(self): 

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

1039 ''' 

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

1041 

1042 

1043class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1049 ''' 

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

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

1052 

1053 @Property_RO 

1054 def azimuth(self): 

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

1056 ''' 

1057 return self.xyzLocal.aer4.azimuth 

1058 

1059 @Property_RO 

1060 def down(self): 

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

1062 ''' 

1063 return -self.z 

1064 

1065 @Property_RO 

1066 def east(self): 

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

1068 ''' 

1069 return self.x 

1070 

1071 @Property_RO 

1072 def elevation(self): 

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

1074 ''' 

1075 return self.xyzLocal.aer4.elevation 

1076 

1077 @Property_RO 

1078 def groundrange(self): 

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

1080 ''' 

1081 return self.xyzLocal.aer4.groundrange 

1082 

1083 @Property_RO 

1084 def lam(self): 

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

1086 ''' 

1087 return self.philam.lam 

1088 

1089 @Property_RO 

1090 def latlon(self): 

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

1092 ''' 

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

1094 

1095 @Property_RO 

1096 def latlonheight(self): 

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

1098 ''' 

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

1100 

1101 @Property_RO 

1102 def north(self): 

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

1104 ''' 

1105 return self.y 

1106 

1107 @Property_RO 

1108 def phi(self): 

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

1110 ''' 

1111 return self.philam.phi 

1112 

1113 @Property_RO 

1114 def philam(self): 

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

1116 ''' 

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

1118 

1119 @Property_RO 

1120 def philamheight(self): 

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

1122 ''' 

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

1124 

1125 @Property_RO 

1126 def slantrange(self): 

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

1128 ''' 

1129 return self.xyzLocal.aer4.slantrange 

1130 

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

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

1133 

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

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

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

1137 

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

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

1140 ''' 

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

1142 

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

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

1145 

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

1147 or C{None}. 

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

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

1150 

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

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

1153 

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

1155 argument. 

1156 ''' 

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

1158 

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

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

1161 

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

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

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

1165 

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

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

1168 ''' 

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

1170 

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

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

1173 

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

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

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

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

1178 

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

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

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

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

1183 

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

1185 argument. 

1186 ''' 

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

1188 

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

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

1191 

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

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

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

1195 

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

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

1198 ''' 

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

1200 

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

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

1203 

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

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

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

1207 

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

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

1210 ''' 

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

1212 

1213 @Property_RO 

1214 def up(self): 

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

1216 ''' 

1217 return self.z 

1218 

1219 @Property_RO 

1220 def xyz(self): 

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

1222 ''' 

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

1224 

1225 @Property_RO 

1226 def xyzLocal(self): 

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

1228 ''' 

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

1230 

1231 

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

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

1234 

1235 

1236class Uvw(_Vector3d): 

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

1238 ''' 

1239 _toStr = _uvw_ 

1240 

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

1242 '''New L{Uvw}. 

1243 

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

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

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

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

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

1249 

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

1251 

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

1253 ''' 

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

1255 

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

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

1258 

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

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

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

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

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

1264 

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

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

1267 

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

1269 

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

1271 ''' 

1272 try: 

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

1274 except Exception as x: 

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

1276 u, v, w = self.uvw 

1277 

1278 t = cb * u + sb * v 

1279 E = cb * v - sb * u 

1280 N = ca * w - sa * t 

1281 U = ca * t + sa * w 

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

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

1284 

1285 u = Vector3d.x 

1286 

1287 @Property_RO 

1288 def uvw(self): 

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

1290 ''' 

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

1292 

1293 v = Vector3d.y 

1294 w = Vector3d.z 

1295 

1296 

1297class Uvw3Tuple(_NamedTuple): 

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

1299 ''' 

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

1301 _Units_ = ( Meter, Meter, Meter) 

1302 

1303 

1304class Los(Aer): 

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

1306 ''' 

1307 

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

1309 '''New L{Los}. 

1310 

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

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

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

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

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

1316 B{C{azimuth_aer}}. 

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

1318 

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

1320 

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

1322 ''' 

1323 t = Aer(azimuth_aer, elevation) 

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

1325 

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

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

1328 

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

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

1331 

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

1333 ''' 

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

1335 

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

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

1338 

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

1340 ''' 

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

1342 

1343 

1344class ChLV9Tuple(Local9Tuple): 

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

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

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

1348 otherwise like L{Local9Tuple}. 

1349 ''' 

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

1351 

1352 @Property_RO 

1353 def E_LV95(self): 

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

1355 ''' 

1356 return self.EN2_LV95.E_LV95 

1357 

1358 @Property_RO 

1359 def EN2_LV95(self): 

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

1361 ''' 

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

1363 

1364 @Property_RO 

1365 def h_LV03(self): 

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

1367 ''' 

1368 return self.h_ 

1369 

1370 @Property_RO 

1371 def h_LV95(self): 

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

1373 ''' 

1374 return self.h_ 

1375 

1376 @property_RO 

1377 def isChLV(self): 

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

1379 ''' 

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

1381 

1382 @property_RO 

1383 def isChLVa(self): 

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

1385 ''' 

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

1387 

1388 @property_RO 

1389 def isChLVe(self): 

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

1391 ''' 

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

1393 

1394 @Property_RO 

1395 def N_LV95(self): 

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

1397 ''' 

1398 return self.EN2_LV95.N_LV95 

1399 

1400 @Property_RO 

1401 def x(self): 

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

1403 ''' 

1404 return self.Y 

1405 

1406 @Property_RO 

1407 def x_LV03(self): 

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

1409 ''' 

1410 return self.yx2_LV03.x_LV03 

1411 

1412 @Property_RO 

1413 def y(self): 

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

1415 ''' 

1416 return self.X 

1417 

1418 @Property_RO 

1419 def y_LV03(self): 

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

1421 ''' 

1422 return self.yx2_LV03.y_LV03 

1423 

1424 @Property_RO 

1425 def YX(self): 

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

1427 ''' 

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

1429 

1430 @Property_RO 

1431 def yx2_LV03(self): 

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

1433 ''' 

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

1435 

1436 @Property_RO 

1437 def z(self): 

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

1439 ''' 

1440 return self.h_ 

1441 

1442 

1443class ChLVYX2Tuple(_NamedTuple): 

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

1445 in C{meter}. 

1446 ''' 

1447 _Names_ = (_Y_, _X_) 

1448 _Units_ = ( Meter, Meter) 

1449 

1450 def false2(self, LV95=True): 

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

1452 

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

1454 ''' 

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

1456 

1457 

1458class ChLVEN2Tuple(_NamedTuple): 

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

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

1461 ''' 

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

1463 _Units_ = ChLVYX2Tuple._Units_ 

1464 

1465 def unfalse2(self): 

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

1467 

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

1469 ''' 

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

1471 

1472 

1473class ChLVyx2Tuple(_NamedTuple): 

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

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

1476 ''' 

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

1478 _Units_ = ChLVYX2Tuple._Units_ 

1479 

1480 def unfalse2(self): 

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

1482 

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

1484 ''' 

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

1486 

1487 

1488class Footprint5Tuple(_NamedTuple): 

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

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

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

1492 

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

1494 ''' 

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

1496 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1497 

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

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

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

1501 

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

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

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

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

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

1507 

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

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

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

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

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

1513 

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

1515 

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

1517 ''' 

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

1519 LatLon=LatLon, name=self.name,) 

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

1521 

1522 def xyzLocal5(self, ltp=None): 

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

1524 

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

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

1527 

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

1529 

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

1531 ''' 

1532 if ltp is None: 

1533 p = self 

1534 else: 

1535 p = _MODS.ltp._xLtp(ltp) 

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

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

1538 

1539 

1540__all__ += _ALL_DOCS(_NamedAerNed) 

1541 

1542# **) MIT License 

1543# 

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

1545# 

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

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

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

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

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

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

1552# 

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

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

1555# 

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

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

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

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

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

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

1562# OTHER DEALINGS IN THE SOFTWARE.