Coverage for pygeodesy/ltpTuples.py: 95%

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} and

7 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, typename

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# rom pygeodesy.internals import typename # from .basics

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

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

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

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

28 _NamedTuple, _Pass, _xnamed

29 from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple

30 from pygeodesy.props import deprecated_method, deprecated_Property_RO, \

31 Property_RO, property_RO

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1613 def _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-2026 -- 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.