Coverage for pygeodesy/ltpTuples.py: 95%

570 statements  

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

14from pygeodesy.basics import issubclassof, typename 

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_ 

20# rom pygeodesy.internals import typename # from .basics 

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

22 _ecef_, _elevation_, _height_, _lat_, _lon_, \ 

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

24 _Y_, _y_, _z_ 

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

26# from pygeodesy.ltp Attitude, ChLV, ChLVa, ChLVe _Xltp # _MODS.into 

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

28 _NamedTuple, _Pass, _xnamed 

29from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple 

30from pygeodesy.props import deprecated_method, deprecated_Property_RO, \ 

31 Property_RO, property_RO 

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

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

34 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_ 

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

36from pygeodesy.vector3d import Vector3d 

37 

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

39 

40__all__ = _ALL_LAZY.ltpTuples 

41__version__ = '25.05.01' 

42 

43_aer_ = 'aer' 

44_alt_ = 'alt' 

45_down_ = 'down' 

46_east_ = 'east' 

47_enu_ = 'enu' 

48_h__ = 'h_' 

49_ned_ = 'ned' 

50_north_ = 'north' 

51_local_ = 'local' 

52_ltp = _MODS.into(ltp=__name__) 

53_roll_ = 'roll' 

54_slantrange_ = 'slantrange' 

55_tilt_ = 'tilt' 

56_uvw_ = 'uvw' 

57_yaw_ = 'yaw' 

58 

59 

60class _AbcBase(_NamedBase): 

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

62 ''' 

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

64 

65 @Property_RO 

66 def ltp(self): 

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

68 ''' 

69 return self._ltp 

70 

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

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

73 

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

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

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

77 is None}. 

78 

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

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

81 

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

83 ''' 

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

85 

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

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

88 

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

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

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

92 is None}. 

93 

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

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

96 

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

98 ''' 

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

100 

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

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

103 

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

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

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

107 is None}. 

108 

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

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

111 

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

113 ''' 

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

115 

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

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

118 

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

120 or C{None}. 

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

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

123 is None}. 

124 

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

126 C{B{Xyz} is None}. 

127 

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

129 ''' 

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

131 

132 @Property_RO 

133 def xyz(self): 

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

135 ''' 

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

137 

138 @property_RO 

139 def xyz3(self): 

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

141 ''' 

142 return tuple(self.xyz) 

143 

144 @property_RO 

145 def xyz4(self): # PYCHOK no cover 

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

147 self._notOverloaded() 

148 

149 @Property_RO 

150 def xyzLocal(self): 

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

152 ''' 

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

154 

155 

156class _Abc4Tuple(_NamedTuple): 

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

158 C{Ned4Tuple} and C{Xyz4Tuple}. 

159 ''' 

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

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

162 ''' 

163 _isc = issubclassof 

164 kwds = _name1__(Cls_kwds, _or_nameof=self) 

165 if Cls is None: 

166 n, _ = _name2__(Cls_kwds) 

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

168 elif _isc(Cls, Abc): 

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

170 elif _isc(Cls, Aer): 

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

172 elif _isc(Cls, Enu): # PYCHOK no cover 

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

174 elif _isc(Cls, Ned): 

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

176 elif _isc(Cls, XyzLocal): # PYCHOK no cover 

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

178 elif Cls is Local9Tuple: # PYCHOK no cover 

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

180 else: # PYCHOK no cover 

181 n = typename(Abc)[:3] 

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

183 return r 

184 

185 @property_RO 

186 def xyzLocal(self): # PYCHOK no cover 

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

188 self._notOverloaded() 

189 

190 

191class Aer(_AbcBase): 

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

193 ''' 

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

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

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

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

198 _toStr = _aer_ 

199 

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

201 '''New L{Aer}. 

202 

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

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

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

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

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

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

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

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

211 B{C{azimuth_aer}}. 

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

213 L{LocalCartesian}). 

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

215 

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

217 

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

219 or B{C{slantrange}}. 

220 ''' 

