Coverage for pygeodesy/ltpTuples.py: 95%

570 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-09 11:05 -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} and 

7local coordinate tuples L{Local9Tuple}, L{Xyz4Tuple}, L{Enu4Tuple}, 

8L{Ned4Tuple}, L{Aer4Tuple}, L{ChLV9Tuple}, L{ChLVEN2Tuple}, 

9L{ChLVYX2Tuple}, L{ChLVyx2Tuple} and L{Footprint5Tuple}. 

10 

11@see: References in module L{ltp}. 

12''' 

13 

14# from pygeodesy.basics import issubclassof # _MODS 

15from pygeodesy.constants import _0_0, _1_0, _90_0, _N_90_0 

16# from pygeodesy.dms import F_D, toDMS # _MODS 

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

18 _xkwds_item2 

19from pygeodesy.fmath import fdot_, hypot, hypot_ 

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

21 _ecef_, _elevation_, _height_, _lat_, _lon_, \ 

22 _ltp_, _M_, _name_, _up_, _X_, _x_, _xyz_, \ 

23 _Y_, _y_, _z_ 

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

25# from pygeodesy.ltp import Attitude, ChLV, ChLVa, ChLVe, _xLtp # _MODS 

26from pygeodesy.named import _name__, _name1__, _name2__, _NamedBase, \ 

27 _NamedTuple, _Pass, _xnamed 

28from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple 

29from pygeodesy.props import deprecated_method, deprecated_Property_RO, \ 

30 Property_RO, property_RO 

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

32from pygeodesy.units import Azimuth, Bearing, Degrees, Degrees_, Height, \ 

33 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_ 

34from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_, cos, radians 

35from pygeodesy.vector3d import Vector3d 

36 

37# from math import cos, radians # from .utily 

38 

39__all__ = _ALL_LAZY.ltpTuples 

40__version__ = '24.12.06' 

41 

42_aer_ = 'aer' 

43_alt_ = 'alt' 

44_down_ = 'down' 

45_east_ = 'east' 

46_enu_ = 'enu' 

47_h__ = 'h_' 

48_ned_ = 'ned' 

49_north_ = 'north' 

50_local_ = 'local' 

51_roll_ = 'roll' 

52_slantrange_ = 'slantrange' 

53_tilt_ = 'tilt' 

54_uvw_ = 'uvw' 

55_yaw_ = 'yaw' 

56 

57 

58class _AbcBase(_NamedBase): 

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

60 ''' 

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

62 

63 @Property_RO 

64 def ltp(self): 

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

66 ''' 

67 return self._ltp 

68 

69 def toAer(self, Aer=None, **name_Aer_kwds): 

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

71 

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

73 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

74 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer} 

75 is None}. 

76 

77 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation, 

78 slantrange, ltp)} if C{B{Aer} is None}. 

79 

80 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}}. 

81 ''' 

82 return self.xyz4._toXyz(Aer, name_Aer_kwds) 

83 

84 def toEnu(self, Enu=None, **name_Enu_kwds): 

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

86 

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

88 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

89 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

90 is None}. 

91 

92 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up, 

93 ltp)} if C{B{Enu} is None}. 

94 

95 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}}. 

96 ''' 

97 return self.xyz4._toXyz(Enu, name_Enu_kwds) 

98 

99 def toNed(self, Ned=None, **name_Ned_kwds): 

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

101 

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

103 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

104 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned} 

105 is None}. 

106 

107 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down, 

108 ltp)} if C{B{Ned} is None}. 

109 

110 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}}. 

111 ''' 

112 return self.xyz4._toXyz(Ned, name_Ned_kwds) 

113 

114 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

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

116 

117 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer}) 

118 or C{None}. 

119 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

120 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz} 

121 is None}. 

122 

123 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if 

124 C{B{Xyz} is None}. 

125 

126 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}}. 

127 ''' 

128 return self.xyz4._toXyz(Xyz, name_Xyz_kwds) 

129 

130 @Property_RO 

131 def xyz(self): 

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

133 ''' 

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

135 

136 @property_RO 

137 def xyz3(self): 

138 '''Get the I{local} C{(X, Y, Z)} coordinates as C{3-tuple}. 

139 ''' 

140 return tuple(self.xyz) 

141 

142 @property_RO 

143 def xyz4(self): # PYCHOK no cover 

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

145 self._notOverloaded() 

146 

147 @Property_RO 

148 def xyzLocal(self): 

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

150 ''' 

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

152 

153 

154class _Abc4Tuple(_NamedTuple): 

155 '''(INTERNAL) Base class for C{Aer4Tuple}, C{Enu4Tuple}, 

156 C{Ned4Tuple} and C{Xyz4Tuple}. 

157 ''' 

158 def _2Cls(self, Abc, Cls, Cls_kwds): 

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

160 ''' 

161 kwds = _name1__(Cls_kwds, _or_nameof=self) 

162 _is = _MODS.basics.issubclassof 

163 if Cls is None: 

164 n, _ = _name2__(Cls_kwds) 

165 r = self.copy(name=n) if n else self 

166 elif _is(Cls, Abc): 

