Coverage for pygeodesy/ltpTuples.py: 94%

493 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-02 08: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 issubclassof, _xinstanceof 

15from pygeodesy.constants import _0_0, _90_0, _N_90_0 

16from pygeodesy.dms import F_D, toDMS 

17from pygeodesy.errors import _TypesError, _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_, _name_, _north_, \ 

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

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

24from pygeodesy.named import _NamedBase, _NamedTuple, 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__ = '22.10.11' 

39 

40_aer_ = 'aer' 

41_alt_ = 'alt' 

42_enu_ = 'enu' 

43_h__ = 'h_' 

44_ned_ = 'ned' 

45_roll_ = 'roll' 

46_slantrange_ = 'slantrange' 

47_tilt_ = 'tilt' 

48_yaw_ = 'yaw' 

49 

50 

51def _er2gr(e, r): 

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

53 ''' 

54 c = cos(radians(e)) 

55 return Meter_(groundrange=r * c) 

56 

57 

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

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

60 ''' 

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

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

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

64 if sep: 

65 t = sep.join(t) 

66 if fmt: 

67 t = fmt(t) 

68 return a, t 

69 

70 

71def _4Tuple2Cls(inst, Cls, Cls_kwds): 

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

73 ''' 

74 if Cls is None: 

75 return inst 

76 elif issubclassof(Cls, Aer): 

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

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

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

80 elif issubclassof(Cls, Ned): 

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

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

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

84 elif Cls is Local9Tuple: # PYCHOK no cover 

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

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

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

88 

89 

90def _xyz2aer4(inst): 

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

92 ''' 

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

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

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

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

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

98 

99 

100class _NamedAerNed(_NamedBase): 

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

102 ''' 

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

104 

105 @Property_RO 

106 def ltp(self): 

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

108 ''' 

109 return self._ltp 

110 

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

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

113 

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

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

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

117 

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

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

120 ''' 

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

122 

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

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

125 

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

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

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

129 

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

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

132 ''' 

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

134 

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

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

137 

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

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

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

141 

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

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

144 ''' 

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

146 

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

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

149 

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

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

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

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

154 

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

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

157 

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

159 ''' 

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

161 

162 @Property_RO 

163 def xyz(self): 

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

165 ''' 

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

167 

168 @property_RO 

169 def xyz4(self): 

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

171 ''' 

172 notOverloaded(self) 

173 

174 @Property_RO 

175 def xyzLocal(self): 

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

177 ''' 

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

179 

180 

181class Aer(_NamedAerNed): 

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

183 ''' 

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

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

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

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

188 _toStr = _aer_ 

189 

190 def __init__(self, aer, elevation=_0_0, slantrange=_0_0, ltp=None, name=NN): 

191 '''New L{Aer}. 

192 

193 @arg aer: Scalar azimuth, bearing from North (C{degrees360}) or 

194 local (L{Aer}, L{Aer4Tuple}). 

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

196 (C{degrees90}) if scalar B{C{aer}}. 

197 @kwarg slantrange: Scalar distance (C{meter}) if scalar B{C{aer}}. 

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

199 L{LocalCartesian}). 

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

201 

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

203 

204 @raise UnitError: Invalid B{C{azimuth}}, B{C{elevation}} or 

205 or B{C{slantrange}}. 

206 ''' 

207 try: # PYCHOK no cover 

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

209 aer.azimuth, aer.elevation, aer.slantrange 

210 _xinstanceof(Aer, Aer4Tuple, aer=aer) 

211 p = getattr(aer, _ltp_, ltp) 

212 n = getattr(aer, _name_, name) 

213 except AttributeError: 

214 self._azimuth = Bearing(azimuth=aer) 

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

216 self._slantrange = Meter_(slantrange=slantrange) 

217 p, n = ltp, name 

218 

219 if p: 

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

221 if name: 

222 self.name = n 

223 

224 @Property_RO 

225 def aer4(self): 

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

227 ''' 

228 return Aer4Tuple(self._azimuth, self._elevation, self._slantrange, self.ltp, name=self.name) 

229 

230 @Property_RO 

231 def azimuth(self): 

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

233 ''' 

