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 # from .units

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

16 from pygeodesy.dms import F_D, toDMS

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

18 _xkwds, _xkwds_item2

19 from pygeodesy.fmath import hypot, hypot_

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

21 _down_, _east_, _ecef_, _elevation_, _height_, \

22 _lat_, _lon_, _ltp_, _M_, _north_, _not_, _up_, \

23 _X_, _x_, _xyz_, _Y_, _y_, _z_

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

25 from pygeodesy.named import _NamedBase, _NamedTuple, notOverloaded, \

26 _Pass, _xnamed

27 from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple

28 from pygeodesy.props import deprecated_method, deprecated_Property_RO, \

29 Property_RO, property_RO

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

31 from pygeodesy.units import Bearing, Degrees, Degrees_, Height, _isDegrees, \

32 _isMeter, Lat, Lon, Meter, Meter_, issubclassof

33 from pygeodesy.utily import atan2d, atan2b, sincos2_, sincos2d_

34 from pygeodesy.vector3d import Vector3d

35

36 from math import cos, radians

37

38__all__ = _ALL_LAZY.ltpTuples

39__version__ = '24.03.15'

40

41_aer_ = 'aer'

42_alt_ = 'alt'

43_enu_ = 'enu'

44_h__ = 'h_'

45_ned_ = 'ned'

46_local_ = 'local'

47_roll_ = 'roll'

48_slantrange_ = 'slantrange'

49_tilt_ = 'tilt'

50_uvw_ = 'uvw'

51_yaw_ = 'yaw'

52

53

54 def _er2gr(e, r):

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

56 '''

57 c = cos(radians(e))

58 return Meter_(groundrange=r * c)

59

60

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

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

63 '''

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

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

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

67 if sep:

68 t = sep.join(t)

69 if fmt:

70 t = fmt(t)

71 return a, t

72

73

74 def _4Tuple2Cls(inst, Cls, Cls_kwds):

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

76 '''

77 if Cls is None:

78 return inst

79 elif issubclassof(Cls, Aer):

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

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

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

83 elif issubclassof(Cls, Ned):

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

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

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

87 elif Cls is Local9Tuple: # PYCHOK no cover

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

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

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

91

92

93 def _xyz2aer4(inst):

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

95 '''

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

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

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

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

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

101

102

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

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

105 '''

106 n, inst = _xkwds_item2(name_inst)

107 if isinstance(inst, Types):

108 return None

109 try:

110 return inst.xyzLocal

111 except (AttributeError, TypeError):

112 raise _TypeError(n, inst, txt=_not_(_local_))

113

114

115 class _NamedAerNed(_NamedBase):

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

117 '''

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

119

120 @Property_RO

121 def ltp(self):

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

123 '''

124 return self._ltp

125

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

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

128

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

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

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

132

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

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

135 '''

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

137

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

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

140

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

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

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

144

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

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

147 '''

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

149

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

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

152

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

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

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

156

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

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

159 '''

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

161

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

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

164

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

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

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

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

169

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

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

172

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

174 '''

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

176

177 @Property_RO

178 def xyz(self):

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

180 '''

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

182

183 @property_RO

184 def xyz4(self): # PYCHOK no cover

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

186 notOverloaded(self)

187

188 @Property_RO

189 def xyzLocal(self):

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

191 '''

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

193

194

195 class Aer(_NamedAerNed):

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

197 '''

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

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

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

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

202 _toStr = _aer_

203

204 def __init__(self, azimuth_aer, elevation=0, slantrange=0, ltp=None, name=NN):

205 '''New L{Aer}.

206

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

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

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

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

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

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

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

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

215 B{C{azimuth_aer}}.

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

217 L{LocalCartesian}).

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

219

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

221

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

223 or B{C{slantrange}}.

224 '''

225 if _isDegrees(azimuth_aer):

226 self._azimuth = Bearing(azimuth=azimuth_aer)

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

228 self._slantrange = Meter_(slantrange=slantrange)

229 p, n = ltp, name

230 else: # PYCHOK no cover

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

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

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

