1

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

3

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

5

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

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

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

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

10

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

12'''

13

14# from pygeodesy.basics import issubclassof # _MODS

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

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

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

18 _xkwds_item2

19 from pygeodesy.fmath import hypot, hypot_

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

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

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

23 _Y_, _y_, _z_

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

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

26 _NamedTuple, _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 Azimuth, Bearing, Degrees, Degrees_, Height, \

32 _isDegrees, _isMeter, Lat, Lon, Meter, Meter_

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

34 from pygeodesy.vector3d import Vector3d

35

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

37

38__all__ = _ALL_LAZY.ltpTuples

39__version__ = '24.08.18'

40

41_aer_ = 'aer'

42_alt_ = 'alt'

43_down_ = 'down'

44_east_ = 'east'

45_enu_ = 'enu'

46_h__ = 'h_'

47_ltp = _MODS.into(ltp=__name__)

48_ned_ = 'ned'

49_north_ = 'north'

50_local_ = 'local'

51_roll_ = 'roll'

52_slantrange_ = 'slantrange'

53_tilt_ = 'tilt'

54_uvw_ = 'uvw'

55_yaw_ = 'yaw'

56

57

58 def _er2gr(e, r):

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

60 '''

61 c = cos(radians(e))

62 return Meter_(groundrange=r * c)

63

64

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

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

67 '''

68 if abc is None:

69 n = _name__(**name)

70 else:

71 n = abc._name__(name)

72 ltp = _xattr(abc, ltp=ltp)

73 if ltp:

74 inst._ltp = _ltp._xLtp(ltp)

75 if n:

76 inst.name = n

77

78

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

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

81 '''

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

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

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

85 if sep:

86 t = sep.join(t)

87 if fmt:

88 t = fmt(t)

89 return a, t

90

91

92 def _xyz2aer4(inst):

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

94 '''

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

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

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

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

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

100

101

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

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

104 '''

105 n, inst = _xkwds_item2(name_inst)

106 if isinstance(inst, Types):

107 return None

108 try:

109 return inst.xyzLocal

110 except (AttributeError, TypeError):

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

112

113

114 class _AbcBase(_NamedBase):

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

116 '''

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

118

119 @Property_RO

120 def ltp(self):

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

122 '''

123 return self._ltp

124

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

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

127

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

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

130 additional B{L{Aer}} keyword arguments, ignored if

131 C{B{Aer} is None}.

132

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

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

135

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

137 '''

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

139

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

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

142

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

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

145 additional B{L{Enu}} keyword arguments, ignored if

146 C{B{Enu} is None}.

147

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

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

150

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

152 '''

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

154

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

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

157

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

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

160 additional B{L{Ned}} keyword arguments, ignored if

161 C{B{Ned} is None}.

162

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

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

165

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

167 '''

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

169

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

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

172

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

174 or C{None}.

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

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

177 C{B{Xyz} is None}.

178

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

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

181

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

183 '''

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

185

186 @Property_RO

187 def xyz(self):

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

189 '''

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

191

192 @property_RO

193 def xyz3(self):

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

195 '''

196 return tuple(self.xyz)

197

198 @property_RO

199 def xyz4(self): # PYCHOK no cover

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

201 self._notOverloaded()

202

203 @Property_RO

204 def xyzLocal(self):

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

206 '''

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

208

209

210 class _Abc4Tuple(_NamedTuple):

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

212 C{Ned4Tuple} and C{Xyz4Tuple}.

213 '''

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

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

216 '''

217 kwds = _name1__(Cls_kwds, _or_nameof=self)

218 _is = _MODS.basics.issubclassof

219 if Cls is None:

220 n, _ = _name2__(Cls_kwds)

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

222 elif _is(Cls, Abc):

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

224 elif _is(Cls, Aer):

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

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

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

228 elif _is(Cls, Ned):

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

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

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

232 elif Cls is Local9Tuple: # PYCHOK no cover

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

234 else: # PYCHOK no cover

235 n = Abc.__name__[:3]

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

237 return r

238

239 @property_RO

240 def xyzLocal(self): # PYCHOK no cover

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

242 self._notOverloaded()

243

244

245 class Aer(_AbcBase):

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

247 '''

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

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

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

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

