Coverage for pygeodesy/ltpTuples.py: 94%

517 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-07-12 13:40 -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 

14from pygeodesy.basics import isscalar, issubclassof 

15from pygeodesy.constants import _0_0, _90_0, _N_90_0 

16from pygeodesy.dms import F_D, toDMS 

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

18from pygeodesy.fmath import hypot, hypot_ 

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

20 _down_, _east_, _ecef_, _elevation_, _height_, \ 

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

22 _X_, _x_, _xyz_, _Y_, _y_, _z_ 

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

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

25 _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, Lat, Lon, \ 

31 Meter, Meter_ 

32from pygeodesy.utily import atan2d, atan2b, sincos2d_ 

33from pygeodesy.vector3d import Vector3d 

34 

35from math import cos, radians 

36 

37__all__ = _ALL_LAZY.ltpTuples 

38__version__ = '23.05.26' 

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_yaw_ = 'yaw' 

50 

51 

52def _er2gr(e, r): 

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

54 ''' 

55 c = cos(radians(e)) 

56 return Meter_(groundrange=r * c) 

57 

58 

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

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

61 ''' 

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

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

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

65 if sep: 

66 t = sep.join(t) 

67 if fmt: 

68 t = fmt(t) 

69 return a, t 

70 

71 

72def _4Tuple2Cls(inst, Cls, Cls_kwds): 

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

74 ''' 

75 if Cls is None: 

76 return inst 

77 elif issubclassof(Cls, Aer): 

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

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

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

81 elif issubclassof(Cls, Ned): 

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

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

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

85 elif Cls is Local9Tuple: # PYCHOK no cover 

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

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

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

89 

90 

91def _xyz2aer4(inst): 

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

93 ''' 

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

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

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

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

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

99 

100 

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

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

103 ''' 

104 n, inst = name_inst.popitem() 

105 if isinstance(inst, Types): 

106 return None 

107 try: 

108 return inst.xyzLocal 

109 except (AttributeError, TypeError): 

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

111 

112 

113class _NamedAerNed(_NamedBase): 

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

115 ''' 

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

117 

118 @Property_RO 

119 def ltp(self): 

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

121 ''' 

122 return self._ltp 

123 

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

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

126 

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

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

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

130 

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

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

133 ''' 

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

135 

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

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

138 

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

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

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

142 

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

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

145 ''' 

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

147 

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

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

150 

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

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

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

154 

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

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

157 ''' 

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

159 

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

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

162 

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

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

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

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

167 

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

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

170 

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

172 ''' 

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

174 

175 @Property_RO 

176 def xyz(self): 

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

178 ''' 

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

180 

181 @property_RO 

182 def xyz4(self): 

183 '''(INTERNAL) I{Must be overloaded}, see function C{notOverloaded}. 

184 ''' 

185 notOverloaded(self) 

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 (C{degrees360}) 

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} horizon, I{above} B{C{ltp}} 

211 (C{degrees90}) only used with scalar B{C{azimuth_aer}}. 

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

213 B{C{azimuth_aer}}. 

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

215 L{LocalCartesian}). 

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

217 

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

219 

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

221 or B{C{slantrange}}. 

222 ''' 

223 if isscalar(azimuth_aer): 

224 self._azimuth = Bearing(azimuth=azimuth_aer) 

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

226 self._slantrange = Meter_(slantrange=slantrange) 

227 p, n = ltp, name 

228 else: # PYCHOK no cover 

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

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

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

232 aer.azimuth, aer.elevation, aer.slantrange 

233 p = _xattr(aer, ltp=ltp) 

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

235 

236 if p: 

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

238 if name: 

239 self.name = n 

240 

241 @Property_RO 

242 def aer4(self): 

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

244 ''' 

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

246 

247 @Property_RO 

248 def azimuth(self): 

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

250 ''' 

251 return self._azimuth 

252 

253 @Property_RO 

254 def down(self): 

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

256 ''' 

257 return self.xyzLocal.down 

258 

259 @Property_RO 

260 def east(self): 

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

262 ''' 

263 return self.xyzLocal.east 

264 

265 @Property_RO 

266 def elevation(self): 

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

268 ''' 

269 return self._elevation 

270 

271 @Property_RO 

272 def groundrange(self): 

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

274 ''' 

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

276 

277 @Property_RO 

278 def north(self): 

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

280 ''' 

281 return self.xyzLocal.north 

282 

283 @Property_RO 