221 if _isDegrees(azimuth_aer): 

222 aer = None 

223 t = (Azimuth(azimuth_aer), 

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

225 Meter_(slantrange=slantrange), ltp) 

226 else: # PYCHOK no cover 

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

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

229 t = aer.aer4 

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

231 _init(self, aer, ltp, name) 

232 

233 @Property_RO 

234 def aer4(self): 

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

236 ''' 

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

238 

239 @Property_RO 

240 def azimuth(self): 

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

242 ''' 

243 return self._azimuth 

244 

245 @Property_RO 

246 def down(self): 

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

248 ''' 

249 return self.xyzLocal.down 

250 

251 @Property_RO 

252 def east(self): 

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

254 ''' 

255 return self.xyzLocal.east 

256 

257 @Property_RO 

258 def elevation(self): 

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

260 ''' 

261 return self._elevation 

262 

263 @Property_RO 

264 def groundrange(self): 

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

266 ''' 

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

268 

269 @Property_RO 

270 def north(self): 

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

272 ''' 

273 return self.xyzLocal.north 

274 

275 @Property_RO 

276 def slantrange(self): 

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

278 ''' 

279 return self._slantrange 

280 

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

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

283 (bearing), elevation and slant range. 

284 

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

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

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

288 

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

290 ''' 

291 m = _MODS.dms 

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

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

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

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

296 

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

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

299 

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

301 number of (decimal) digits, unstripped 

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

303 backets format (C{str}) and separator 

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

305 

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

307 ''' 

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

309 return t 

310 

311 @Property_RO 

312 def up(self): 

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

314 ''' 

315 return self.xyzLocal.up 

316 

317 @Property_RO 

318 def x(self): 

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

320 ''' 

321 return self.xyz4.x 

322 

323 @Property_RO 

324 def xyz4(self): 

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

326 ''' 

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

328 R = self._slantrange 

329 r = cE * R # ground range 

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

331 

332 @Property_RO 

333 def y(self): 

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

335 ''' 

336 return self.xyz4.y 

337 

338 @Property_RO 

339 def z(self): 

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

341 ''' 

342 return self.xyz4.z 

343 

344 

345class Aer4Tuple(_Abc4Tuple): 

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

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

348 ''' 

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

350 _Units_ = ( Meter, Meter, Meter, _Pass) 

351 

352 def _toAer(self, Cls, Cls_kwds): 

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

354 ''' 

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

356 

357 @Property_RO 

358 def groundrange(self): 

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

360 ''' 

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

362 

363 @Property_RO 

364 def xyzLocal(self): 

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

366 ''' 

367 return Aer(self).xyzLocal 

368 

369 

370class Attitude4Tuple(_NamedTuple): 

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

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

373 the attitude of a plane or camera. 

374 ''' 

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

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

377 

378 @Property_RO 

379 def atyr(self): 

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

381 ''' 

382 return self 

383 

384 @Property_RO 

385 def tyr3d(self): 

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

387 ''' 

388 return _ltp.Attitude(self).tyr3d 

389 

390 

391class Ned(_AbcBase): 

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

393 

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

395 ''' 

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

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

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

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

400 _toStr = _ned_ 

401 

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

403 '''New L{Ned} vector. 

404 

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

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

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

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

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

410 scalar B{C{north_ned}}. 

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

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

413 scalar B{C{north_ned}}. 

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

415 L{LocalCartesian}). 

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

417 

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

419 

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

421 ''' 

422 if _isMeter(north_ned): 

423 ned = None 

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

425 Meter(east=east or _0_0), 

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

427 else: # PYCHOK no cover 

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

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

430 t = ned.ned4 

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

432 _init(self, ned, ltp, name) 

433 

434 @Property_RO 

435 def aer4(self): 

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

437 ''' 

438 return _xyz2aer4(self) 

439 

440 @Property_RO 