234 return self._azimuth 

235 

236 @Property_RO 

237 def down(self): 

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

239 ''' 

240 return self.xyzLocal.down 

241 

242 @Property_RO 

243 def east(self): 

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

245 ''' 

246 return self.xyzLocal.east 

247 

248 @Property_RO 

249 def elevation(self): 

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

251 ''' 

252 return self._elevation 

253 

254 @Property_RO 

255 def groundrange(self): 

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

257 ''' 

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

259 

260 @Property_RO 

261 def north(self): 

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

263 ''' 

264 return self.xyzLocal.north 

265 

266 @Property_RO 

267 def slantrange(self): 

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

269 ''' 

270 return self._slantrange 

271 

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

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

274 (bearing), elevation and slant range. 

275 

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

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

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

279 

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

281 ''' 

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

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

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

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

286 

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

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

289 

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

291 number of (decimal) digits, unstripped 

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

293 backets format (C{str}) and separator 

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

295 

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

297 ''' 

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

299 return t 

300 

301 @Property_RO 

302 def up(self): 

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

304 ''' 

305 return self.xyzLocal.up 

306 

307 @Property_RO 

308 def x(self): 

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

310 ''' 

311 return self.xyz4.x 

312 

313 @Property_RO 

314 def xyz4(self): 

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

316 ''' 

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

318 R = self._slantrange 

319 r = cE * R # ground range 

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

321 

322 @Property_RO 

323 def y(self): 

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

325 ''' 

326 return self.xyz4.y 

327 

328 @Property_RO 

329 def z(self): 

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

331 ''' 

332 return self.xyz4.z 

333 

334 

335class Aer4Tuple(_NamedTuple): 

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

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

338 ''' 

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

340 _Units_ = ( Meter, Meter, Meter, _Pass) 

341 

342 def _toAer(self, Cls, Cls_kwds): 

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

344 ''' 

345 if issubclassof(Cls, Aer): 

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

347 else: 

348 return _4Tuple2Cls(self, Cls, Cls_kwds) 

349 

350 @Property_RO 

351 def groundrange(self): 

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

353 ''' 

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

355 

356 @Property_RO 

357 def xyzLocal(self): 

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

359 ''' 

360 return Aer(self).xyzLocal 

361 

362 

363class Attitude4Tuple(_NamedTuple): 

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

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

366 the attitude of a plane or camera. 

367 ''' 

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

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

370 

371 @Property_RO 

372 def atyr(self): 

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

374 ''' 

375 return self 

376 

377 @Property_RO 

378 def tyr3d(self): 

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

380 ''' 

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

382 

383 

384class Ned(_NamedAerNed): 

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

386 

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

388 ''' 

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

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

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

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

393 _toStr = _ned_ 

394 

395 def __init__(self, ned, east=_0_0, down=_0_0, ltp=None, name=NN): 

396 '''New L{Ned} vector. 

397 

398 @arg ned: Scalar north component (C{meter}) or local NED (L{Ned}, 

399 L{Ned4Tuple}). 

400 @kwarg east: Scalar east component (C{meter}) if scalar B{C{ned}}. 

401 @kwarg down: Scalar down component, normal to I{inside} surface of 

402 the ellipsoid or sphere (C{meter}) if scalar B{C{ned}}. 

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

404 L{LocalCartesian}). 

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

406 

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

408 

409 @raise UnitError: Invalid B{C{north}}, B{C{east}} or B{C{down}}. 

410 ''' 

411 try: # PYCHOK no cover 

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

413 _xinstanceof(Ned, Ned4Tuple, ned=ned) 

414 p = getattr(ned, _ltp_, ltp) 

415 n = getattr(ned, _name_, name) 

416 except AttributeError: 

417 self._north = Meter(north=ned or _0_0) 

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

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

420 p, n = ltp, name 

421 

422 if p: 

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

424 if n: 

425 self.name = n 

426 

427 @Property_RO 

428 def aer4(self): 

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

430 ''' 

431 return _xyz2aer4(self) 

432 

433 @Property_RO 

434 def azimuth(self): 

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