284 def slantrange(self): 

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

286 ''' 

287 return self._slantrange 

288 

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

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

291 (bearing), elevation and slant range. 

292 

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

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

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

296 

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

298 ''' 

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

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

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

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

303 

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

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

306 

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

308 number of (decimal) digits, unstripped 

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

310 backets format (C{str}) and separator 

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

312 

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

314 ''' 

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

316 return t 

317 

318 @Property_RO 

319 def up(self): 

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

321 ''' 

322 return self.xyzLocal.up 

323 

324 @Property_RO 

325 def x(self): 

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

327 ''' 

328 return self.xyz4.x 

329 

330 @Property_RO 

331 def xyz4(self): 

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

333 ''' 

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

335 R = self._slantrange 

336 r = cE * R # ground range 

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

338 

339 @Property_RO 

340 def y(self): 

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

342 ''' 

343 return self.xyz4.y 

344 

345 @Property_RO 

346 def z(self): 

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

348 ''' 

349 return self.xyz4.z 

350 

351 

352class Aer4Tuple(_NamedTuple): 

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

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

355 ''' 

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

357 _Units_ = ( Meter, Meter, Meter, _Pass) 

358 

359 def _toAer(self, Cls, Cls_kwds): 

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

361 ''' 

362 if issubclassof(Cls, Aer): 

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

364 else: 

365 return _4Tuple2Cls(self, Cls, Cls_kwds) 

366 

367 @Property_RO 

368 def groundrange(self): 

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

370 ''' 

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

372 

373 @Property_RO 

374 def xyzLocal(self): 

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

376 ''' 

377 return Aer(self).xyzLocal 

378 

379 

380class Attitude4Tuple(_NamedTuple): 

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

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

383 the attitude of a plane or camera. 

384 ''' 

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

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

387 

388 @Property_RO 

389 def atyr(self): 

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

391 ''' 

392 return self 

393 

394 @Property_RO 

395 def tyr3d(self): 

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

397 ''' 

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

399 

400 

401class Ned(_NamedAerNed): 

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

403 

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

405 ''' 

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

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

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

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

410 _toStr = _ned_ 

411 

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

413 '''New L{Ned} vector. 

414 

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

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

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

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

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

420 scalar B{C{north_ned}}. 

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

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

423 scalar B{C{north_ned}}. 

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

425 L{LocalCartesian}). 

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

427 

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

429 

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

431 ''' 

432 if isscalar(north_ned): 

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

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

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

436 p, n = ltp, name 

437 else: # PYCHOK no cover 

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

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

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

441 p = _xattr(ned, ltp=ltp) 

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

443 

444 if p: 

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

446 if n: 

447 self.name = n 

448 

449 @Property_RO 

450 def aer4(self): 

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

452 ''' 

453 return _xyz2aer4(self) 

454 

455 @Property_RO 

456 def azimuth(self): 

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

458 ''' 

459 return self.aer4.azimuth 

460 

461 @deprecated_Property_RO 

462 def bearing(self): 

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

464 return self.azimuth 

465 

466 @Property_RO 

467 def down(self): 

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

469 ''' 

470 return self._down 

471 

472 @Property_RO 

473 def east(self): 

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

475 ''' 

476 return self._east 

477 

478 @Property_RO 

479 def elevation(self): 

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

481 ''' 

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

483 

484 @Property_RO 

485 def groundrange(self): 

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

487 ''' 

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

489 

490 @deprecated_Property_RO 

491 def length(self): 

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

493 return self.slantrange 

494 

495 @deprecated_Property_RO 

496 def ned(self): 

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

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

499 

500 @Property_RO 

501 def ned4(self): 

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

503 ''' 

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

505 

506 @Property_RO 

507 def north(self): 

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

509 ''' 

510 return self._north 

511 

512 @Property_RO 

513 def slantrange(self): 

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

515 ''' 

516 return self.aer4.slantrange 

517 

518 @deprecated_method 

519 def to3ned(self): # PYCHOK no cover 

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

521 return self.ned # XXX deprecated too 

522 

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

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

525 

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

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

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

529 

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

531 ''' 

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

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

534 

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

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

537 

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

539 number of (decimal) digits, unstripped 

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

541 backets format (C{str}) and separator 

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

543 

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

545 ''' 

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

547 return t 

548 

549 @deprecated_method 

550 def toVector3d(self): 

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

552 return self.xyz 

553 

554 @Property_RO 

555 def up(self): 

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

557 ''' 

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