441 def azimuth(self): 

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

443 ''' 

444 return self.aer4.azimuth 

445 

446 @deprecated_Property_RO 

447 def bearing(self): 

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

449 return self.azimuth 

450 

451 @Property_RO 

452 def down(self): 

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

454 ''' 

455 return self._down 

456 

457 @Property_RO 

458 def east(self): 

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

460 ''' 

461 return self._east 

462 

463 @Property_RO 

464 def elevation(self): 

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

466 ''' 

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

468 

469 @Property_RO 

470 def groundrange(self): 

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

472 ''' 

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

474 

475 @deprecated_Property_RO 

476 def length(self): 

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

478 return self.slantrange 

479 

480 @deprecated_Property_RO 

481 def ned(self): 

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

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

484 

485 @Property_RO 

486 def ned4(self): 

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

488 ''' 

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

490 

491 @Property_RO 

492 def north(self): 

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

494 ''' 

495 return self._north 

496 

497 @Property_RO 

498 def slantrange(self): 

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

500 ''' 

501 return self.aer4.slantrange 

502 

503 @deprecated_method 

504 def to3ned(self): # PYCHOK no cover 

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

506 return self.ned # XXX deprecated too 

507 

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

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

510 

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

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

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

514 

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

516 ''' 

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

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

519 

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

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

522 

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

524 number of (decimal) digits, unstripped 

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

526 backets format (C{str}) and separator 

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

528 

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

530 ''' 

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

532 return t 

533 

534 @deprecated_method 

535 def toVector3d(self): 

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

537 return self.xyz 

538 

539 @Property_RO 

540 def up(self): 

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

542 ''' 

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

544 

545 @Property_RO 

546 def x(self): 

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

548 ''' 

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

550 

551 @Property_RO 

552 def xyz4(self): 

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

554 ''' 

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

556 

557 @Property_RO 

558 def y(self): 

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

560 ''' 

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

562 

563 @Property_RO 

564 def z(self): 

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

566 ''' 

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

568 

569 

570class Ned4Tuple(_Abc4Tuple): 

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

572 ''' 

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

574 _Units_ = ( Meter, Meter, Meter, _Pass) 

575 

576 def _toNed(self, Cls, Cls_kwds): 

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

578 ''' 

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

580 

581 @Property_RO 

582 def xyzLocal(self): 

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

584 ''' 

585 return Ned(self).xyzLocal 

586 

587 

588class _Vector3d(Vector3d): 

589 

590 _toStr = _xyz_ 

591 

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

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

594 

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

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

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

598 

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

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

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

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

603 ''' 

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

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

606 

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

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

609 

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

611 number of (decimal) digits, unstripped 

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

613 backets format (C{str}) and separator 

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

615 

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

617 ''' 

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

619 return t 

620 

621 

622class XyzLocal(_Vector3d): 

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

624 also base class for local L{Enu}. 

625 ''' 

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

627 

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

629 '''New L{XyzLocal}. 

630 

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

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

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

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

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

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

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

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

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

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

641 L{LocalCartesian}). 

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

643 

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

645 

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

647 ''' 

648 if _isMeter(x_xyz): 

649 xyz = None 

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

651 Meter(y=y or _0_0), 

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

653 else: 

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

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

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

657 _init(self, xyz, ltp, name) 

658 

659 def __str__(self): 

660 return self.toStr() 

661 

662 @Property_RO 

663 def aer4(self): 

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

665 ''' 

666 return _xyz2aer4(self) 

667 

668 @Property_RO 

669 def azimuth(self): 

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

671 

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

673 Transformations_between_ECEF_and_ENU_coordinates>}. 

674 ''' 

675 return self.aer4.azimuth 

676 

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

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

679 

680 @arg args: Optional, positional arguments. 

681 @kwarg kwds: Optional, keyword arguments. 

682 

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

684 ''' 

685 kwds = _name1__(kwds, _or_nameof=self) 

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

687 