167 r = Cls(*self, **kwds) 

168 elif _is(Cls, Aer): 

169 r = self.xyzLocal.toAer(**_xkwds(kwds, Aer=Cls)) 

170 elif _is(Cls, Enu): # PYCHOK no cover 

171 r = self.xyzLocal.toEnu(**_xkwds(kwds, Enu=Cls)) 

172 elif _is(Cls, Ned): 

173 r = self.xyzLocal.toNed(**_xkwds(kwds, Ned=Cls)) 

174 elif _is(Cls, XyzLocal): # PYCHOK no cover 

175 r = self.xyzLocal.toXyz(**_xkwds(kwds, Xyz=Cls)) 

176 elif Cls is Local9Tuple: # PYCHOK no cover 

177 r = self.xyzLocal.toLocal9Tuple(**kwds) 

178 else: # PYCHOK no cover 

179 n = Abc.__name__[:3] 

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

181 return r 

182 

183 @property_RO 

184 def xyzLocal(self): # PYCHOK no cover 

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

186 self._notOverloaded() 

187 

188 

189class Aer(_AbcBase): 

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

191 ''' 

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

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

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

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

196 _toStr = _aer_ 

197 

198 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, **name): 

199 '''New L{Aer}. 

200 

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

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

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

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

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

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

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

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

209 B{C{azimuth_aer}}. 

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

211 L{LocalCartesian}). 

212 @kwarg name: Optional C{B{name}=NN} (C{str}). 

213 

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

215 

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

217 or B{C{slantrange}}. 

218 ''' 

219 if _isDegrees(azimuth_aer): 

220 aer = None 

221 t = (Azimuth(azimuth_aer), 

222 Degrees_(elevation=elevation, low=_N_90_0, high=_90_0), 

223 Meter_(slantrange=slantrange), ltp) 

224 else: # PYCHOK no cover 

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

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

227 t = aer.aer4 

228 self._azimuth, self._elevation, self._slantrange, _ = t 

229 _init(self, aer, ltp, name) 

230 

231 @Property_RO 

232 def aer4(self): 

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

234 ''' 

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

236 

237 @Property_RO 

238 def azimuth(self): 

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

240 ''' 

241 return self._azimuth 

242 

243 @Property_RO 

244 def down(self): 

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

246 ''' 

247 return self.xyzLocal.down 

248 

249 @Property_RO 

250 def east(self): 

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

252 ''' 

253 return self.xyzLocal.east 

254 

255 @Property_RO 

256 def elevation(self): 

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

258 ''' 

259 return self._elevation 

260 

261 @Property_RO 

262 def groundrange(self): 

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

264 ''' 

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

266 

267 @Property_RO 

268 def north(self): 

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

270 ''' 

271 return self.xyzLocal.north 

272 

273 @Property_RO 

274 def slantrange(self): 

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

276 ''' 

277 return self._slantrange 

278 

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

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

281 (bearing), elevation and slant range. 

282 

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

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

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

286 

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

288 ''' 

289 m = _MODS.dms 

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

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

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

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

294 

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

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

297 

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

299 number of (decimal) digits, unstripped 

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

301 backets format (C{str}) and separator 

302 C{B{sep}=", "} to join (C{str}). 

303 

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

305 ''' 

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

307 return t 

308 

309 @Property_RO 

310 def up(self): 

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

312 ''' 

313 return self.xyzLocal.up 

314 

315 @Property_RO 

316 def x(self): 

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

318 ''' 

319 return self.xyz4.x 

320 

321 @Property_RO 

322 def xyz4(self): 

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

324 ''' 

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

326 R = self._slantrange 

327 r = cE * R # ground range 

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

329 

330 @Property_RO 

331 def y(self): 

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

333 ''' 

334 return self.xyz4.y 

335 

336 @Property_RO 

337 def z(self): 

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

339 ''' 

340 return self.xyz4.z 

341 

342 

343class Aer4Tuple(_Abc4Tuple): 

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

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

346 ''' 

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

348 _Units_ = ( Meter, Meter, Meter, _Pass) 

349 

350 def _toAer(self, Cls, Cls_kwds): 

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

352 ''' 

353 return self._2Cls(Aer, Cls, Cls_kwds) 

354 

355 @Property_RO 

356 def groundrange(self): 

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

358 ''' 

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

360 

361 @Property_RO 

362 def xyzLocal(self): 

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

364 ''' 

365 return Aer(self).xyzLocal 

366 

367 

368class Attitude4Tuple(_NamedTuple): 

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

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

371 the attitude of a plane or camera. 

372 ''' 

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

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

375 

376 @Property_RO 

377 def atyr(self): 

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

379 ''' 

380 return self 

381 

382 @Property_RO 

383 def tyr3d(self): 

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

385 ''' 

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

387 

388 

389class Ned(_AbcBase): 

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

391 

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

393 ''' 

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

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

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

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

398 _toStr = _ned_ 

399 

400 def __init__(self, north_ned, east=0, down=0, ltp=None, **name): 