252 _toStr = _aer_

253

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

255 '''New L{Aer}.

256

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

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

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

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

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

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

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

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

265 B{C{azimuth_aer}}.

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

267 L{LocalCartesian}).

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

269

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

271

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

273 or B{C{slantrange}}.

274 '''

275 if _isDegrees(azimuth_aer):

276 aer = None

277 t = (Azimuth(azimuth_aer),

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

279 Meter_(slantrange=slantrange), ltp)

280 else: # PYCHOK no cover

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

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

283 t = aer.aer4

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

285 _init(self, aer, ltp, name)

286

287 @Property_RO

288 def aer4(self):

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

290 '''

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

292

293 @Property_RO

294 def azimuth(self):

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

296 '''

297 return self._azimuth

298

299 @Property_RO

300 def down(self):

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

302 '''

303 return self.xyzLocal.down

304

305 @Property_RO

306 def east(self):

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

308 '''

309 return self.xyzLocal.east

310

311 @Property_RO

312 def elevation(self):

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

314 '''

315 return self._elevation

316

317 @Property_RO

318 def groundrange(self):

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

320 '''

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

322

323 @Property_RO

324 def north(self):

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

326 '''

327 return self.xyzLocal.north

328

329 @Property_RO

330 def slantrange(self):

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

332 '''

333 return self._slantrange

334

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

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

337 (bearing), elevation and slant range.

338

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

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

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

342

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

344 '''

345 m = _MODS.dms

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

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

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

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

350

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

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

353

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

355 number of (decimal) digits, unstripped

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

357 backets format (C{str}) and separator

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

359

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

361 '''

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

363 return t

364

365 @Property_RO

366 def up(self):

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

368 '''

369 return self.xyzLocal.up

370

371 @Property_RO

372 def x(self):

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

374 '''

375 return self.xyz4.x

376

377 @Property_RO

378 def xyz4(self):

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

380 '''

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

382 R = self._slantrange

383 r = cE * R # ground range

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

385

386 @Property_RO

387 def y(self):

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

389 '''

390 return self.xyz4.y

391

392 @Property_RO

393 def z(self):

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

395 '''

396 return self.xyz4.z

397

398

399 class Aer4Tuple(_Abc4Tuple):

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

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

402 '''

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

404 _Units_ = ( Meter, Meter, Meter, _Pass)

405

406 def _toAer(self, Cls, Cls_kwds):

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

408 '''

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

410

411 @Property_RO

412 def groundrange(self):

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

414 '''

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

416

417 @Property_RO

418 def xyzLocal(self):

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

420 '''

421 return Aer(self).xyzLocal

422

423

424 class Attitude4Tuple(_NamedTuple):

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

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

427 the attitude of a plane or camera.

428 '''

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

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

431

432 @Property_RO

433 def atyr(self):

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

435 '''

436 return self

437

438 @Property_RO

439 def tyr3d(self):

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

441 '''

442 return _ltp.Attitude(self).tyr3d

443

444

445 class Ned(_AbcBase):

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

447

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

449 '''

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

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

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

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

454 _toStr = _ned_

455

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

457 '''New L{Ned} vector.

458

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

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

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

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

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

464 scalar B{C{north_ned}}.

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

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

467 scalar B{C{north_ned}}.

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

469 L{LocalCartesian}).

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

471

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

473

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

475 '''

476 if _isMeter(north_ned):

477 ned = None

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

479 Meter(east=east or _0_0),

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

481 else: # PYCHOK no cover

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

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

484 t = ned.ned4

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

486 _init(self, ned, ltp, name)

487

488 @Property_RO

489 def aer4(self):

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

491 '''

492 return _xyz2aer4(self)

493

494 @Property_RO

495 def azimuth(self):

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

497 '''

498 return self.aer4.azimuth

499

500 @deprecated_Property_RO

501 def bearing(self):

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

503 return self.azimuth

504

505 @Property_RO

506 def down(self):

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

508 '''

509 return self._down

510

511 @Property_RO

512 def east(self):

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