688 @Property_RO 

689 def down(self): 

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

691 ''' 

692 return Meter(down=-self.z) 

693 

694 @property_RO 

695 def ecef(self): 

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

697 ''' 

698 return self.ltp.ecef 

699 

700 @Property_RO 

701 def east(self): 

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

703 ''' 

704 return Meter(east=self.x) 

705 

706 @Property_RO 

707 def elevation(self): 

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

709 

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

711 Transformations_between_ECEF_and_ENU_coordinates>}. 

712 ''' 

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

714 

715 @Property_RO 

716 def enu4(self): 

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

718 ''' 

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

720 

721 @Property_RO 

722 def groundrange(self): 

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

724 ''' 

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

726 

727 @Property_RO 

728 def ltp(self): 

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

730 ''' 

731 return self._ltp 

732 

733 def _ltp_kwds_name3(self, ltp, kwds): 

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

735 ''' 

736 ltp = _ltp._xLtp(ltp, self.ltp) 

737 kwds = _name1__(kwds, _or_nameof=self) 

738 kwds = _name1__(kwds, _or_nameof=ltp) 

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

740 

741 @Property_RO 

742 def ned4(self): 

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

744 ''' 

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

746 

747 @Property_RO 

748 def north(self): 

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

750 ''' 

751 return Meter(north=self.y) 

752 

753 @Property_RO 

754 def slantrange(self): 

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

756 ''' 

757 return self.aer4.slantrange 

758 

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

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

761 

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

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

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

765 is None}. 

766 

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

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

769 

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

771 ''' 

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

773 

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

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

776 

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

778 or C{None}. 

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

780 this C{ltp}. 

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

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

783 C{B{Cartesian} is None}. 

784 

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

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

787 C{M=None}, always. 

788 

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

790 ''' 

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

792 if Cartesian is None: 

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

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

795 else: 

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

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

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

799 return r 

800 

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

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

803 

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

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

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

807 is None}. 

808 

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

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

811 

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

813 ''' 

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

815 

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

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

818 

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

820 C{None}. 

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

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

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

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

825 is None}. 

826 

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

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

829 

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

831 ''' 

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

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

834 if LatLon is None: 

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

836 else: 

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

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

839 return r 

840 

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

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

843 

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

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

846 

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

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

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

850 ''' 

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

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

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

854 t.lat, t.lon, t.height, 

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

856 

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

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

859 

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

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

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

863 is None}. 

864 

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

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

867 

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

869 ''' 

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

871 

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

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

874 

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

876 or C{None}. 

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

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

879 is None}. 

880 

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

882 C{B{Xyz} is None}. 

883 

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

885 ''' 

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

887 

888 @Property_RO 

889 def up(self): 

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

891 ''' 

892 return Meter(up=self.z) 

893 

894# @Property_RO 

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

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

897# ''' 

898# return self._x 

899 

900# @Property_RO 

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

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

903# ''' 

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

905 

906 @Property_RO 

907 def xyz4(self): 

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

909 ''' 

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

911 

912 @Property_RO 

913 def xyzLocal(self): 

914 '''Get this L{XyzLocal}. 

915 ''' 

916 return self 

917 

918# @Property_RO 

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

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

921# ''' 

922# return self._y 

923 

924# @Property_RO 

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

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

927# ''' 

928# return self._z 

929 

930 

931class Xyz4Tuple(_Abc4Tuple): 

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

933 ''' 

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

935 _Units_ = ( Meter, Meter, Meter, _Pass) 

936 

937 def _toXyz(self, Cls, Cls_kwds): 

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

939 ''' 

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

941 

942 @property_RO 

943 def xyz4(self): 

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

945 ''' 

946 return self 

947 

948 @Property_RO 

949 def xyzLocal(self): 

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

951 ''' 

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

953 

954 

955class Enu(XyzLocal): 

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

957 

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

959 Transformations_between_ECEF_and_ENU_coordinates>} coordinates. 