234 aer.azimuth, aer.elevation, aer.slantrange

235 p = _xattr(aer, ltp=ltp)

236 n = name or _xattr(aer, name=name)

237

238 if p:

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

240 if name:

241 self.name = n

242

243 @Property_RO

244 def aer4(self):

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

246 '''

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

248

249 @Property_RO

250 def azimuth(self):

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

252 '''

253 return self._azimuth

254

255 @Property_RO

256 def down(self):

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

258 '''

259 return self.xyzLocal.down

260

261 @Property_RO

262 def east(self):

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

264 '''

265 return self.xyzLocal.east

266

267 @Property_RO

268 def elevation(self):

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

270 '''

271 return self._elevation

272

273 @Property_RO

274 def groundrange(self):

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

276 '''

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

278

279 @Property_RO

280 def north(self):

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

282 '''

283 return self.xyzLocal.north

284

285 @Property_RO

286 def slantrange(self):

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

288 '''

289 return self._slantrange

290

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

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

293 (bearing), elevation and slant range.

294

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

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

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

298

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

300 '''

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

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

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

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

305

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

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

308

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

310 number of (decimal) digits, unstripped

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

312 backets format (C{str}) and separator

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

314

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

316 '''

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

318 return t

319

320 @Property_RO

321 def up(self):

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

323 '''

324 return self.xyzLocal.up

325

326 @Property_RO

327 def x(self):

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

329 '''

330 return self.xyz4.x

331

332 @Property_RO

333 def xyz4(self):

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

335 '''

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

337 R = self._slantrange

338 r = cE * R # ground range

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

340

341 @Property_RO

342 def y(self):

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

344 '''

345 return self.xyz4.y

346

347 @Property_RO

348 def z(self):

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

350 '''

351 return self.xyz4.z

352

353

354 class Aer4Tuple(_NamedTuple):

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

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

357 '''

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

359 _Units_ = ( Meter, Meter, Meter, _Pass)

360

361 def _toAer(self, Cls, Cls_kwds):

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

363 '''

364 if issubclassof(Cls, Aer):

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

366 else:

367 return _4Tuple2Cls(self, Cls, Cls_kwds)

368

369 @Property_RO

370 def groundrange(self):

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

372 '''

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

374

375 @Property_RO

376 def xyzLocal(self):

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

378 '''

379 return Aer(self).xyzLocal

380

381

382 class Attitude4Tuple(_NamedTuple):

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

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

385 the attitude of a plane or camera.

386 '''

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

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

389

390 @Property_RO

391 def atyr(self):

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

393 '''

394 return self

395

396 @Property_RO

397 def tyr3d(self):

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

399 '''

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

401

402

403 class Ned(_NamedAerNed):

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

405

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

407 '''

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

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

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

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

412 _toStr = _ned_

413

414 def __init__(self, north_ned, east=0, down=0, ltp=None, name=NN):

415 '''New L{Ned} vector.

416

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

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

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

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

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

422 scalar B{C{north_ned}}.

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

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

425 scalar B{C{north_ned}}.

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

427 L{LocalCartesian}).

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

429

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

431

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

433 '''

434 if _isMeter(north_ned):

435 self._north = Meter(north=north_ned or _0_0)

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

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

438 p, n = ltp, name

439 else: # PYCHOK no cover

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

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

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

443 p = _xattr(ned, ltp=ltp)

444 n = name or _xattr(ned, name=name)

445

446 if p:

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

448 if n:

449 self.name = n

450

451 @Property_RO

452 def aer4(self):

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

454 '''

455 return _xyz2aer4(self)

456

457 @Property_RO

458 def azimuth(self):

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

460 '''

461 return self.aer4.azimuth

462

463 @deprecated_Property_RO

464 def bearing(self):

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

466 return self.azimuth

467

468 @Property_RO

469 def down(self):

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

471 '''

472 return self._down

473

474 @Property_RO

475 def east(self):

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

477 '''

478 return self._east

479

480 @Property_RO

481 def elevation(self):

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

483 '''

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

485

486 @Property_RO

487 def groundrange(self):

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