401 '''New L{Ned} vector. 

402 

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

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

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

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

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

408 scalar B{C{north_ned}}. 

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

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

411 scalar B{C{north_ned}}. 

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

413 L{LocalCartesian}). 

414 @kwarg name: Optional C{B{name}=NN} (C{str}). 

415 

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

417 

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

419 ''' 

420 if _isMeter(north_ned): 

421 ned = None 

422 t = (Meter(north=north_ned or _0_0), 

423 Meter(east=east or _0_0), 

424 Meter(down=down or _0_0), ltp) 

425 else: # PYCHOK no cover 

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

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

428 t = ned.ned4 

429 self._north, self._east, self._down, _ = t 

430 _init(self, ned, ltp, name) 

431 

432 @Property_RO 

433 def aer4(self): 

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

435 ''' 

436 return _xyz2aer4(self) 

437 

438 @Property_RO 

439 def azimuth(self): 

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

441 ''' 

442 return self.aer4.azimuth 

443 

444 @deprecated_Property_RO 

445 def bearing(self): 

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

447 return self.azimuth 

448 

449 @Property_RO 

450 def down(self): 

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

452 ''' 

453 return self._down 

454 

455 @Property_RO 

456 def east(self): 

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

458 ''' 

459 return self._east 

460 

461 @Property_RO 

462 def elevation(self): 

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

464 ''' 

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

466 

467 @Property_RO 

468 def groundrange(self): 

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

470 ''' 

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

472 

473 @deprecated_Property_RO 

474 def length(self): 

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

476 return self.slantrange 

477 

478 @deprecated_Property_RO 

479 def ned(self): 

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

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

482 

483 @Property_RO 

484 def ned4(self): 

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

486 ''' 

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

488 

489 @Property_RO 

490 def north(self): 

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

492 ''' 

493 return self._north 

494 

495 @Property_RO 

496 def slantrange(self): 

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

498 ''' 

499 return self.aer4.slantrange 

500 

501 @deprecated_method 

502 def to3ned(self): # PYCHOK no cover 

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

504 return self.ned # XXX deprecated too 

505 

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

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

508 

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

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

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

512 

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

514 ''' 

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

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

517 

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

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

520 

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

522 number of (decimal) digits, unstripped 

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

524 backets format (C{str}) and separator 

525 C{B{sep}=", "} to join (C{str}). 

526 

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

528 ''' 

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

530 return t 

531 

532 @deprecated_method 

533 def toVector3d(self): 

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

535 return self.xyz 

536 

537 @Property_RO 

538 def up(self): 

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

540 ''' 

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

542 

543 @Property_RO 

544 def x(self): 

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

546 ''' 

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

548 

549 @Property_RO 

550 def xyz4(self): 

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

552 ''' 

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

554 

555 @Property_RO 

556 def y(self): 

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

558 ''' 

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

560 

561 @Property_RO 

562 def z(self): 

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

564 ''' 

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

566 

567 

568class Ned4Tuple(_Abc4Tuple): 

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

570 ''' 

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

572 _Units_ = ( Meter, Meter, Meter, _Pass) 

573 

574 def _toNed(self, Cls, Cls_kwds): 

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

576 ''' 

577 return self._2Cls(Ned, Cls, Cls_kwds) 

578 

579 @Property_RO 

580 def xyzLocal(self): 

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

582 ''' 

583 return Ned(self).xyzLocal 

584 

585 

586class _Vector3d(Vector3d): 

587 

588 _toStr = _xyz_ 

589 

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

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

592 

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

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

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

596 

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

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

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

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

601 ''' 

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

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

604 

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

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

607 

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

609 number of (decimal) digits, unstripped 

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

611 backets format (C{str}) and separator 

612 C{B{sep}=", "} to join (C{str}). 

613 

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

615 ''' 

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

617 return t 

618 

619 

620class XyzLocal(_Vector3d): 

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

622 also base class for local L{Enu}. 

623 ''' 

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

625 

626 def __init__(self, x_xyz, y=0, z=0, ltp=None, **name): 

627 '''New L{XyzLocal}. 

628 

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

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

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

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

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

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

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

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

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

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

639 L{LocalCartesian}). 

640 @kwarg name: Optional C{B{name}=NN} (C{str}). 

641 

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

643 

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

645 ''' 

646 if _isMeter(x_xyz): 

647 xyz = None 

648 t = (Meter(x=x_xyz or _0_0), 

649 Meter(y=y or _0_0), 

650 Meter(z=z or _0_0), ltp) 

651 else: 

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

653 t = xyz.xyz4 # xyz.x, xyz.y, xyz.z, xyz.ltp 

654 self._x, self._y, self._z, _ = t 

655 _init(self, xyz, ltp, name) 

656 

657 def __str__(self): 

658 return self.toStr() 

659 

660 @Property_RO 

661 def aer4(self): 

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

663 ''' 

664 return _xyz2aer4(self) 

665 

666 @Property_RO 

667 def azimuth(self): 

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

669 

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

671 Transformations_between_ECEF_and_ENU_coordinates>}. 

672 ''' 

673 return self.aer4.azimuth 

674 

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

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

677 

678 @arg args: Optional, positional arguments. 