960 ''' 

961 _toStr = _enu_ 

962 

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

964 '''New L{Enu}. 

965 

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

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

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

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

970 @kwarg north: Scalar North component (C{meter}), iff B{C{east_enu}} 

971 is C{meter}, ignored otherwise. 

972 @kwarg up: Scalar Up component (C{meter}, normal from the surface 

973 of the ellipsoid or sphere), iff B{C{east_enu}} is 

974 C{meter}, ignored otherwise. 

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

976 L{LocalCartesian}). 

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

978 

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

980 

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

982 ''' 

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

984 

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

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

987 

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

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

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

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

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

993 is None}. 

994 

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

996 is None}. 

997 

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

999 

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

1001 ''' 

1002 try: 

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

1004 except Exception as x: 

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

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

1007 

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

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

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

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

1012 

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

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

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

1016 

1017 @Property_RO 

1018 def xyzLocal(self): 

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

1020 ''' 

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

1022 

1023 

1024class Enu4Tuple(_Abc4Tuple): 

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

1026 ''' 

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

1028 _Units_ = ( Meter, Meter, Meter, _Pass) 

1029 

1030 def _toEnu(self, Cls, Cls_kwds): 

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

1032 ''' 

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

1034 

1035 @Property_RO 

1036 def xyzLocal(self): 

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

1038 ''' 

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

1040 

1041 

1042class Local9Tuple(_NamedTuple): 

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

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

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

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

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

1048 ''' 

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

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

1051 

1052 @Property_RO 

1053 def azimuth(self): 

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

1055 ''' 

1056 return self.xyzLocal.aer4.azimuth 

1057 

1058 @Property_RO 

1059 def down(self): 

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

1061 ''' 

1062 return -self.z 

1063 

1064 @Property_RO 

1065 def east(self): 

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

1067 ''' 

1068 return self.x 

1069 

1070 @Property_RO 

1071 def elevation(self): 

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

1073 ''' 

1074 return self.xyzLocal.aer4.elevation 

1075 

1076 @Property_RO 

1077 def groundrange(self): 

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

1079 ''' 

1080 return self.xyzLocal.aer4.groundrange 

1081 

1082 @Property_RO 

1083 def lam(self): 

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

1085 ''' 

1086 return self.philam.lam 

1087 

1088 @Property_RO 

1089 def latlon(self): 

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

1091 ''' 

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

1093 

1094 @Property_RO 

1095 def latlonheight(self): 

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

1097 ''' 

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

1099 

1100 @Property_RO 

1101 def north(self): 

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

1103 ''' 

1104 return self.y 

1105 

1106 @Property_RO 

1107 def phi(self): 

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

1109 ''' 

1110 return self.philam.phi 

1111 

1112 @Property_RO 

1113 def philam(self): 

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

1115 ''' 

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

1117 

1118 @Property_RO 

1119 def philamheight(self): 

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

1121 ''' 

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

1123 

1124 @Property_RO 

1125 def slantrange(self): 

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

1127 ''' 

1128 return self.xyzLocal.aer4.slantrange 

1129 

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

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

1132 

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

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

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

1136 is None}. 

1137 

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

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

1140 

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

1142 ''' 

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

1144 

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

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

1147 

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

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

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

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

1152 C{B{Cartesian} is None}. 

1153 

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

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

1156 

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

1158 ''' 

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

1160 

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

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

1163 

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

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

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

1167 is None}. 

1168 

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

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

1171 

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

1173 ''' 

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

1175 

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

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

1178 

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

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

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

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

1183 C{B{LatLon} is None}. 

1184 

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

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

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

1188 is specified or not. 

1189 

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

1191 ''' 

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

1193 

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

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

1196 

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

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

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

1200 is None}. 

1201 

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

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

1204 

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

1206 ''' 

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

1208 

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

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

1211 

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

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

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

1215 is None}. 

1216 

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

1218 C{B{Xyz} is None}. 

1219 

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

1221 ''' 

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