489 '''

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

491

492 @deprecated_Property_RO

493 def length(self):

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

495 return self.slantrange

496

497 @deprecated_Property_RO

498 def ned(self):

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

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

501

502 @Property_RO

503 def ned4(self):

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

505 '''

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

507

508 @Property_RO

509 def north(self):

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

511 '''

512 return self._north

513

514 @Property_RO

515 def slantrange(self):

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

517 '''

518 return self.aer4.slantrange

519

520 @deprecated_method

521 def to3ned(self): # PYCHOK no cover

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

523 return self.ned # XXX deprecated too

524

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

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

527

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

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

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

531

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

533 '''

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

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

536

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

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

539

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

541 number of (decimal) digits, unstripped

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

543 backets format (C{str}) and separator

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

545

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

547 '''

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

549 return t

550

551 @deprecated_method

552 def toVector3d(self):

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

554 return self.xyz

555

556 @Property_RO

557 def up(self):

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

559 '''

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

561

562 @Property_RO

563 def x(self):

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

565 '''

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

567

568 @Property_RO

569 def xyz4(self):

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

571 '''

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

573

574 @Property_RO

575 def y(self):

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

577 '''

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

579

580 @Property_RO

581 def z(self):

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

583 '''

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

585

586

587 class Ned4Tuple(_NamedTuple):

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

589 '''

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

591 _Units_ = ( Meter, Meter, Meter, _Pass)

592

593 def _toNed(self, Cls, Cls_kwds):

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

595 '''

596 if issubclassof(Cls, Ned):

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

598 else:

599 return _4Tuple2Cls(self, Cls, Cls_kwds)

600

601 @Property_RO

602 def xyzLocal(self):

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

604 '''

605 return Ned(self).xyzLocal

606

607

608 class _Vector3d(Vector3d):

609

610 _toStr = _xyz_

611

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

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

614

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

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

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

618

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

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

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

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

623 '''

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

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

626

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

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

629

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

631 number of (decimal) digits, unstripped

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

633 backets format (C{str}) and separator

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

635

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

637 '''

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

639 return t

640

641

642 class XyzLocal(_Vector3d):

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

644 also base class for local L{Enu}.

645 '''

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

647

648 def __init__(self, x_xyz, y=0, z=0, ltp=None, name=NN):

649 '''New L{XyzLocal}.

650

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

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

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

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

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

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

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

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

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

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

661 L{LocalCartesian}).

662

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

664

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

666 '''

667 if _isMeter(x_xyz):

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

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

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

671 p, n = ltp, name

672 else:

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

674 self._x, self._y, self._z = xyz.x, xyz.y, xyz.z

675 p = _xattr(xyz, ltp=ltp)

676 n = name or _xattr(xyz, name=NN)

677

678 if p:

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

680 if n:

681 self.name = n

682

683 def __str__(self):

684 return self.toStr()

685

686 @Property_RO

687 def aer4(self):

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

689 '''

690 return _xyz2aer4(self)

691

692 @Property_RO

693 def azimuth(self):

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

695

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

697 Transformations_between_ECEF_and_ENU_coordinates>}.

698 '''

699 return self.aer4.azimuth

700

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

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

703

704 @arg args: Optional, positional arguments.

705 @kwarg kwds: Optional, keyword arguments.

706

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

708 '''

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

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

711

712 @Property_RO

713 def down(self):

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

715 '''

716 return Meter(down=-self.z)

717

718 @property_RO

719 def ecef(self):

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

721 '''

722 return self.ltp.ecef

723

724 @Property_RO

725 def east(self):

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

727 '''

728 return Meter(east=self.x)

729

730 @Property_RO

731 def elevation(self):

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

733

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

735 Transformations_between_ECEF_and_ENU_coordinates>}.

736 '''

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

738

739 @Property_RO

740 def enu4(self):

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

742 '''

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

744

745 @Property_RO

746 def groundrange(self):

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

748 '''

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

750

751 @Property_RO

752 def ltp(self):

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

754 '''

755 return self._ltp

756

757 @Property_RO

758 def ned4(self):

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

760 '''

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