679 @kwarg kwds: Optional, keyword arguments. 

680 

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

682 ''' 

683 kwds = _name1__(kwds, _or_nameof=self) 

684 return self.__class__(*args, **_xkwds(kwds, ltp=self.ltp)) 

685 

686 @Property_RO 

687 def down(self): 

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

689 ''' 

690 return Meter(down=-self.z) 

691 

692 @property_RO 

693 def ecef(self): 

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

695 ''' 

696 return self.ltp.ecef 

697 

698 @Property_RO 

699 def east(self): 

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

701 ''' 

702 return Meter(east=self.x) 

703 

704 @Property_RO 

705 def elevation(self): 

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

707 

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

709 Transformations_between_ECEF_and_ENU_coordinates>}. 

710 ''' 

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

712 

713 @Property_RO 

714 def enu4(self): 

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

716 ''' 

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

718 

719 @Property_RO 

720 def groundrange(self): 

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

722 ''' 

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

724 

725 @Property_RO 

726 def ltp(self): 

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

728 ''' 

729 return self._ltp 

730 

731 def _ltp_kwds_name3(self, ltp, kwds): 

732 '''(INTERNAL) Helper for methods C{toCartesian} and C{toLatLon}. 

733 ''' 

734 ltp = _xLtp(ltp, self.ltp) 

735 kwds = _name1__(kwds, _or_nameof=self) 

736 kwds = _name1__(kwds, _or_nameof=ltp) 

737 return ltp, kwds, kwds.get(_name_, NN) 

738 

739 @Property_RO 

740 def ned4(self): 

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

742 ''' 

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

744 

745 @Property_RO 

746 def north(self): 

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

748 ''' 

749 return Meter(north=self.y) 

750 

751 @Property_RO 

752 def slantrange(self): 

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

754 ''' 

755 return self.aer4.slantrange 

756 

757 def toAer(self, Aer=None, **name_Aer_kwds): 

758 '''Get the local I{Azimuth, Elevation, slant Range} components. 

759 

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

761 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

762 additional B{C{Aer}} keyword arguments, ignored if C{B{Aer} 

763 is None}. 

764 

765 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation, 

766 slantrange, ltp)} if C{B{Aer} is None}. 

767 

768 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item. 

769 ''' 

770 return self.aer4._toAer(Aer, name_Aer_kwds) 

771 

772 def toCartesian(self, Cartesian=None, ltp=None, **name_Cartesian_kwds): # PYCHOK signature 

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

774 

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

776 or C{None}. 

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

778 this C{ltp}. 

779 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

780 additional B{C{Cartesian}} keyword arguments, ignored if 

781 C{B{Cartesian} is None}. 

782 

783 @return: A B{C{Cartesian}} instance or if C{B{Cartesian} is None}, an 

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

785 C{M=None}, always. 

786 

787 @raise TypeError: Invalid B{C{ltp}}, B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item. 

788 ''' 

789 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_Cartesian_kwds) 

790 if Cartesian is None: 

791 t = ltp._local2ecef(self, nine=True) 

792 r = _xnamed(t, n) if n else t 

793 else: 

794 kwds = _xkwds(kwds, datum=ltp.datum, name=n) 

795 xyz = ltp._local2ecef(self) # [:3] 

796 r = Cartesian(*xyz, **kwds) 

797 return r 

798 

799 def toEnu(self, Enu=None, **name_Enu_kwds): 

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

801 

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

803 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

804 additional B{C{Enu}} keyword arguments, ignored if C{B{Enu} 

805 is None}. 

806 

807 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up, 

808 ltp)} if C{B{Enu} is None}. 

809 

810 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item. 

811 ''' 

812 return self.enu4._toEnu(Enu, name_Enu_kwds) 

813 

814 def toLatLon(self, LatLon=None, ltp=None, **name_LatLon_kwds): 

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

816 

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

818 C{None}. 

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

820 this ENU/NED/AER/XYZ's LTP. 

821 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

822 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon} 

823 is None}. 

824 

825 @return: An B{C{LatLon}} instance or an L{Ecef9Tuple}C{(x, y, z, lat, lon, 

826 height, C, M, datum)} if C{B{LatLon} is None}, with C{M=None}. 

827 

828 @raise TypeError: Invalid B{C{LatLon}}, B{C{ltp}} or B{C{name_LatLon_kwds}} item. 

829 ''' 

830 ltp, kwds, n = self._ltp_kwds_name3(ltp, name_LatLon_kwds) 

831 t = ltp._local2ecef(self, nine=True) 

832 if LatLon is None: 

833 r = _xnamed(t, n) if n else t 

834 else: 

835 kwds = _xkwds(kwds, height=t.height, datum=t.datum) 

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

837 return r 

838 

839 def toLocal9Tuple(self, M=False, **name): 

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

841 

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

843 @kwarg name: Optional C{B{name}=NN} (C{str}). 

844 

845 @return: L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)} 

846 with C{ltp} this C{Ltp}, C{ecef} an L{Ecef9Tuple} and C{M} 

847 an L{EcefMatrix} or C{None}. 