1223 

1224 @Property_RO 

1225 def up(self): 

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

1227 ''' 

1228 return self.z 

1229 

1230 @Property_RO 

1231 def xyz(self): 

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

1233 ''' 

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

1235 

1236 @Property_RO 

1237 def xyzLocal(self): 

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

1239 ''' 

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

1241 

1242 

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

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

1245 

1246 

1247class Uvw(_Vector3d): 

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

1249 ''' 

1250 _toStr = _uvw_ 

1251 

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

1253 '''New L{Uvw}. 

1254 

1255 @arg u_uvw: Scalar U component (C{meter}) or a previous instance (L{Uvw}, 

1256 L{Uvw3Tuple}, L{Vector3d}). 

1257 @kwarg v: V component (C{meter}), iff B{C{u_uvw}} is C{meter}, ignored 

1258 otherwise. 

1259 @kwarg w: W component (C{meter}), iff B{C{u_uvw}} is C{meter}, ignored 

1260 otherwise. 

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

1262 

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

1264 

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

1266 ''' 

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

1268 

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

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

1271 

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

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

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

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

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

1277 is None}. 

1278 

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

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

1281 

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

1283 

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

1285 ''' 

1286 try: 

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

1288 except Exception as x: 

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

1290 u, v, w = self.uvw 

1291 

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

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

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

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

1296 

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

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

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

1300 

1301 u = Vector3d.x 

1302 

1303 @Property_RO 

1304 def uvw(self): 

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

1306 ''' 

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

1308 

1309 v = Vector3d.y 

1310 w = Vector3d.z 

1311 

1312 

1313class Uvw3Tuple(_NamedTuple): 

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

1315 ''' 

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

1317 _Units_ = ( Meter, Meter, Meter) 

1318 

1319 

1320class Los(Aer): 

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

1322 ''' 

1323 

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

1325 '''New L{Los}. 

1326 

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

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

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

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

1331 is 0, zenith +90, nadir -90), if B{C{azimuth_aer}} is 

1332 C{degrees}, ignored otherwise. 

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

1334 

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

1336 

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

1338 ''' 

1339 t = Aer(azimuth_aer, elevation) 

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

1341 

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

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

1344 

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

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

1347 

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

1349 ''' 

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

1351 

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

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

1354 

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

1356 ''' 

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

1358 

1359 

1360class ChLV9Tuple(Local9Tuple): 

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

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

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

1364 otherwise like L{Local9Tuple}. 

1365 ''' 

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

1367 

1368 @Property_RO 

1369 def E_LV95(self): 

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

1371 ''' 

1372 return self.EN2_LV95.E_LV95 

1373 

1374 @Property_RO 

1375 def EN2_LV95(self): 

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

1377 ''' 

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

1379 

1380 @Property_RO 

1381 def h_LV03(self): 

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

1383 ''' 

1384 return self.h_ 

1385 

1386 @Property_RO 

1387 def h_LV95(self): 

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

1389 ''' 

1390 return self.h_ 

1391 

1392 @property_RO 

1393 def isChLV(self): 

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

1395 ''' 

1396 return self.ltp.__class__ is _ltp.ChLV 

1397 

1398 @property_RO 

1399 def isChLVa(self): 

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

1401 ''' 

1402 return self.ltp.__class__ is _ltp.ChLVa 

1403 

1404 @property_RO 

1405 def isChLVe(self): 

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

1407 ''' 

1408 return self.ltp.__class__ is _ltp.ChLVe 

1409 

1410 @Property_RO 

1411 def N_LV95(self): 

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

1413 ''' 

1414 return self.EN2_LV95.N_LV95 

1415 

1416 @Property_RO 

1417 def x(self): 

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

1419 ''' 

1420 return self.Y 

1421 

1422 @Property_RO 

1423 def x_LV03(self): 

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