514 '''

515 return self._east

516

517 @Property_RO

518 def elevation(self):

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

520 '''

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

522

523 @Property_RO

524 def groundrange(self):

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

526 '''

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

528

529 @deprecated_Property_RO

530 def length(self):

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

532 return self.slantrange

533

534 @deprecated_Property_RO

535 def ned(self):

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

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

538

539 @Property_RO

540 def ned4(self):

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

542 '''

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

544

545 @Property_RO

546 def north(self):

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

548 '''

549 return self._north

550

551 @Property_RO

552 def slantrange(self):

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

554 '''

555 return self.aer4.slantrange

556

557 @deprecated_method

558 def to3ned(self): # PYCHOK no cover

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

560 return self.ned # XXX deprecated too

561

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

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

564

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

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

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

568

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

570 '''

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

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

573

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

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

576

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

578 number of (decimal) digits, unstripped

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

580 backets format (C{str}) and separator

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

582

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

584 '''

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

586 return t

587

588 @deprecated_method

589 def toVector3d(self):

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

591 return self.xyz

592

593 @Property_RO

594 def up(self):

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

596 '''

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

598

599 @Property_RO

600 def x(self):

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

602 '''

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

604

605 @Property_RO

606 def xyz4(self):

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

608 '''

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

610

611 @Property_RO

612 def y(self):

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

614 '''

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

616

617 @Property_RO

618 def z(self):

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

620 '''

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

622

623

624 class Ned4Tuple(_Abc4Tuple):

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

626 '''

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

628 _Units_ = ( Meter, Meter, Meter, _Pass)

629

630 def _toNed(self, Cls, Cls_kwds):

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

632 '''

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

634

635 @Property_RO

636 def xyzLocal(self):

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

638 '''

639 return Ned(self).xyzLocal

640

641

642 class _Vector3d(Vector3d):

643

644 _toStr = _xyz_

645

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

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

648

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

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

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

652

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

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

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

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

657 '''

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

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

660

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

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

663

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

665 number of (decimal) digits, unstripped

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

667 backets format (C{str}) and separator

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

669

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

671 '''

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

673 return t

674

675

676 class XyzLocal(_Vector3d):

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

678 also base class for local L{Enu}.

679 '''

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

681

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

683 '''New L{XyzLocal}.

684

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

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

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

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

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

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

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

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

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

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

695 L{LocalCartesian}).

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

697

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

699

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

701 '''

702 if _isMeter(x_xyz):

703 xyz = None

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

705 Meter(y=y or _0_0),

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

707 else:

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

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

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

711 _init(self, xyz, ltp, name)

712

713 def __str__(self):

714 return self.toStr()

715

716 @Property_RO

717 def aer4(self):

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

719 '''

720 return _xyz2aer4(self)

721

722 @Property_RO

723 def azimuth(self):

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

725

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

727 Transformations_between_ECEF_and_ENU_coordinates>}.

728 '''

729 return self.aer4.azimuth

730

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

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

733

734 @arg args: Optional, positional arguments.

735 @kwarg kwds: Optional, keyword arguments.

736

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

738 '''

739 kwds = _name1__(kwds, _or_nameof=self)

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

741

742 @Property_RO

743 def down(self):

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

745 '''

746 return Meter(down=-self.z)

747

748 @property_RO

749 def ecef(self):

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

751 '''

752 return self.ltp.ecef

753

754 @Property_RO

755 def east(self):

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

757 '''

758 return Meter(east=self.x)

759

760 @Property_RO

761 def elevation(self):

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

763

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

765 Transformations_between_ECEF_and_ENU_coordinates>}.

766 '''

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

768

769 @Property_RO

770 def enu4(self):

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

772 '''

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

774

775 @Property_RO

776 def groundrange(self):

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

778 '''

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

780

781 @Property_RO

782 def ltp(self):

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

784 '''

785 return self._ltp

786

787 def _ltp_kwds_name3(self, ltp, kwds):

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

789 '''

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

791 kwds = _name1__(kwds, _or_nameof=self)

792 kwds = _name1__(kwds, _or_nameof=ltp)

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

794

795 @Property_RO

796 def ned4(self):

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