848 ''' 

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

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

851 return Local9Tuple(self.x, self.y, self.z, 

852 t.lat, t.lon, t.height, 

853 ltp, t, t.M, name=t._name__(name)) 

854 

855 def toNed(self, Ned=None, **name_Ned_kwds): 

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

857 

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

859 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

860 additional B{C{Ned}} keyword arguments, ignored if C{B{Ned} 

861 is None}. 

862 

863 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down, 

864 ltp)} if C{B{Ned} is None}. 

865 

866 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item. 

867 ''' 

868 return self.ned4._toNed(Ned, name_Ned_kwds) 

869 

870 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

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

872 

873 @kwarg Xyz: Class to return XYZ (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer}) 

874 or C{None}. 

875 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

876 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz} 

877 is None}. 

878 

879 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if 

880 C{B{Xyz} is None}. 

881 

882 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item. 

883 ''' 

884 return self.xyz4._toXyz(Xyz, name_Xyz_kwds) 

885 

886 @Property_RO 

887 def up(self): 

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

889 ''' 

890 return Meter(up=self.z) 

891 

892# @Property_RO 

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

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

895# ''' 

896# return self._x 

897 

898# @Property_RO 

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

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

901# ''' 

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

903 

904 @Property_RO 

905 def xyz4(self): 

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

907 ''' 

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

909 

910 @Property_RO 

911 def xyzLocal(self): 

912 '''Get this L{XyzLocal}. 

913 ''' 

914 return self 

915 

916# @Property_RO 

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

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

919# ''' 

920# return self._y 

921 

922# @Property_RO 

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

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

925# ''' 

926# return self._z 

927 

928 

929class Xyz4Tuple(_Abc4Tuple): 

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

931 ''' 

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

933 _Units_ = ( Meter, Meter, Meter, _Pass) 

934 

935 def _toXyz(self, Cls, Cls_kwds): 

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

937 ''' 

938 return self._2Cls(XyzLocal, Cls, Cls_kwds) 

939 

940 @property_RO 

941 def xyz4(self): 

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

943 ''' 

944 return self 

945 

946 @Property_RO 

947 def xyzLocal(self): 

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

949 ''' 

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

951 

952 

953class Enu(XyzLocal): 

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

955 

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

957 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

958 ''' 

959 _toStr = _enu_ 

960 

961 def __init__(self, east_enu, north=0, up=0, ltp=None, **name): 

962 '''New L{Enu}. 

963 

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

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

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

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

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

969 scalar B{C{east_enu}}. 

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

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

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

973 L{LocalCartesian}). 

974 @kwarg name: Optional C{B{name}=NN} (C{str}). 

975 

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

977 

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

979 ''' 

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

981 

982 def toUvw(self, location, Uvw=None, **name_Uvw_kwds): 

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

984 

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

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

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

988 @kwarg name_Uvw_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

989 additional B{L{Uvw}} keyword arguments, ignored if C{B{Uvw} 

990 is None}. 

991 

992 @return: A B{C{Uvw}} instance or a L{Uvw3Tuple}C{(u, v, w)} if C{B{Uvw} 

993 is None}. 

994 

995 @raise TypeError: Invalid B{C{location}} or B{C{name_Uvw_kwds}} item. 

996 

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

998 ''' 

999 try: 

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

1001 except Exception as x: 

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

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

1004 

1005 t = fdot_(ca, u, -sa, n) 

1006 U = fdot_(cb, t, -sb, e) 

1007 V = fdot_(cb, e, sb, t) 

1008 W = fdot_(ca, n, sa, u) 

1009 

1010 n, kwds = _name2__(name_Uvw_kwds, _or_nameof=self) 

1011 return Uvw3Tuple(U, V, W, name=n) if Uvw is None else \ 

1012 Uvw(U, V, W, name=n, **kwds) 

1013 

1014 @Property_RO 

1015 def xyzLocal(self): 

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

1017 ''' 

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

1019 

1020 

1021class Enu4Tuple(_Abc4Tuple): 

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

1023 ''' 

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

1025 _Units_ = ( Meter, Meter, Meter, _Pass) 

1026 

1027 def _toEnu(self, Cls, Cls_kwds): 

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

1029 ''' 

1030 return self._2Cls(Enu, Cls, Cls_kwds) 

1031 

1032 @Property_RO 

1033 def xyzLocal(self): 

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

1035 ''' 

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

1037 

1038 

1039class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1045 ''' 

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

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

1048 

1049 @Property_RO 

1050 def azimuth(self): 

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

1052 ''' 

1053 return self.xyzLocal.aer4.azimuth 

1054 

1055 @Property_RO 

1056 def down(self): 

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

1058 ''' 

1059 return -self.z 

1060 

1061 @Property_RO 

1062 def east(self): 

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

1064 ''' 

1065 return self.x 

1066 

1067 @Property_RO 

1068 def elevation(self): 

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

1070 ''' 

1071 return self.xyzLocal.aer4.elevation 

1072 

1073 @Property_RO 

1074 def groundrange(self): 

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

1076 ''' 

1077 return self.xyzLocal.aer4.groundrange 