1425 ''' 

1426 return self.yx2_LV03.x_LV03 

1427 

1428 @Property_RO 

1429 def y(self): 

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

1431 ''' 

1432 return self.X 

1433 

1434 @Property_RO 

1435 def y_LV03(self): 

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

1437 ''' 

1438 return self.yx2_LV03.y_LV03 

1439 

1440 @Property_RO 

1441 def YX(self): 

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

1443 ''' 

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

1445 

1446 @Property_RO 

1447 def yx2_LV03(self): 

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

1449 ''' 

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

1451 

1452 @Property_RO 

1453 def z(self): 

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

1455 ''' 

1456 return self.h_ 

1457 

1458 

1459class ChLVYX2Tuple(_NamedTuple): 

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

1461 in C{meter}. 

1462 ''' 

1463 _Names_ = (_Y_, _X_) 

1464 _Units_ = ( Meter, Meter) 

1465 

1466 def false2(self, LV95=True): 

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

1468 

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

1470 ''' 

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

1472 

1473 

1474class ChLVEN2Tuple(_NamedTuple): 

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

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

1477 ''' 

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

1479 _Units_ = ChLVYX2Tuple._Units_ 

1480 

1481 def unfalse2(self): 

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

1483 

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

1485 ''' 

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

1487 

1488 

1489class ChLVyx2Tuple(_NamedTuple): 

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

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

1492 ''' 

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

1494 _Units_ = ChLVYX2Tuple._Units_ 

1495 

1496 def unfalse2(self): 

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

1498 

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

1500 ''' 

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

1502 

1503 

1504class Footprint5Tuple(_NamedTuple): 

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

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

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

1508 

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

1510 ''' 

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

1512 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass) 

1513 

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

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

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

1517 

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

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

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

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

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

1523 is None}. 

1524 

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

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

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

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

1529 

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

1531 

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

1533 ''' 

1534 ltp = _ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center 

1535 kwds = _name1__(name_LatLon_kwds, _or_nameof=self) 

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

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

1538 

1539 def xyzLocal5(self, ltp=None): 

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

1541 

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

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

1544 

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

1546 

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

1548 ''' 

1549 if ltp is None: 

1550 p = self 

1551 else: 

1552 p = _ltp._xLtp(ltp) 

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

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

1555 

1556 

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

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

1559 ''' 

1560 return _ltp.ChLV.false2(Y, X, **LV95_name) 

1561 

1562 

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

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

1565 ''' 

1566 return _ltp.ChLV.unfalse2(e, n, **LV95_name) 

1567 

1568 

1569def _er2gr(e, r): 

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

1571 ''' 

1572 c = cos(radians(e)) 

1573 return Meter_(groundrange=r * c) 

1574 

1575 

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

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

1578 ''' 

1579 if abc is None: 

1580 n = _name__(**name) 

1581 else: 

1582 n = abc._name__(name) 

1583 ltp = _xattr(abc, ltp=ltp) 

1584 if ltp: 

1585 inst._ltp = _ltp._xLtp(ltp) 

1586 if n: 

1587 inst.name = n 

1588 

1589 

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

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

1592 ''' 

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

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

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

1596 if sep: 

1597 t = sep.join(t) 

1598 if fmt: 

1599 t = fmt(t) 

1600 return a, t 

1601 

1602 

1603def _xyz2aer4(inst): 

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

1605 ''' 

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

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

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

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

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

1611 

1612 

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

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

1615 ''' 

1616 n, inst = _xkwds_item2(name_inst) 

1617 if isinstance(inst, Types): 

1618 return None 

1619 try: 

1620 return inst.xyzLocal 

1621 except (AttributeError, TypeError): 

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

1623 

1624 

1625__all__ += _ALL_DOCS(_AbcBase) 

1626 

1627# **) MIT License 

1628# 

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

1630# 

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

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

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

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

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

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

1637# 

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

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

1640# 

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

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

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

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

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

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

1647# OTHER DEALINGS IN THE SOFTWARE.