798 '''

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

800

801 @Property_RO

802 def north(self):

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

804 '''

805 return Meter(north=self.y)

806

807 @Property_RO

808 def slantrange(self):

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

810 '''

811 return self.aer4.slantrange

812

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

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

815

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

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

818 optional, additional B{C{Aer}} keyword arguments,

819 ignored if C{B{Aer} is None}.

820

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

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

823

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

825 '''

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

827

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

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

830

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

832 or C{None}.

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

834 this C{ltp}.

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

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

837 C{B{Cartesian} is None}.

838

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

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

841 C{M=None}, always.

842

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

844 '''

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

846 if Cartesian is None:

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

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

849 else:

850 kwds = _xkwds(kwds, datum=ltp.datum)

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

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

853 return r

854

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

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

857

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

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

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

861 C{B{Enu} is None}.

862

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

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

865

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

867 '''

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

869

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

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

872

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

874 C{None}.

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

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

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

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

879 C{B{LatLon} is None}.

880

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

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

883 C{M=None}, always.

884

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

886 '''

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

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

889 if LatLon is None:

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

891 else:

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

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

894 return r

895

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

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

898

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

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

901

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

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

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

905 '''

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

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

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

909 t.lat, t.lon, t.height,

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

911

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

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

914

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

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

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

918 C{B{Ned} is None}.

919

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

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

922

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

924 '''

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

926

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

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

929

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

931 or C{None}.

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

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

934 C{B{Xyz} is None}.

935

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

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

938

939 @raise TypeError: Invalid B{C{Xyz}} or B{C{name_EXyz_kwds}}.

940 '''

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

942

943 @Property_RO

944 def up(self):

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

946 '''

947 return Meter(up=self.z)

948

949# @Property_RO

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

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

952# '''

953# return self._x

954

955# @Property_RO

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

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

958# '''

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

960

961 @Property_RO

962 def xyz4(self):

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

964 '''

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

966

967 @Property_RO

968 def xyzLocal(self):

969 '''Get this L{XyzLocal}.

970 '''

971 return self

972

973# @Property_RO

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

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

976# '''

977# return self._y

978

979# @Property_RO

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

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

982# '''

983# return self._z

984

985

986 class Xyz4Tuple(_Abc4Tuple):

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

988 '''

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

990 _Units_ = ( Meter, Meter, Meter, _Pass)

991

992 def _toXyz(self, Cls, Cls_kwds):

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

994 '''

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

996

997 @property_RO

998 def xyz4(self):

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

1000 '''

1001 return self

1002

1003 @Property_RO

1004 def xyzLocal(self):

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

1006 '''

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

1008

1009

1010 class Enu(XyzLocal):

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

1012

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

1014 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.

1015 '''

1016 _toStr = _enu_

1017

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

1019 '''New L{Enu}.

1020

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

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

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

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

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

1026 scalar B{C{east_enu}}.

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

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

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

1030 L{LocalCartesian}).

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

1032

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

1034

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

1036 '''

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

1038

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

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

1041

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

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

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

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

1046 additional B{L{Uvw}} keyword arguments, ignored if

1047 C{B{Uvw} is None}.

1048

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

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

1051

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

1053

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

1055 '''

1056 try:

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

1058 except Exception as x:

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

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

1061

1062 t = ca * u - sa * n

1063 U = cb * t - sb * e

1064 V = cb * e + sb * t

1065 W = ca * n + sa * u

1066

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

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

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

1070

1071 @Property_RO

1072 def xyzLocal(self):

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

1074 '''

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

1076

1077

1078 class Enu4Tuple(_Abc4Tuple):

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

1080 '''

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

1082 _Units_ = ( Meter, Meter, Meter, _Pass)

1083

1084 def _toEnu(self, Cls, Cls_kwds):

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

1086 '''

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

1088

1089 @Property_RO

1090 def xyzLocal(self):

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

1092 '''

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

1094

1095

1096 class Local9Tuple(_NamedTuple):

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

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

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

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

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

1102 '''

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

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

1105

1106 @Property_RO

1107 def azimuth(self):

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

1109 '''

1110 return self.xyzLocal.aer4.azimuth

1111

