1

2# -*- coding: utf-8 -*-

3

4 u'''Named, I{Local Tangent Plane} (LTP) tuples.

5

6 Local coordinate classes L{XyzLocal}, L{Enu}, L{Ned} and L{Aer}

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

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

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

10

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

12'''

13

14# from pygeodesy.basics import issubclassof # _MODS

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

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

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

18 _xkwds_item2

19 from pygeodesy.fmath import fdot_, hypot, hypot_

20 from 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_

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

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

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

27 _NamedTuple, _Pass, _xnamed

28 from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple

29 from pygeodesy.props import deprecated_method, deprecated_Property_RO, \

30 Property_RO, property_RO

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

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

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

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

35 from 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

58 class _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

154 class _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

189 class 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

343 class 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

368 class 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

389 class 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

568 class 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

586 class _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

620 class 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

929 class 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

953 class 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

1021 class 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

1039 class 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

1244 class 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

1308 class 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

1315 class 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

1355 class 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

1454 class 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

1469 class 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

1484 class 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

1499 class 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

1552 def _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

1558 def _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

1564 def _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

1571 def _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

1585 def _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

1598 def _xLtp(ltp, *dflt):

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

1600 '''

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

1602

1603

1604 def _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

1614 def _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.