762

763 @Property_RO

764 def north(self):

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

766 '''

767 return Meter(north=self.y)

768

769 @Property_RO

770 def slantrange(self):

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

772 '''

773 return self.aer4.slantrange

774

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

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

777

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

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

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

781

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

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

784

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

786 '''

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

788

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

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

791

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

793 or C{None}.

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

795 overriding this C{ltp}.

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

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

798

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

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

801 with C{M=None}, always.

802

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

804 B{C{Cartesian_kwds}} argument.

805 '''

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

807 if Cartesian is None:

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

809 else:

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

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

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

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

814

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

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

817

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

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

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

821

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

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

824 '''

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

826

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

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

829

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

831 or C{None}.

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

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

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

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

836

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

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

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

840

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

842 B{C{LatLon_kwds}} argument.

843 '''

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

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

846 if LatLon is None:

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

848 else:

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

850 name=self.name or ltp.name)

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

852 return r

853

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

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

856

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

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

859

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

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

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

863 '''

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

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

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

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

868

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

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

871

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

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

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

875

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

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

878 '''

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

880

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

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

883

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

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

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

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

888

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

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

891 '''

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

893

894 @Property_RO

895 def up(self):

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

897 '''

898 return Meter(up=self.z)

899

900# @Property_RO

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

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

903# '''

904# return self._x

905

906# @Property_RO

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

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

909# '''

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

911

912 @Property_RO

913 def xyz4(self):

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

915 '''

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

917

918 @Property_RO

919 def xyzLocal(self):

920 '''Get this L{XyzLocal}.

921 '''

922 return self

923

924# @Property_RO

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

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

927# '''

928# return self._y

929

930# @Property_RO

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

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

933# '''

934# return self._z

935

936

937 class Xyz4Tuple(_NamedTuple):

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

939 '''

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

941 _Units_ = ( Meter, Meter, Meter, _Pass)

942

943 def _toXyz(self, Cls, Cls_kwds):

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

945 '''

946 if issubclassof(Cls, XyzLocal):

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

948 else:

949 return _4Tuple2Cls(self, Cls, Cls_kwds)

950

951 @Property_RO

952 def xyzLocal(self):

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

954 '''

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

956

957

958 class Enu(XyzLocal):

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

960

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

962 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.

963 '''

964 _toStr = _enu_

965

966 def __init__(self, east_enu, north=0, up=0, ltp=None, name=NN):

967 '''New L{Enu}.

968

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

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

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

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

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

974 scalar B{C{east_enu}}.

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

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

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

978 L{LocalCartesian}).

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

980

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

982

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

984 '''

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

986

987 def toUvw(self, location, Uvw=None, **Uvw_kwds):

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

989

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

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

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

993 @kwarg Uvw_kwds: Optional, additional B{L{Uvw}} keyword

994 arguments, ignored if C{B{Uvw} is None}.

995

996 @return: UVW as a L{Uvw} instance or if C{B{Uvw} is None}, a

997 L{Uvw3Tuple}C{(u, v, w)}.

998

999 @raise TypeError: InvalidB{C{location}}.

1000

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

1002 '''

1003 try:

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

1005 except Exception as x:

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

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

1008

1009 t = ca * u - sa * n

1010 U = cb * t - sb * e

1011 V = cb * e + sb * t

1012 W = ca * n + sa * u

1013 return Uvw3Tuple(U, V, W, name=self.name) if Uvw is None else \

1014 Uvw( U, V, W, **_xkwds(Uvw_kwds, name=self.name))

1015

1016 @Property_RO

1017 def xyzLocal(self):

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

1019 '''

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

1021

1022

1023 class Enu4Tuple(_NamedTuple):

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

1025 '''

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

1027 _Units_ = ( Meter, Meter, Meter, _Pass)

1028

1029 def _toEnu(self, Cls, Cls_kwds):

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

1031 '''

1032 if issubclassof(Cls, XyzLocal):

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

1034 else:

1035 return _4Tuple2Cls(self, Cls, Cls_kwds)