1078 

1079 @Property_RO 

1080 def lam(self): 

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

1082 ''' 

1083 return self.philam.lam 

1084 

1085 @Property_RO 

1086 def latlon(self): 

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

1088 ''' 

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

1090 

1091 @Property_RO 

1092 def latlonheight(self): 

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

1094 ''' 

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

1096 

1097 @Property_RO 

1098 def north(self): 

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

1100 ''' 

1101 return self.y 

1102 

1103 @Property_RO 

1104 def phi(self): 

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

1106 ''' 

1107 return self.philam.phi 

1108 

1109 @Property_RO 

1110 def philam(self): 

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

1112 ''' 

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

1114 

1115 @Property_RO 

1116 def philamheight(self): 

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

1118 ''' 

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

1120 

1121 @Property_RO 

1122 def slantrange(self): 

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

1124 ''' 

1125 return self.xyzLocal.aer4.slantrange 

1126 

1127 def toAer(self, Aer=None, **name_Aer_kwds): 

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

1129 

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

1131 @kwarg name_Aer_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1132 additional B{L{Aer}} keyword arguments, ignored if C{B{Aer} 

1133 is None}. 

1134 

1135 @return: An B{C{Aer}} instance or an L{Aer4Tuple}C{(azimuth, elevation, 

1136 slantrange, ltp)} if C{B{Aer} is None}. 

1137 

1138 @raise TypeError: Invalid B{C{Aer}} or B{C{name_Aer_kwds}} item. 

1139 ''' 

1140 return self.xyzLocal.toAer(Aer=Aer, **name_Aer_kwds) 

1141 

1142 def toCartesian(self, Cartesian=None, **name_Cartesian_kwds): 

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

1144 

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

1146 (C{Cartesian}) or C{None}. 

1147 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1148 additional B{C{Cartesian}} keyword arguments, ignored if 

1149 C{B{Cartesian} is None}. 

1150 

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

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

1153 

1154 @raise TypeError: Invalid B{C{Cartesian}} or B{C{name_Cartesian_kwds}} item. 

1155 ''' 

1156 return self.ecef.toCartesian(Cartesian=Cartesian, **name_Cartesian_kwds) # PYCHOK _Tuple 

1157 

1158 def toEnu(self, Enu=None, **name_Enu_kwds): 

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

1160 

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

1162 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1163 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

1164 is None}. 

1165 

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

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

1168 

1169 @raise TypeError: Invalid B{C{Enu}} or B{C{name_Enu_kwds}} item. 

1170 ''' 

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

1172 

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

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

1175 

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

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

1178 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1179 additional B{C{LatLon}} keyword arguments, ignored if 

1180 C{B{LatLon} is None}. 

1181 

1182 @return: An C{B{LatLon}(lat, lon, **B{LatLon_kwds})} instance or if 

1183 C{B{LatLon} is None}, a L{LatLon4Tuple}C{(lat, lon, height, 

1184 datum)} or L{LatLon3Tuple}C{(lat, lon, height)} if C{datum} 

1185 is specified or not. 

1186 

1187 @raise TypeError: Invalid B{C{LatLon}} or B{C{name_LatLon_kwds}}. 

1188 ''' 

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

1190 

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

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

1193 

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

1195 @kwarg name_Ned_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1196 additional B{L{Ned}} keyword arguments, ignored if C{B{Ned} 

1197 is None}. 

1198 

1199 @return: An B{C{Ned}} instance or an L{Ned4Tuple}C{(north, east, down, 

1200 ltp)} if C{B{Ned} is None}. 

1201 

1202 @raise TypeError: Invalid B{C{Ned}} or B{C{name_Ned_kwds}} item. 

1203 ''' 

1204 return self.xyzLocal.toNed(Ned=Ned, **name_Ned_kwds) 

1205 

1206 def toXyz(self, Xyz=None, **name_Xyz_kwds): 

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

1208 

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

1210 @kwarg name_Xyz_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1211 additional B{C{Xyz}} keyword arguments, ignored if C{B{Xyz} 

1212 is None}. 

1213 

1214 @return: An B{C{Xyz}} instance or an L{Xyz4Tuple}C{(x, y, z, ltp)} if 

1215 C{B{Xyz} is None}. 

1216 

1217 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_Xyz_kwds}} item. 

1218 ''' 

1219 return self.xyzLocal.toXyz(Xyz=Xyz, **name_Xyz_kwds) 

1220 

1221 @Property_RO 

1222 def up(self): 

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

1224 ''' 

1225 return self.z 

1226 

1227 @Property_RO 

1228 def xyz(self): 

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

1230 ''' 

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

1232 

1233 @Property_RO 

1234 def xyzLocal(self): 

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

1236 ''' 

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

1238 

1239 

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

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

1242 

1243 

1244class Uvw(_Vector3d): 

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

1246 ''' 

1247 _toStr = _uvw_ 

1248 

1249 def __init__(self, u_uvw, v=0, w=0, **name): 

1250 '''New L{Uvw}. 

1251 

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

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

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

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

1256 @kwarg name: Optional C{B{name}=NN} (C{str}). 

1257 

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

1259 

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

1261 ''' 