1112 @Property_RO

1113 def down(self):

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

1115 '''

1116 return -self.z

1117

1118 @Property_RO

1119 def east(self):

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

1121 '''

1122 return self.x

1123

1124 @Property_RO

1125 def elevation(self):

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

1127 '''

1128 return self.xyzLocal.aer4.elevation

1129

1130 @Property_RO

1131 def groundrange(self):

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

1133 '''

1134 return self.xyzLocal.aer4.groundrange

1135

1136 @Property_RO

1137 def lam(self):

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

1139 '''

1140 return self.philam.lam

1141

1142 @Property_RO

1143 def latlon(self):

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

1145 '''

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

1147

1148 @Property_RO

1149 def latlonheight(self):

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

1151 '''

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

1153

1154 @Property_RO

1155 def north(self):

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

1157 '''

1158 return self.y

1159

1160 @Property_RO

1161 def phi(self):

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

1163 '''

1164 return self.philam.phi

1165

1166 @Property_RO

1167 def philam(self):

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

1169 '''

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

1171

1172 @Property_RO

1173 def philamheight(self):

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

1175 '''

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

1177

1178 @Property_RO

1179 def slantrange(self):

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

1181 '''

1182 return self.xyzLocal.aer4.slantrange

1183

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

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

1186

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

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

1189 additional B{L{Aer}} keyword arguments, ignored if

1190 C{B{Aer} is None}.

1191

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

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

1194

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

1196 '''

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

1198

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

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

1201

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

1203 or C{None}.

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

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

1206 C{B{Cartesian} is None}.

1207

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

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

1210

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

1212 '''

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

1214

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

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

1217

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

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

1220 additional B{L{Enu}} keyword arguments, ignored if

1221 C{B{Enu} is None}.

1222

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

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

1225

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

1227 '''

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

1229

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

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

1232

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

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

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

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

1237 C{B{LatLon} is None}.

1238

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

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

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

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

1243

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

1245 '''

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

1247

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

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

1250

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

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

1253 additional B{L{Ned}} keyword arguments, ignored if

1254 C{B{Ned} is None}.

1255

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

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

1258

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

1260 '''

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

1262

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

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

1265

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

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

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

1269 C{B{Xyz} is None}.

1270

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

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

1273

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

1275 '''

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

1277

1278 @Property_RO

1279 def up(self):

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

1281 '''

1282 return self.z

1283

1284 @Property_RO

1285 def xyz(self):

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

1287 '''

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

1289

1290 @Property_RO

1291 def xyzLocal(self):

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

1293 '''

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

1295

1296

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

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

1299

1300

1301 class Uvw(_Vector3d):

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

1303 '''

1304 _toStr = _uvw_

1305

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

1307 '''New L{Uvw}.

1308

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

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

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

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

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

1314

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

1316

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

1318 '''

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

1320

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

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

1323

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

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

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

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

1328 additional B{L{Enu}} keyword arguments, ignored if

1329 C{B{Enu} is None}.

1330

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

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

1333

1334 @raise TypeError: Invalid B{C{location}} or B{C{name_Enu_kwds}}.

1335

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

1337 '''

1338 try:

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

1340 except Exception as x:

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

1342 u, v, w = self.uvw

1343

1344 t = cb * u + sb * v

1345 E = cb * v - sb * u

1346 N = ca * w - sa * t

1347 U = ca * t + sa * w

1348

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

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

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

1352

1353 u = Vector3d.x

1354

1355 @Property_RO

1356 def uvw(self):

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

1358 '''

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

1360

1361 v = Vector3d.y

1362 w = Vector3d.z

1363

1364

1365 class Uvw3Tuple(_NamedTuple):

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

1367 '''

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

1369 _Units_ = ( Meter, Meter, Meter)

1370

1371

1372 class Los(Aer):

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

1374 '''

1375

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

1377 '''New L{Los}.

1378

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

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

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

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

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

1384 B{C{azimuth_aer}}.

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

1386

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

1388

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

1390 '''

1391 t = Aer(azimuth_aer, elevation)

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

1393

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

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

1396

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

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

1399

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

1401 '''

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

1403

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

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

1406

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

1408 '''

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