436 ''' 

437 return self.aer4.azimuth 

438 

439 @deprecated_Property_RO 

440 def bearing(self): 

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

442 return self.azimuth 

443 

444 @Property_RO 

445 def down(self): 

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

447 ''' 

448 return self._down 

449 

450 @Property_RO 

451 def east(self): 

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

453 ''' 

454 return self._east 

455 

456 @Property_RO 

457 def elevation(self): 

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

459 ''' 

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

461 

462 @Property_RO 

463 def groundrange(self): 

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

465 ''' 

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

467 

468 @deprecated_Property_RO 

469 def length(self): 

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

471 return self.slantrange 

472 

473 @deprecated_Property_RO 

474 def ned(self): 

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

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

477 

478 @Property_RO 

479 def ned4(self): 

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

481 ''' 

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

483 

484 @Property_RO 

485 def north(self): 

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

487 ''' 

488 return self._north 

489 

490 @Property_RO 

491 def slantrange(self): 

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

493 ''' 

494 return self.aer4.slantrange 

495 

496 @deprecated_method 

497 def to3ned(self): # PYCHOK no cover 

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

499 return self.ned # XXX deprecated too 

500 

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

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

503 

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

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

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

507 

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

509 ''' 

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

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

512 

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

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

515 

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

517 number of (decimal) digits, unstripped 

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

519 backets format (C{str}) and separator 

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

521 

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

523 ''' 

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

525 return t 

526 

527 @deprecated_method 

528 def toVector3d(self): 

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

530 return self.xyz 

531 

532 @Property_RO 

533 def up(self): 

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

535 ''' 

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

537 

538 @Property_RO 

539 def x(self): 

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

541 ''' 

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

543 

544 @Property_RO 

545 def xyz4(self): 

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

547 ''' 

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

549 

550 @Property_RO 

551 def y(self): 

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

553 ''' 

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

555 

556 @Property_RO 

557 def z(self): 

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

559 ''' 

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

561 

562 

563class Ned4Tuple(_NamedTuple): 

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

565 ''' 

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

567 _Units_ = ( Meter, Meter, Meter, _Pass) 

568 

569 def _toNed(self, Cls, Cls_kwds): 

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

571 ''' 

572 if issubclassof(Cls, Ned): 

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

574 else: 

575 return _4Tuple2Cls(self, Cls, Cls_kwds) 

576 

577 @Property_RO 

578 def xyzLocal(self): 

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

580 ''' 

581 return Ned(self).xyzLocal 

582 

583 

584class XyzLocal(Vector3d): 

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

586 and base class for local L{Enu}, L{Ned} and L{Aer}. 

587 ''' 

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

589 _toStr = _xyz_ 

590 

591 def __init__(self, x_xyz, y=_0_0, z=_0_0, ltp=None, name=NN): 

592 '''New L{XyzLocal}. 

593 

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

595 a local (L{XyzLocal}, L{Xyz4Tuple}, L{Enu}, 

596 L{Enu4Tuple}, L{Local9Tuple}). 

597 @kwarg y: Scalar Y component (C{meter}) for scalar B{C{x_xyz}}, 

598 C{positive north}. 

599 @kwarg z: Scalar Z component for scalar B{C{x_xyz}}, normal 

600 C{positive up} from the surface of the ellipsoid 

601 or sphere (C{meter}). 

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

603 L{LocalCartesian}). 

604 

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

606 

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

608 ''' 

609 try: 

610 self._x, self._y, self._z = x_xyz.x, x_xyz.y, x_xyz.z 

611 _xinstanceof(XyzLocal, Xyz4Tuple, Enu, Enu4Tuple, Local9Tuple, Ned, 

612 **{self._toStr: x_xyz}) 

613 p = getattr(x_xyz, _ltp_, ltp) 

614 n = name or getattr(x_xyz, _name_, NN) 

615 except AttributeError: 

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

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

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

619 p, n = ltp, name 

620 

621 if p: 

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

623 if n: 

624 self.name = n 

625 

626 def __str__(self): 

627 return self.toStr() 

628 

629 @Property_RO 

630 def aer4(self): 

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

632 ''' 

633 return _xyz2aer4(self) 