559 

560 @Property_RO 

561 def x(self): 

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

563 ''' 

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

565 

566 @Property_RO 

567 def xyz4(self): 

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

569 ''' 

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

571 

572 @Property_RO 

573 def y(self): 

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

575 ''' 

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

577 

578 @Property_RO 

579 def z(self): 

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

581 ''' 

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

583 

584 

585class Ned4Tuple(_NamedTuple): 

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

587 ''' 

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

589 _Units_ = ( Meter, Meter, Meter, _Pass) 

590 

591 def _toNed(self, Cls, Cls_kwds): 

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

593 ''' 

594 if issubclassof(Cls, Ned): 

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

596 else: 

597 return _4Tuple2Cls(self, Cls, Cls_kwds) 

598 

599 @Property_RO 

600 def xyzLocal(self): 

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

602 ''' 

603 return Ned(self).xyzLocal 

604 

605 

606class XyzLocal(Vector3d): 

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

608 also base class for local L{Enu}. 

609 ''' 

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

611 _toStr = _xyz_ 

612 

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

614 '''New L{XyzLocal}. 

615 

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

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

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

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

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

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

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

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

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

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

626 L{LocalCartesian}). 

627 

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

629 

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

631 ''' 

632 if isscalar(x_xyz): 

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

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

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

636 p, n = ltp, name 

637 else: 

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

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

640 p = _xattr(xyz, ltp=ltp) 

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

642 

643 if p: 

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

645 if n: 

646 self.name = n 

647 

648 def __str__(self): 

649 return self.toStr() 

650 

651 @Property_RO 

652 def aer4(self): 

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

654 ''' 

655 return _xyz2aer4(self) 

656 

657 @Property_RO 

658 def azimuth(self): 

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

660 

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

662 Transformations_between_ECEF_and_ENU_coordinates>}. 

663 ''' 

664 return self.aer4.azimuth 

665 

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

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

668 

669 @arg args: Optional, positional arguments. 

670 @kwarg kwds: Optional, keyword arguments. 

671 

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

673 ''' 

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

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

676 

677 @Property_RO 

678 def down(self): 

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

680 ''' 

681 return Meter(down=-self.z) 

682 

683 @property_RO 

684 def ecef(self): 

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

686 ''' 

687 return self.ltp.ecef 

688 

689 @Property_RO 

690 def east(self): 

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

692 ''' 

693 return Meter(east=self.x) 

694 

695 @Property_RO 

696 def elevation(self): 

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

698 

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

700 Transformations_between_ECEF_and_ENU_coordinates>}. 

701 ''' 

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

703 

704 @Property_RO 

705 def enu4(self): 

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

707 ''' 

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

709 

710 @Property_RO 

711 def groundrange(self): 

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

713 ''' 

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

715 

716 @Property_RO 

717 def ltp(self): 

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

719 ''' 

720 return self._ltp 

721 

722 @Property_RO 

723 def ned4(self): 

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

725 ''' 

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

727 

728 @Property_RO 

729 def north(self): 

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

731 ''' 

732 return Meter(north=self.y) 

733 

734 @Property_RO 

735 def slantrange(self): 

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

737 ''' 

738 return self.aer4.slantrange 

739 

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

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

742 

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

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

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

746 

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

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

749 

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

751 ''' 

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

753 

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

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

756 

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

758 or C{None}. 

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

760 overriding this C{ltp}. 

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

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

763 

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

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

766 with C{M=None}, always. 

767 

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

769 B{C{Cartesian_kwds}} argument. 

770 ''' 

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

772 if Cartesian is None: 

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

774 else: 

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

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

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

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

779 

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

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

782 

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

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

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

786 

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

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

789 ''' 

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

791 

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

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

794 

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

796 or C{None}. 

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

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

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

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

801 

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

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

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

805 

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

807 B{C{LatLon_kwds}} argument. 

808 ''' 

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

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

811 if LatLon is None: 

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

813 else: 

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

815 name=self.name or ltp.name) 

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

817 return r 

818 

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

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

821 

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

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

824 

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

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

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

828 ''' 

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

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

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

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

833 

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

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

836 

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

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

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

840 

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

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

843 ''' 

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

845 

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

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

848 

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

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

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

852 

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

854 "[N:meter, E:meter, D:meter]" respectively 

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

856 ''' 

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

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

859 

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

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

862 

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

864 number of (decimal) digits, unstripped 

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