1262 Vector3d.__init__(self, u_uvw, v, w, **name) 

1263 

1264 def toEnu(self, location, Enu=Enu, **name_Enu_kwds): 

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

1266 

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

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

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

1270 @kwarg name_Enu_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1271 additional B{L{Enu}} keyword arguments, ignored if C{B{Enu} 

1272 is None}. 

1273 

1274 @return: An B{C{Enu}} instance or an L{Enu4Tuple}C{(east, north, up, ltp)} 

1275 with C{ltp=None} if C{B{Enu} is None}. 

1276 

1277 @raise TypeError: Invalid B{C{location}}, B{C{Enu}} or B{C{name_Enu_kwds}} item. 

1278 

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

1280 ''' 

1281 try: 

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

1283 except Exception as x: 

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

1285 u, v, w = self.uvw 

1286 

1287 t = fdot_(cb, u, sb, v) 

1288 E = fdot_(cb, v, -sb, u) 

1289 N = fdot_(ca, w, -sa, t) 

1290 U = fdot_(ca, t, sa, w) 

1291 

1292 n, kwds = _name2__(name_Enu_kwds, _or_nameof=self) 

1293 return Enu4Tuple(E, N, U, name=n) if Enu is None else \ 

1294 Enu(E, N, U, name=n, **kwds) 

1295 

1296 u = Vector3d.x 

1297 

1298 @Property_RO 

1299 def uvw(self): 

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

1301 ''' 

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

1303 

1304 v = Vector3d.y 

1305 w = Vector3d.z 

1306 

1307 

1308class Uvw3Tuple(_NamedTuple): 

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

1310 ''' 

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

1312 _Units_ = ( Meter, Meter, Meter) 

1313 

1314 

1315class Los(Aer): 

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

1317 ''' 

1318 

1319 def __init__(self, azimuth_aer, elevation=0, **name): 

1320 '''New L{Los}. 

1321 

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

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

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

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

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

1327 B{C{azimuth_aer}}. 

1328 @kwarg name: Optional C{B{name}=NN} (C{str}). 

1329 

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

1331 

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

1333 ''' 

1334 t = Aer(azimuth_aer, elevation) 

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

1336 

1337 def toUvw(self, location, Uvw=Uvw, **name_Uvw_kwds): 

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

1339 

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

1341 L{Vector3d}) location from where to cast this LOS. 

1342 

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

1344 ''' 

1345 return self.toEnu().toUvw(location, Uvw=Uvw, **name_Uvw_kwds) 

1346 

1347 def toEnu(self, Enu=Enu, **name_Enu_kwds): 

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

1349 

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

1351 ''' 

1352 return Aer.toEnu(self, Enu=Enu, **name_Enu_kwds) 

1353 

1354 

1355class ChLV9Tuple(Local9Tuple): 

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

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

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

1359 otherwise like L{Local9Tuple}. 

1360 ''' 

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

1362 

1363 @Property_RO 

1364 def E_LV95(self): 

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

1366 ''' 

1367 return self.EN2_LV95.E_LV95 

1368 

1369 @Property_RO 

1370 def EN2_LV95(self): 

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

1372 ''' 

1373 return ChLVEN2Tuple(*_ChLV_false2(*self.YX, LV95=True), name=self.name) 

1374 

1375 @Property_RO 

1376 def h_LV03(self): 

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

1378 ''' 

1379 return self.h_ 

1380 

1381 @Property_RO 

1382 def h_LV95(self): 

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

1384 ''' 

1385 return self.h_ 

1386 

1387 @property_RO 

1388 def isChLV(self): 

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

1390 ''' 

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

1392 

1393 @property_RO 

1394 def isChLVa(self): 

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

1396 ''' 

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

1398 

1399 @property_RO 

1400 def isChLVe(self): 

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

1402 ''' 

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

1404 

1405 @Property_RO 

1406 def N_LV95(self): 

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

1408 ''' 

1409 return self.EN2_LV95.N_LV95 

1410 

1411 @Property_RO 

1412 def x(self): 

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

1414 ''' 

1415 return self.Y 

1416 

1417 @Property_RO 

1418 def x_LV03(self): 

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

1420 ''' 

1421 return self.yx2_LV03.x_LV03 

1422 

1423 @Property_RO 

1424 def y(self): 

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

1426 ''' 

1427 return self.X 

1428 

1429 @Property_RO 

1430 def y_LV03(self): 

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

1432 ''' 

1433 return self.yx2_LV03.y_LV03 

1434 

1435 @Property_RO 

1436 def YX(self): 

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

1438 ''' 

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

1440 

1441 @Property_RO 

1442 def yx2_LV03(self): 

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

1444 ''' 

1445 return ChLVyx2Tuple(*_ChLV_false2(*self.YX, LV95=False), name=self.name) 

1446 

1447 @Property_RO 

1448 def z(self): 

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

1450 ''' 

1451 return self.h_ 

1452 

1453 

1454class ChLVYX2Tuple(_NamedTuple): 

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

1456 in C{meter}. 