634 

635 @Property_RO 

636 def azimuth(self): 

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

638 

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

640 Transformations_between_ECEF_and_ENU_coordinates>}. 

641 ''' 

642 return self.aer4.azimuth 

643 

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

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

646 

647 @arg args: Optional, positional arguments. 

648 @kwarg kwds: Optional, keyword arguments. 

649 

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

651 ''' 

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

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

654 

655 @Property_RO 

656 def down(self): 

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

658 ''' 

659 return Meter(down=-self.z) 

660 

661 @property_RO 

662 def ecef(self): 

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

664 ''' 

665 return self.ltp.ecef 

666 

667 @Property_RO 

668 def east(self): 

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

670 ''' 

671 return Meter(east=self.x) 

672 

673 @Property_RO 

674 def elevation(self): 

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

676 

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

678 Transformations_between_ECEF_and_ENU_coordinates>}. 

679 ''' 

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

681 

682 @Property_RO 

683 def enu4(self): 

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

685 ''' 

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

687 

688 @Property_RO 

689 def groundrange(self): 

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

691 ''' 

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

693 

694 @Property_RO 

695 def ltp(self): 

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

697 ''' 

698 return self._ltp 

699 

700 @Property_RO 

701 def ned4(self): 

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

703 ''' 

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

705 

706 @Property_RO 

707 def north(self): 

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

709 ''' 

710 return Meter(north=self.y) 

711 

712 @Property_RO 

713 def slantrange(self): 

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

715 ''' 

716 return self.aer4.slantrange 

717 

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

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

720 

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

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

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

724 

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

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

727 

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

729 ''' 

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

731 

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

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

734 

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

736 or C{None}. 

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

738 overriding this C{ltp}. 

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

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

741 

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

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

744 with C{M=None}, always. 

745 

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

747 B{C{Cartesian_kwds}} argument. 

748 ''' 

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

750 if Cartesian is None: 

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

752 else: 

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

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

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

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

757 

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

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

760 

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

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

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

764 

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

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

767 ''' 

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

769 

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

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

772 

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

774 or C{None}. 

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

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

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

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

779 

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

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

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

783 

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

785 B{C{LatLon_kwds}} argument. 

786 ''' 

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

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

789 if LatLon is None: 

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

791 else: 

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

793 name=self.name or ltp.name) 

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

795 return r 

796 

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

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

799 

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

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

802 

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

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

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

806 ''' 

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

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

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

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

811 

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

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

814 

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

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

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

818 

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

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

821 ''' 

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

823 

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

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

826 

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

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

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

830 

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

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

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

834 ''' 

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

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

837 

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

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

840 

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

842 number of (decimal) digits, unstripped 

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

844 backets format (C{str}) and separator 

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

846 

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

848 ''' 

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

850 return t 

851 

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

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

854 

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

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

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

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

859 

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

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

862 ''' 

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

864 

865 @Property_RO 

866 def up(self): 

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

868 ''' 

869 return Meter(up=self.z) 

870 

871# @Property_RO 

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

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

874# ''' 

875# return self._x 

876 

877# @Property_RO 

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

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

880# ''' 

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

882 

883 @Property_RO 

884 def xyz4(self): 

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

886 ''' 

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

888 

889 @Property_RO 

890 def xyzLocal(self): 

891 '''Get this L{XyzLocal}. 

892 ''' 

893 return self 

894 

895# @Property_RO 

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

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

898# ''' 

899# return self._y 

900 

901# @Property_RO 

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

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

904# ''' 

905# return self._z 

906 

907 

908class Xyz4Tuple(_NamedTuple): 

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

910 ''' 

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

912 _Units_ = ( Meter, Meter, Meter, _Pass) 

913 

914 def _toXyz(self, Cls, Cls_kwds): 

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

916 ''' 

917 if issubclassof(Cls, XyzLocal): 

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

919 else: 

920 return _4Tuple2Cls(self, Cls, Cls_kwds) 

921 

922 @Property_RO 

923 def xyzLocal(self): 

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