866 backets format (C{str}) and separator 

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

868 

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

870 ''' 

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

872 return t 

873 

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

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

876 

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

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

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

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

881 

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

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

884 ''' 

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

886 

887 @Property_RO 

888 def up(self): 

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

890 ''' 

891 return Meter(up=self.z) 

892 

893# @Property_RO 

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

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

896# ''' 

897# return self._x 

898 

899# @Property_RO 

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

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

902# ''' 

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

904 

905 @Property_RO 

906 def xyz4(self): 

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

908 ''' 

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

910 

911 @Property_RO 

912 def xyzLocal(self): 

913 '''Get this L{XyzLocal}. 

914 ''' 

915 return self 

916 

917# @Property_RO 

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

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

920# ''' 

921# return self._y 

922 

923# @Property_RO 

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

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

926# ''' 

927# return self._z 

928 

929 

930class Xyz4Tuple(_NamedTuple): 

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

932 ''' 

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

934 _Units_ = ( Meter, Meter, Meter, _Pass) 

935 

936 def _toXyz(self, Cls, Cls_kwds): 

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

938 ''' 

939 if issubclassof(Cls, XyzLocal): 

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

941 else: 

942 return _4Tuple2Cls(self, Cls, Cls_kwds) 

943 

944 @Property_RO 

945 def xyzLocal(self): 

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

947 ''' 

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

949 

950 

951class Enu(XyzLocal): 

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

953 

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

955 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

956 ''' 

957 _toStr = _enu_ 

958 

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

960 '''New L{Enu}. 

961 

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

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

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

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

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

967 scalar B{C{east_enu}}. 

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

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

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

971 L{LocalCartesian}). 

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

973 

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

975 

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

977 ''' 

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

979 

980 @Property_RO 

981 def xyzLocal(self): 

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

983 ''' 

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

985 

986 

987class Enu4Tuple(_NamedTuple): 

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

989 ''' 

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

991 _Units_ = ( Meter, Meter, Meter, _Pass) 

992 

993 def _toEnu(self, Cls, Cls_kwds): 

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

995 ''' 

996 if issubclassof(Cls, XyzLocal): 

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

998 else: 

999 return _4Tuple2Cls(self, Cls, Cls_kwds) 

1000 

1001 @Property_RO 

1002 def xyzLocal(self): 

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

1004 ''' 

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

1006 

1007 

1008class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1014 ''' 

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

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

1017 

1018 @Property_RO 

1019 def azimuth(self): 

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

1021 ''' 

1022 return self.xyzLocal.aer4.azimuth 

1023 

1024 @Property_RO 

1025 def down(self): 

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

1027 ''' 

1028 return -self.z 

1029 

1030 @Property_RO 

1031 def east(self): 

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

1033 ''' 

1034 return self.x 

1035 

1036 @Property_RO 

1037 def elevation(self): 

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

1039 ''' 

1040 return self.xyzLocal.aer4.elevation 

1041 

1042 @Property_RO 

1043 def groundrange(self): 

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

1045 ''' 

1046 return self.xyzLocal.aer4.groundrange 

1047 

1048 @Property_RO 

1049 def lam(self): 

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

1051 ''' 

1052 return self.philam.lam 

1053 

1054 @Property_RO 

1055 def latlon(self): 

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

1057 ''' 

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

1059 

1060 @Property_RO 

1061 def latlonheight(self): 

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

1063 ''' 

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

1065 

1066 @Property_RO 

1067 def north(self): 

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

1069 ''' 

1070 return self.y 

1071 

1072 @Property_RO 

1073 def phi(self): 

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

1075 ''' 

1076 return self.philam.phi 

1077 

1078 @Property_RO 

1079 def philam(self): 

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

1081 ''' 

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

1083 

1084 @Property_RO 

1085 def philamheight(self): 

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

1087 ''' 

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

1089 

1090 @Property_RO 

1091 def slantrange(self): 

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

1093 ''' 

1094 return self.xyzLocal.aer4.slantrange 

1095 

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

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

1098 

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

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

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

1102 

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

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

1105 ''' 

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

1107 

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

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

1110 

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

1112 or C{None}. 

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

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

1115 

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

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

1118 

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

1120 argument. 

1121 ''' 

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

1123 

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

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

1126 

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

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

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

1130 

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

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

1133 ''' 

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

1135 

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

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

1138 

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

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

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

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

1143 

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

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

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

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

1148 

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

1150 argument. 

1151 ''' 

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