1410

1411

1412 class ChLV9Tuple(Local9Tuple):

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

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

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

1416 otherwise like L{Local9Tuple}.

1417 '''

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

1419

1420 @Property_RO

1421 def E_LV95(self):

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

1423 '''

1424 return self.EN2_LV95.E_LV95

1425

1426 @Property_RO

1427 def EN2_LV95(self):

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

1429 '''

1430 return ChLVEN2Tuple(*_ltp.ChLV.false2(self.Y, self.X, True), name=self.name)

1431

1432 @Property_RO

1433 def h_LV03(self):

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

1435 '''

1436 return self.h_

1437

1438 @Property_RO

1439 def h_LV95(self):

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

1441 '''

1442 return self.h_

1443

1444 @property_RO

1445 def isChLV(self):

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

1447 '''

1448 return self.ltp.__class__ is _ltp.ChLV

1449

1450 @property_RO

1451 def isChLVa(self):

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

1453 '''

1454 return self.ltp.__class__ is _ltp.ChLVa

1455

1456 @property_RO

1457 def isChLVe(self):

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

1459 '''

1460 return self.ltp.__class__ is _ltp.ChLVe

1461

1462 @Property_RO

1463 def N_LV95(self):

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

1465 '''

1466 return self.EN2_LV95.N_LV95

1467

1468 @Property_RO

1469 def x(self):

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

1471 '''

1472 return self.Y

1473

1474 @Property_RO

1475 def x_LV03(self):

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

1477 '''

1478 return self.yx2_LV03.x_LV03

1479

1480 @Property_RO

1481 def y(self):

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

1483 '''

1484 return self.X

1485

1486 @Property_RO

1487 def y_LV03(self):

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

1489 '''

1490 return self.yx2_LV03.y_LV03

1491

1492 @Property_RO

1493 def YX(self):

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

1495 '''

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

1497

1498 @Property_RO

1499 def yx2_LV03(self):

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

1501 '''

1502 return ChLVyx2Tuple(*_ltp.ChLV.false2(self.Y, self.X, False), name=self.name)

1503

1504 @Property_RO

1505 def z(self):

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

1507 '''

1508 return self.h_

1509

1510

1511 class ChLVYX2Tuple(_NamedTuple):

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

1513 in C{meter}.

1514 '''

1515 _Names_ = (_Y_, _X_)

1516 _Units_ = ( Meter, Meter)

1517

1518 def false2(self, LV95=True):

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

1520

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

1522 '''

1523 return _ltp.ChLV.false2(*self, LV95=LV95, name=self.name)

1524

1525

1526 class ChLVEN2Tuple(_NamedTuple):

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

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

1529 '''

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

1531 _Units_ = ChLVYX2Tuple._Units_

1532

1533 def unfalse2(self):

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

1535

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

1537 '''

1538 return _ltp.ChLV.unfalse2(*self, LV95=True, name=self.name)

1539

1540

1541 class ChLVyx2Tuple(_NamedTuple):

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

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

1544 '''

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

1546 _Units_ = ChLVYX2Tuple._Units_

1547

1548 def unfalse2(self):

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

1550

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

1552 '''

1553 return _ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)

1554

1555

1556 class Footprint5Tuple(_NamedTuple):

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

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

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

1560

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

1562 '''

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

1564 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)

1565

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

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

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

1569

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

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

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

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

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

1575 C{B{LatLon} is None}.

1576

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

1578 **B{name_LatLon_kwds})} instances or if C{B{LatLon} is None},

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

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

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

1582

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

1584

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

1586 '''

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

1588 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)

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

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

1591

1592 def xyzLocal5(self, ltp=None):

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

1594

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

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

1597

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

1599

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

1601 '''

1602 if ltp is None:

1603 p = self

1604 else:

1605 p = _ltp._xLtp(ltp)

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

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

1608

1609

1610__all__ += _ALL_DOCS(_AbcBase)

1611

1612# **) MIT License

1613#

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

1615#

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

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

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

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

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

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

1622#

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

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

1625#

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

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

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

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

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

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

1632# OTHER DEALINGS IN THE SOFTWARE.