925 ''' 

926 return XyzLocal(self, ltp=self.ltp, name=self.name) # PYCHOK .ltp 

927 

928 

929class Enu(XyzLocal): 

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

931 

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

933 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

934 ''' 

935 _toStr = _enu_ 

936 

937 def __init__(self, enu, north=_0_0, up=_0_0, ltp=None, name=NN): 

938 '''New L{Enu}. 

939 

940 @arg enu: Scalar East component (C{meter}) or a local (L{Enu}, 

941 (L{XyzLocal}, L{Local9Tuple}). 

942 @kwarg north: Scalar North component (C{meter}) if scalar B{C{enu}}. 

943 @kwarg up: Scalar Up component if scalar B{C{enu}}, normal from the 

944 surface of the ellipsoid or sphere (C{meter}). 

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

946 L{LocalCartesian}). 

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

948 

949 @raise TypeError: Invalid B{C{enu}} or B{C{ltp}}. 

950 

951 @raise UnitError: Invalid B{C{east}}, B{C{north}} or B{C{up}}. 

952 ''' 

953 XyzLocal.__init__(self, enu, north, up, ltp=ltp, name=name) 

954 

955# @Property_RO 

956# def xyzLocal(self): 

957# return XyzLocal(self, name=self.name) 

958 

959 

960class Enu4Tuple(_NamedTuple): 

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

962 ''' 

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

964 _Units_ = ( Meter, Meter, Meter, _Pass) 

965 

966 def _toEnu(self, Cls, Cls_kwds): 

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

968 ''' 

969 if issubclassof(Cls, XyzLocal): 

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

971 else: 

972 return _4Tuple2Cls(self, Cls, Cls_kwds) 

973 

974 @Property_RO 

975 def xyzLocal(self): 

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

977 ''' 

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

979 

980 

981class Local9Tuple(_NamedTuple): 

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

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

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

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

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

987 ''' 

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

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

990 

991 @Property_RO 

992 def azimuth(self): 

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

994 ''' 

995 return self.xyzLocal.aer4.azimuth 

996 

997 @Property_RO 

998 def down(self): 

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

1000 ''' 

1001 return -self.z 

1002 

1003 @Property_RO 

1004 def east(self): 

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

1006 ''' 

1007 return self.x 

1008 

1009 @Property_RO 

1010 def elevation(self): 

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

1012 ''' 

1013 return self.xyzLocal.aer4.elevation 

1014 

1015 @Property_RO 

1016 def groundrange(self): 

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

1018 ''' 

1019 return self.xyzLocal.aer4.groundrange 

1020 

1021 @Property_RO 

1022 def lam(self): 

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

1024 ''' 

1025 return self.philam.lam 

1026 

1027 @Property_RO 

1028 def latlon(self): 

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

1030 ''' 

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

1032 

1033 @Property_RO 

1034 def latlonheight(self): 

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

1036 ''' 

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

1038 

1039 @Property_RO 

1040 def north(self): 

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

1042 ''' 

1043 return self.y 

1044 

1045 @Property_RO 

1046 def phi(self): 

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

1048 ''' 

1049 return self.philam.phi 

1050 

1051 @Property_RO 

1052 def philam(self): 

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

1054 ''' 

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

1056 

1057 @Property_RO 

1058 def philamheight(self): 

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

1060 ''' 

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

1062 

1063 @Property_RO 

1064 def slantrange(self): 

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

1066 ''' 

1067 return self.xyzLocal.aer4.slantrange 

1068 

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

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

1071 

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

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

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

1075 

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

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

1078 ''' 

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

1080 

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

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

1083 

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

1085 or C{None}. 

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

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

1088 

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

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

1091 

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

1093 argument. 

1094 ''' 

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

1096 

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

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

1099 

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

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

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

1103 

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

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

1106 ''' 

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

1108 

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

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

1111 

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

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

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

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

1116 

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

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

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

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

1121 

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

1123 argument. 

1124 ''' 

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

1126 

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

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

1129 

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

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

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

1133 

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

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

1136 ''' 

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

1138 

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

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

1141 

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

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

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

1145 

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

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

1148 ''' 

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

1150 

1151 @Property_RO 

1152 def up(self): 

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

1154 ''' 

1155 return self.z 