1153 

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

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

1156 

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

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

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

1160 

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

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

1163 ''' 

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

1165 

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

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

1168 

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

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

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

1172 

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

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

1175 ''' 

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

1177 

1178 @Property_RO 

1179 def up(self): 

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

1181 ''' 

1182 return self.z 

1183 

1184 @Property_RO 

1185 def xyz(self): 

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

1187 ''' 

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

1189 

1190 @Property_RO 

1191 def xyzLocal(self): 

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

1193 ''' 

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

1195 

1196 

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

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

1199 

1200 

1201class ChLV9Tuple(Local9Tuple): 

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

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

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

1205 otherwise like L{Local9Tuple}. 

1206 ''' 

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

1208 

1209 @Property_RO 

1210 def E_LV95(self): 

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

1212 ''' 

1213 return self.EN2_LV95.E_LV95 

1214 

1215 @Property_RO 

1216 def EN2_LV95(self): 

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

1218 ''' 

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

1220 

1221 @Property_RO 

1222 def h_LV03(self): 

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

1224 ''' 

1225 return self.h_ 

1226 

1227 @Property_RO 

1228 def h_LV95(self): 

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

1230 ''' 

1231 return self.h_ 

1232 

1233 @property_RO 

1234 def isChLV(self): 

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

1236 ''' 

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

1238 

1239 @property_RO 

1240 def isChLVa(self): 

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

1242 ''' 

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

1244 

1245 @property_RO 

1246 def isChLVe(self): 

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

1248 ''' 

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

1250 

1251 @Property_RO 

1252 def N_LV95(self): 

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

1254 ''' 

1255 return self.EN2_LV95.N_LV95 

1256 

1257 @Property_RO 

1258 def x(self): 

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

1260 ''' 

1261 return self.Y 

1262 

1263 @Property_RO 

1264 def x_LV03(self): 

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

1266 ''' 

1267 return self.yx2_LV03.x_LV03 

1268 

1269 @Property_RO 

1270 def y(self): 

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

1272 ''' 

1273 return self.X 

1274 

1275 @Property_RO 

1276 def y_LV03(self): 

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

1278 ''' 

1279 return self.yx2_LV03.y_LV03 

1280 

1281 @Property_RO 

1282 def YX(self): 

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

1284 ''' 

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

1286 

1287 @Property_RO 

1288 def yx2_LV03(self): 

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

1290 ''' 

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

1292 

1293 @Property_RO 

1294 def z(self): 

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

1296 ''' 

1297 return self.h_ 

1298 

1299 

1300class ChLVYX2Tuple(_NamedTuple): 

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

1302 in C{meter}. 

1303 ''' 

1304 _Names_ = (_Y_, _X_) 

1305 _Units_ = ( Meter, Meter) 

1306 

1307 def false2(self, LV95=True): 

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

1309 

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

1311 ''' 

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

1313 

1314 

1315class ChLVEN2Tuple(_NamedTuple): 

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

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

1318 ''' 

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

1320 _Units_ = ChLVYX2Tuple._Units_ 

1321 

1322 def unfalse2(self): 

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

1324 

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

1326 ''' 

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

1328 

1329 

1330class ChLVyx2Tuple(_NamedTuple): 

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

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

1333 ''' 

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

1335 _Units_ = ChLVYX2Tuple._Units_ 

1336 

1337 def unfalse2(self): 

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

1339 

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

1341 ''' 

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

1343 

1344 

1345class Footprint5Tuple(_NamedTuple): 

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

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

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

1349 

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

1351 ''' 

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

1353 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1354 

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

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

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

1358 

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

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

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

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

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

1364 

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

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

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

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

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

1370 

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

1372 

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

1374 ''' 

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

1376 LatLon=LatLon, name=self.name,) 

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

1378 

1379 def xyzLocal5(self, ltp=None): 

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

1381 

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

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

1384 

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

1386 

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

1388 ''' 

1389 if ltp is None: 

1390 p = self 

1391 else: 

1392 p = _MODS.ltp._xLtp(ltp) 

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

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

1395 

1396 

1397__all__ += _ALL_DOCS(_NamedAerNed) 

1398 

1399# **) MIT License 

1400# 

1401# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved. 

1402# 

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

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

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

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

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

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

1409# 

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

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

1412# 

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

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

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

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

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

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

1419# OTHER DEALINGS IN THE SOFTWARE.