1036

1037 @Property_RO

1038 def xyzLocal(self):

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

1040 '''

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

1042

1043

1044 class Local9Tuple(_NamedTuple):

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

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

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

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

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

1050 '''

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

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

1053

1054 @Property_RO

1055 def azimuth(self):

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

1057 '''

1058 return self.xyzLocal.aer4.azimuth

1059

1060 @Property_RO

1061 def down(self):

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

1063 '''

1064 return -self.z

1065

1066 @Property_RO

1067 def east(self):

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

1069 '''

1070 return self.x

1071

1072 @Property_RO

1073 def elevation(self):

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

1075 '''

1076 return self.xyzLocal.aer4.elevation

1077

1078 @Property_RO

1079 def groundrange(self):

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

1081 '''

1082 return self.xyzLocal.aer4.groundrange

1083

1084 @Property_RO

1085 def lam(self):

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

1087 '''

1088 return self.philam.lam

1089

1090 @Property_RO

1091 def latlon(self):

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

1093 '''

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

1095

1096 @Property_RO

1097 def latlonheight(self):

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

1099 '''

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

1101

1102 @Property_RO

1103 def north(self):

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

1105 '''

1106 return self.y

1107

1108 @Property_RO

1109 def phi(self):

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

1111 '''

1112 return self.philam.phi

1113

1114 @Property_RO

1115 def philam(self):

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

1117 '''

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

1119

1120 @Property_RO

1121 def philamheight(self):

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

1123 '''

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

1125

1126 @Property_RO

1127 def slantrange(self):

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

1129 '''

1130 return self.xyzLocal.aer4.slantrange

1131

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

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

1134

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

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

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

1138

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

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

1141 '''

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

1143

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

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

1146

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

1148 or C{None}.

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

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

1151

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

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

1154

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

1156 argument.

1157 '''

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

1159

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

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

1162

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

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

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

1166

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

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

1169 '''

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

1171

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

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

1174

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

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

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

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

1179

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

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

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

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

1184

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

1186 argument.

1187 '''

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

1189

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

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

1192

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

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

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

1196

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

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

1199 '''

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

1201

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

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

1204

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

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

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

1208

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

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

1211 '''

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

1213

1214 @Property_RO

1215 def up(self):

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

1217 '''

1218 return self.z

1219

1220 @Property_RO

1221 def xyz(self):

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

1223 '''

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

1225

1226 @Property_RO

1227 def xyzLocal(self):

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

1229 '''

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

1231

1232

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

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

1235

1236

1237 class Uvw(_Vector3d):

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

1239 '''

1240 _toStr = _uvw_

1241

1242 def __init__(self, u_uvw, v=0, w=0, name=NN):

1243 '''New L{Uvw}.

1244

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

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

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

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

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

1250

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

1252

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

1254 '''

1255 Vector3d.__init__(self, u_uvw, v, w, name=name)

1256

1257 def toEnu(self, location, Enu=Enu, **Enu_kwds):

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

1259

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

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

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

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

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

1265

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

1267 L{Enu4Tuple}C{(east, north, up, ltp)} with C{ltp=None}.

1268

1269 @raise TypeError: InvalidB{C{location}}.

1270

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

1272 '''

1273 try:

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

1275 except Exception as x:

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

1277 u, v, w = self.uvw

1278

1279 t = cb * u + sb * v

1280 E = cb * v - sb * u

1281 N = ca * w - sa * t

1282 U = ca * t + sa * w

1283 return Enu4Tuple(E, N, U, name=self.name) if Enu is None else \

1284 Enu( E, N, U, **_xkwds(Enu_kwds, name=self.name))

1285

1286 u = Vector3d.x

1287

1288 @Property_RO

1289 def uvw(self):

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

1291 '''

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

1293

1294 v = Vector3d.y

1295 w = Vector3d.z

1296

1297

1298 class Uvw3Tuple(_NamedTuple):

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

1300 '''

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

1302 _Units_ = ( Meter, Meter, Meter)

1303

1304

1305 class Los(Aer):

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