1156 

1157 @Property_RO 

1158 def xyz(self): 

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

1160 ''' 

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

1162 

1163 @Property_RO 

1164 def xyzLocal(self): 

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

1166 ''' 

1167 return XyzLocal(self, name=self.name) 

1168 

1169 

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

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

1172 

1173 

1174class ChLV9Tuple(Local9Tuple): 

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

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

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

1178 otherwise like L{Local9Tuple}. 

1179 ''' 

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

1181 

1182 @Property_RO 

1183 def E_LV95(self): 

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

1185 ''' 

1186 return self.EN2_LV95.E_LV95 

1187 

1188 @Property_RO 

1189 def EN2_LV95(self): 

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

1191 ''' 

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

1193 

1194 @Property_RO 

1195 def h_LV03(self): 

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

1197 ''' 

1198 return self.h_ 

1199 

1200 @Property_RO 

1201 def h_LV95(self): 

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

1203 ''' 

1204 return self.h_ 

1205 

1206 @property_RO 

1207 def isChLV(self): 

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

1209 ''' 

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

1211 

1212 @property_RO 

1213 def isChLVa(self): 

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

1215 ''' 

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

1217 

1218 @property_RO 

1219 def isChLVe(self): 

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

1221 ''' 

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

1223 

1224 @Property_RO 

1225 def N_LV95(self): 

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

1227 ''' 

1228 return self.EN2_LV95.N_LV95 

1229 

1230 @Property_RO 

1231 def x(self): 

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

1233 ''' 

1234 return self.Y 

1235 

1236 @Property_RO 

1237 def x_LV03(self): 

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

1239 ''' 

1240 return self.yx2_LV03.x_LV03 

1241 

1242 @Property_RO 

1243 def y(self): 

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

1245 ''' 

1246 return self.X 

1247 

1248 @Property_RO 

1249 def y_LV03(self): 

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

1251 ''' 

1252 return self.yx2_LV03.y_LV03 

1253 

1254 @Property_RO 

1255 def YX(self): 

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

1257 ''' 

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

1259 

1260 @Property_RO 

1261 def yx2_LV03(self): 

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

1263 ''' 

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

1265 

1266 @Property_RO 

1267 def z(self): 

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

1269 ''' 

1270 return self.h_ 

1271 

1272 

1273class ChLVYX2Tuple(_NamedTuple): 

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

1275 in C{meter}. 

1276 ''' 

1277 _Names_ = (_Y_, _X_) 

1278 _Units_ = ( Meter, Meter) 

1279 

1280 def false2(self, LV95=True): 

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

1282 

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

1284 ''' 

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

1286 

1287 

1288class ChLVEN2Tuple(_NamedTuple): 

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

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

1291 ''' 

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

1293 _Units_ = ChLVYX2Tuple._Units_ 

1294 

1295 def unfalse2(self): 

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

1297 

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

1299 ''' 

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

1301 

1302 

1303class ChLVyx2Tuple(_NamedTuple): 

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

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

1306 ''' 

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

1308 _Units_ = ChLVYX2Tuple._Units_ 

1309 

1310 def unfalse2(self): 

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

1312 

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

1314 ''' 

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

1316 

1317 

1318class Footprint5Tuple(_NamedTuple): 

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

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

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

1322 

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

1324 ''' 

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

1326 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1327 

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

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

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

1331 

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

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

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

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

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

1337 

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

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

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

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

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

1343 

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

1345 

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

1347 ''' 

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

1349 LatLon=LatLon, name=self.name,) 

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

1351 

1352 def xyzLocal5(self, ltp=None): 

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

1354 

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

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

1357 

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

1359 

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

1361 ''' 

1362 if ltp is None: 

1363 p = self 

1364 else: 

1365 p = _MODS.ltp._xLtp(ltp) 

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

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

1368 

1369 

1370__all__ += _ALL_DOCS(_NamedAerNed) 

1371 

1372# **) MIT License 

1373# 

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

1375# 

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

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

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

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

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

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

1382# 

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

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

1385# 

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

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

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

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

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

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

1392# OTHER DEALINGS IN THE SOFTWARE.