1457 ''' 

1458 _Names_ = (_Y_, _X_) 

1459 _Units_ = ( Meter, Meter) 

1460 

1461 def false2(self, LV95=True): 

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

1463 

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

1465 ''' 

1466 return _ChLV_false2(*self, LV95=LV95, name=self.name) 

1467 

1468 

1469class ChLVEN2Tuple(_NamedTuple): 

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

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

1472 ''' 

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

1474 _Units_ = ChLVYX2Tuple._Units_ 

1475 

1476 def unfalse2(self): 

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

1478 

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

1480 ''' 

1481 return _ChLV_unfalse2(*self, LV95=True, name=self.name) 

1482 

1483 

1484class ChLVyx2Tuple(_NamedTuple): 

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

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

1487 ''' 

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

1489 _Units_ = ChLVYX2Tuple._Units_ 

1490 

1491 def unfalse2(self): 

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

1493 

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

1495 ''' 

1496 return _ChLV_unfalse2(*self, LV95=False, name=self.name) 

1497 

1498 

1499class Footprint5Tuple(_NamedTuple): 

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

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

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

1503 

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

1505 ''' 

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

1507 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1508 

1509 def toLatLon5(self, ltp=None, LatLon=None, **name_LatLon_kwds): 

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

1511 C{LatLon(lat, lon, height)}s, C{LatLon3Tuple}s or C{LatLon4Tuple}s. 

1512 

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

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

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

1516 @kwarg name_LatLon_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

1517 additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon} 

1518 is None}. 

1519 

1520 @return: A L{Footprint5Tuple} of 5 C{B{LatLon}(lat, lon, **B{name_LatLon_kwds})} 

1521 instances or if C{B{LatLon} is None}, 5 L{LatLon3Tuple}C{(lat, lon, 

1522 height)}s respectively 5 L{LatLon4Tuple}C{(lat, lon, height, datum)}s 

1523 depending on whether keyword argument C{datum} is un-/specified. 

1524 

1525 @raise TypeError: Invalid B{C{ltp}}, B{C{LatLon}} or B{C{name_LatLon_kwds}} item. 

1526 

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

1528 ''' 

1529 ltp = _xLtp(ltp, self.center.ltp) # PYCHOK .center 

1530 kwds = _name1__(name_LatLon_kwds, _or_nameof=self) 

1531 kwds = _xkwds(kwds, ltp=ltp, LatLon=LatLon) 

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

1533 

1534 def xyzLocal5(self, ltp=None): 

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

1536 

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

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

1539 

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

1541 

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

1543 ''' 

1544 if ltp is None: 

1545 p = self 

1546 else: 

1547 p = _xLtp(ltp) 

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

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

1550 

1551 

1552def _ChLV_false2(Y, X, **LV95_name): 

1553 '''(INTERNAL) Invoke static method C{ltp.ChLV.false2}. 

1554 ''' 

1555 return _MODS.ltp.ChLV.false2(Y, X, **LV95_name) 

1556 

1557 

1558def _ChLV_unfalse2(e, n, **LV95_name): 

1559 '''(INTERNAL) Invoke static method C{ltp.ChLV.unfalse2}. 

1560 ''' 

1561 return _MODS.ltp.ChLV.unfalse2(e, n, **LV95_name) 

1562 

1563 

1564def _er2gr(e, r): 

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

1566 ''' 

1567 c = cos(radians(e)) 

1568 return Meter_(groundrange=r * c) 

1569 

1570 

1571def _init(inst, abc, ltp, name): 

1572 '''(INTERNAL) Complete C{__init__}. 

1573 ''' 

1574 if abc is None: 

1575 n = _name__(**name) 

1576 else: 

1577 n = abc._name__(name) 

1578 ltp = _xattr(abc, ltp=ltp) 

1579 if ltp: 

1580 inst._ltp = _xLtp(ltp) 

1581 if n: 

1582 inst.name = n 

1583 

1584 

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

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

1587 ''' 

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

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

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

1591 if sep: 

1592 t = sep.join(t) 

1593 if fmt: 

1594 t = fmt(t) 

1595 return a, t 

1596 

1597 

1598def _xLtp(ltp, *dflt): 

1599 '''(INTERNAL) Invoke C{ltp._xLtp}. 

1600 ''' 

1601 return _MODS.ltp._xLtp(ltp, *dflt) 

1602 

1603 

1604def _xyz2aer4(inst): 

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

1606 ''' 

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

1608 A = Azimuth(atan2b(x, y)) 

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

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

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

1612 

1613 

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

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

1616 ''' 

1617 n, inst = _xkwds_item2(name_inst) 

1618 if isinstance(inst, Types): 

1619 return None 

1620 try: 

1621 return inst.xyzLocal 

1622 except (AttributeError, TypeError): 

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

1624 

1625 

1626__all__ += _ALL_DOCS(_AbcBase) 

1627 

1628# **) MIT License 

1629# 

1630# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

1631# 

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

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

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

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

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

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

1638# 

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

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

1641# 

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

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

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

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

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

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

1648# OTHER DEALINGS IN THE SOFTWARE.