1307 '''

1308

1309 def __init__(self, azimuth_aer, elevation=0, name=NN):

1310 '''New L{Los}.

1311

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

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

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

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

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

1317 B{C{azimuth_aer}}.

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

1319

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

1321

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

1323 '''

1324 t = Aer(azimuth_aer, elevation)

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

1326

1327 def toUvw(self, location, Uvw=Uvw, **Uvw_kwds):

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

1329

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

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

1332

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

1334 '''

1335 return self.toEnu().toUvw(location, Uvw=Uvw, **Uvw_kwds)

1336

1337 def toEnu(self, Enu=Enu, **Enu_kwds):

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

1339

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

1341 '''

1342 return Aer.toEnu(self, Enu=Enu, **Enu_kwds)

1343

1344

1345 class ChLV9Tuple(Local9Tuple):

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

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

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

1349 otherwise like L{Local9Tuple}.

1350 '''

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

1352

1353 @Property_RO

1354 def E_LV95(self):

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

1356 '''

1357 return self.EN2_LV95.E_LV95

1358

1359 @Property_RO

1360 def EN2_LV95(self):

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

1362 '''

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

1364

1365 @Property_RO

1366 def h_LV03(self):

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

1368 '''

1369 return self.h_

1370

1371 @Property_RO

1372 def h_LV95(self):

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

1374 '''

1375 return self.h_

1376

1377 @property_RO

1378 def isChLV(self):

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

1380 '''

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

1382

1383 @property_RO

1384 def isChLVa(self):

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

1386 '''

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

1388

1389 @property_RO

1390 def isChLVe(self):

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

1392 '''

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

1394

1395 @Property_RO

1396 def N_LV95(self):

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

1398 '''

1399 return self.EN2_LV95.N_LV95

1400

1401 @Property_RO

1402 def x(self):

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

1404 '''

1405 return self.Y

1406

1407 @Property_RO

1408 def x_LV03(self):

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

1410 '''

1411 return self.yx2_LV03.x_LV03

1412

1413 @Property_RO

1414 def y(self):

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

1416 '''

1417 return self.X

1418

1419 @Property_RO

1420 def y_LV03(self):

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

1422 '''

1423 return self.yx2_LV03.y_LV03

1424

1425 @Property_RO

1426 def YX(self):

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

1428 '''

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

1430

1431 @Property_RO

1432 def yx2_LV03(self):

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

1434 '''

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

1436

1437 @Property_RO

1438 def z(self):

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

1440 '''

1441 return self.h_

1442

1443

1444 class ChLVYX2Tuple(_NamedTuple):

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

1446 in C{meter}.

1447 '''

1448 _Names_ = (_Y_, _X_)

1449 _Units_ = ( Meter, Meter)

1450

1451 def false2(self, LV95=True):

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

1453

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

1455 '''

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

1457

1458

1459 class ChLVEN2Tuple(_NamedTuple):

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

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

1462 '''

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

1464 _Units_ = ChLVYX2Tuple._Units_

1465

1466 def unfalse2(self):

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

1468

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

1470 '''

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

1472

1473

1474 class ChLVyx2Tuple(_NamedTuple):

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

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

1477 '''

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

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 _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)

1487

1488

1489 class Footprint5Tuple(_NamedTuple):

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

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

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

1493

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

1495 '''

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

1497 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)

1498

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

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

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

1502

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

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

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

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

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

1508

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

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

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

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

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

1514

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

1516

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

1518 '''

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

1520 LatLon=LatLon, name=self.name,)

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

1522

1523 def xyzLocal5(self, ltp=None):

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

1525

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

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

1528

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

1530

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

1532 '''

1533 if ltp is None:

1534 p = self

1535 else:

1536 p = _MODS.ltp._xLtp(ltp)

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

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

1539

1540

1541__all__ += _ALL_DOCS(_NamedAerNed)

1542

1543# **) MIT License

1544#

1545# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

1546#

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

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

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

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

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

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

1553#

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

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

1556#

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

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

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

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

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

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

1563# OTHER DEALINGS IN THE SOFTWARE.