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 isscalar, issubclassof

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

18 from pygeodesy.fmath import hypot, hypot_

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

20 _down_, _east_, _ecef_, _elevation_, _height_, \

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

22 _X_, _x_, _xyz_, _Y_, _y_, _z_

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

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

25 _Pass, _xnamed

26 from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple, Vector3Tuple

27 from pygeodesy.props import deprecated_method, deprecated_Property_RO, \

28 Property_RO, property_RO

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

30 from pygeodesy.units import Bearing, Degrees, Degrees_, Height, Lat, Lon, \

31 Meter, Meter_

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

33 from pygeodesy.vector3d import Vector3d

34

35 from math import cos, radians

36

37__all__ = _ALL_LAZY.ltpTuples

38__version__ = '23.10.02'

39

40_aer_ = 'aer'

41_alt_ = 'alt'

42_enu_ = 'enu'

43_h__ = 'h_'

44_ned_ = 'ned'

45_local_ = 'local'

46_roll_ = 'roll'

47_slantrange_ = 'slantrange'

48_tilt_ = 'tilt'

49_uvw_ = 'uvw'

50_yaw_ = 'yaw'

51

52

53 def _er2gr(e, r):

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

55 '''

56 c = cos(radians(e))

57 return Meter_(groundrange=r * c)

58

59

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

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

62 '''

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

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

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

66 if sep:

67 t = sep.join(t)

68 if fmt:

69 t = fmt(t)

70 return a, t

71

72

73 def _4Tuple2Cls(inst, Cls, Cls_kwds):

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

75 '''

76 if Cls is None:

77 return inst

78 elif issubclassof(Cls, Aer):

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

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

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

82 elif issubclassof(Cls, Ned):

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

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

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

86 elif Cls is Local9Tuple: # PYCHOK no cover

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

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

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

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 = Bearing(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 = name_inst.popitem()

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 _NamedAerNed(_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, **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 Aer_kwds: Optional, additional B{L{Aer}} keyword

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

131

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

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

134 '''

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

136

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

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

139

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

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

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

143

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

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

146 '''

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

148

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

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

151

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

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

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

155

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

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

158 '''

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

160

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

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

163

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

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

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

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

168

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

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

171

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

173 '''

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

175

176 @Property_RO

177 def xyz(self):

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

179 '''

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

181

182 @property_RO

183 def xyz4(self): # PYCHOK no cover

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

185 notOverloaded(self)

186

187 @Property_RO

188 def xyzLocal(self):

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

190 '''

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

192

193

194 class Aer(_NamedAerNed):

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

196 '''

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

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

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

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

201 _toStr = _aer_

202

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

204 '''New L{Aer}.

205

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

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

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

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

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

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

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

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

214 B{C{azimuth_aer}}.

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

216 L{LocalCartesian}).

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

218

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

220

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

222 or B{C{slantrange}}.

223 '''

224 if isscalar(azimuth_aer):

225 self._azimuth = Bearing(azimuth=azimuth_aer)

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

227 self._slantrange = Meter_(slantrange=slantrange)

228 p, n = ltp, name

229 else: # PYCHOK no cover

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

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

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

233 aer.azimuth, aer.elevation, aer.slantrange

234 p = _xattr(aer, ltp=ltp)

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

236

237 if p:

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

239 if name:

240 self.name = n

241

242 @Property_RO

243 def aer4(self):

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

245 '''

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

247

248 @Property_RO

249 def azimuth(self):

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

251 '''

252 return self._azimuth

253

254 @Property_RO

255 def down(self):

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

257 '''

258 return self.xyzLocal.down

259

260 @Property_RO

261 def east(self):

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

263 '''

264 return self.xyzLocal.east

265

266 @Property_RO

267 def elevation(self):

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

269 '''

270 return self._elevation

271

272 @Property_RO

273 def groundrange(self):

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

275 '''

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

277

278 @Property_RO

279 def north(self):

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

281 '''

282 return self.xyzLocal.north

283

284 @Property_RO

285 def slantrange(self):

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

287 '''

288 return self._slantrange

289

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

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

292 (bearing), elevation and slant range.

293

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

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

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

297

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

299 '''

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

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

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

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

304

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

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

307

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

309 number of (decimal) digits, unstripped

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

311 backets format (C{str}) and separator

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

313

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

315 '''

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

317 return t

318

319 @Property_RO

320 def up(self):

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

322 '''

323 return self.xyzLocal.up

324

325 @Property_RO

326 def x(self):

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

328 '''

329 return self.xyz4.x

330

331 @Property_RO

332 def xyz4(self):

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

334 '''

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

336 R = self._slantrange

337 r = cE * R # ground range

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

339

340 @Property_RO

341 def y(self):

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

343 '''

344 return self.xyz4.y

345

346 @Property_RO

347 def z(self):

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

349 '''

350 return self.xyz4.z

351

352

353 class Aer4Tuple(_NamedTuple):

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

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

356 '''

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

358 _Units_ = ( Meter, Meter, Meter, _Pass)

359

360 def _toAer(self, Cls, Cls_kwds):

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

362 '''

363 if issubclassof(Cls, Aer):

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

365 else:

366 return _4Tuple2Cls(self, Cls, Cls_kwds)

367

368 @Property_RO

369 def groundrange(self):

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

371 '''

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

373

374 @Property_RO

375 def xyzLocal(self):

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

377 '''

378 return Aer(self).xyzLocal

379

380

381 class Attitude4Tuple(_NamedTuple):

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

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

384 the attitude of a plane or camera.

385 '''

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

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

388

389 @Property_RO

390 def atyr(self):

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

392 '''

393 return self

394

395 @Property_RO

396 def tyr3d(self):

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

398 '''

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

400

401

402 class Ned(_NamedAerNed):

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

404

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

406 '''

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

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

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

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

411 _toStr = _ned_

412

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

414 '''New L{Ned} vector.

415

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

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

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

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

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

421 scalar B{C{north_ned}}.

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

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

424 scalar B{C{north_ned}}.

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

426 L{LocalCartesian}).

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

428

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

430

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

432 '''

433 if isscalar(north_ned):

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

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

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

437 p, n = ltp, name

438 else: # PYCHOK no cover

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

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

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

442 p = _xattr(ned, ltp=ltp)

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

444

445 if p:

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

447 if n:

448 self.name = n

449

450 @Property_RO

451 def aer4(self):

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

453 '''

454 return _xyz2aer4(self)

455

456 @Property_RO

457 def azimuth(self):

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

459 '''

460 return self.aer4.azimuth

461

462 @deprecated_Property_RO

463 def bearing(self):

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

465 return self.azimuth

466

467 @Property_RO

468 def down(self):

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

470 '''

471 return self._down

472

473 @Property_RO

474 def east(self):

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

476 '''

477 return self._east

478

479 @Property_RO

480 def elevation(self):

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

482 '''

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

484

485 @Property_RO

486 def groundrange(self):

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

488 '''

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

490

491 @deprecated_Property_RO

492 def length(self):

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

494 return self.slantrange

495

496 @deprecated_Property_RO

497 def ned(self):

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

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

500

501 @Property_RO

502 def ned4(self):

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

504 '''

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

506

507 @Property_RO

508 def north(self):

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

510 '''

511 return self._north

512

513 @Property_RO

514 def slantrange(self):

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

516 '''

517 return self.aer4.slantrange

518

519 @deprecated_method

520 def to3ned(self): # PYCHOK no cover

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

522 return self.ned # XXX deprecated too

523

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

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

526

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

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

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

530

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

532 '''

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

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

535

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

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

538

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

540 number of (decimal) digits, unstripped

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

542 backets format (C{str}) and separator

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

544

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

546 '''

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

548 return t

549

550 @deprecated_method

551 def toVector3d(self):

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

553 return self.xyz

554

555 @Property_RO

556 def up(self):

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

558 '''

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

560

561 @Property_RO

562 def x(self):

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

564 '''

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

566

567 @Property_RO

568 def xyz4(self):

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

570 '''

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

572

573 @Property_RO

574 def y(self):

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

576 '''

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

578

579 @Property_RO

580 def z(self):

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

582 '''

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

584

585

586 class Ned4Tuple(_NamedTuple):

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

588 '''

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

590 _Units_ = ( Meter, Meter, Meter, _Pass)

591

592 def _toNed(self, Cls, Cls_kwds):

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

594 '''

595 if issubclassof(Cls, Ned):

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

597 else:

598 return _4Tuple2Cls(self, Cls, Cls_kwds)

599

600 @Property_RO

601 def xyzLocal(self):

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

603 '''

604 return Ned(self).xyzLocal

605

606

607 class _Vector3d(Vector3d):

608

609 _toStr = _xyz_

610

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

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

613

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

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

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

617

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

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

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

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

622 '''

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

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

625

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

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

628

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

630 number of (decimal) digits, unstripped

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

632 backets format (C{str}) and separator

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

634

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

636 '''

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

638 return t

639

640

641 class XyzLocal(_Vector3d):

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

643 also base class for local L{Enu}.

644 '''

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

646

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

648 '''New L{XyzLocal}.

649

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

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

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

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

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

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

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

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

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

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

660 L{LocalCartesian}).

661

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

663

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

665 '''

666 if isscalar(x_xyz):

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

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

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

670 p, n = ltp, name

671 else:

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

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

674 p = _xattr(xyz, ltp=ltp)

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

676

677 if p:

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

679 if n:

680 self.name = n

681

682 def __str__(self):

683 return self.toStr()

684

685 @Property_RO

686 def aer4(self):

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

688 '''

689 return _xyz2aer4(self)

690

691 @Property_RO

692 def azimuth(self):

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

694

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

696 Transformations_between_ECEF_and_ENU_coordinates>}.

697 '''

698 return self.aer4.azimuth

699

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

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

702

703 @arg args: Optional, positional arguments.

704 @kwarg kwds: Optional, keyword arguments.

705

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

707 '''

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

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

710

711 @Property_RO

712 def down(self):

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

714 '''

715 return Meter(down=-self.z)

716

717 @property_RO

718 def ecef(self):

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

720 '''

721 return self.ltp.ecef

722

723 @Property_RO

724 def east(self):

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

726 '''

727 return Meter(east=self.x)

728

729 @Property_RO

730 def elevation(self):

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

732

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

734 Transformations_between_ECEF_and_ENU_coordinates>}.

735 '''

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

737

738 @Property_RO

739 def enu4(self):

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

741 '''

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

743

744 @Property_RO

745 def groundrange(self):

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

747 '''

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

749

750 @Property_RO

751 def ltp(self):

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

753 '''

754 return self._ltp

755

756 @Property_RO

757 def ned4(self):

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

759 '''

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

761

762 @Property_RO

763 def north(self):

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

765 '''

766 return Meter(north=self.y)

767

768 @Property_RO

769 def slantrange(self):

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

771 '''

772 return self.aer4.slantrange

773

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

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

776

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

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

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

780

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

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

783

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

785 '''

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

787

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

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

790

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

792 or C{None}.

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

794 overriding this C{ltp}.

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

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

797

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

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

800 with C{M=None}, always.

801

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

803 B{C{Cartesian_kwds}} argument.

804 '''

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

806 if Cartesian is None:

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

808 else:

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

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

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

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

813

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

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

816

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

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

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

820

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

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

823 '''

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

825

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

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

828

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

830 or C{None}.

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

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

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

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

835

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

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

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

839

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

841 B{C{LatLon_kwds}} argument.

842 '''

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

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

845 if LatLon is None:

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

847 else:

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

849 name=self.name or ltp.name)

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

851 return r

852

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

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

855

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

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

858

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

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

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

862 '''

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

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

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

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

867

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

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

870

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

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

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

874

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

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

877 '''

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

879

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

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

882

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

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

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

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

887

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

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

890 '''

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

892

893 @Property_RO

894 def up(self):

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

896 '''

897 return Meter(up=self.z)

898

899# @Property_RO

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

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

902# '''

903# return self._x

904

905# @Property_RO

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

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

908# '''

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

910

911 @Property_RO

912 def xyz4(self):

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

914 '''

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

916

917 @Property_RO

918 def xyzLocal(self):

919 '''Get this L{XyzLocal}.

920 '''

921 return self

922

923# @Property_RO

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

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

926# '''

927# return self._y

928

929# @Property_RO

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

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

932# '''

933# return self._z

934

935

936 class Xyz4Tuple(_NamedTuple):

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

938 '''

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

940 _Units_ = ( Meter, Meter, Meter, _Pass)

941

942 def _toXyz(self, Cls, Cls_kwds):

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

944 '''

945 if issubclassof(Cls, XyzLocal):

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

947 else:

948 return _4Tuple2Cls(self, Cls, Cls_kwds)

949

950 @Property_RO

951 def xyzLocal(self):

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

953 '''

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

955

956

957 class Enu(XyzLocal):

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

959

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

961 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.

962 '''

963 _toStr = _enu_

964

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

966 '''New L{Enu}.

967

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

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

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

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

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

973 scalar B{C{east_enu}}.

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

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

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

977 L{LocalCartesian}).

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

979

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

981

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

983 '''

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

985

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

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

988

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

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

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

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

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

994

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

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

997

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

999

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

1001 '''

1002 try:

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

1004 except Exception as x:

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

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

1007

1008 t = ca * u - sa * n

1009 U = cb * t - sb * e

1010 V = cb * e + sb * t

1011 W = ca * n + sa * u

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

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

1014

1015 @Property_RO

1016 def xyzLocal(self):

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

1018 '''

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

1020

1021

1022 class Enu4Tuple(_NamedTuple):

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

1024 '''

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

1026 _Units_ = ( Meter, Meter, Meter, _Pass)

1027

1028 def _toEnu(self, Cls, Cls_kwds):

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

1030 '''

1031 if issubclassof(Cls, XyzLocal):

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

1033 else:

1034 return _4Tuple2Cls(self, Cls, Cls_kwds)

1035

1036 @Property_RO

1037 def xyzLocal(self):

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

1039 '''

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

1041

1042

1043 class Local9Tuple(_NamedTuple):

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

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

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

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

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

1049 '''

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

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

1052

1053 @Property_RO

1054 def azimuth(self):

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

1056 '''

1057 return self.xyzLocal.aer4.azimuth

1058

1059 @Property_RO

1060 def down(self):

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

1062 '''

1063 return -self.z

1064

1065 @Property_RO

1066 def east(self):

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

1068 '''

1069 return self.x

1070

1071 @Property_RO

1072 def elevation(self):

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

1074 '''

1075 return self.xyzLocal.aer4.elevation

1076

1077 @Property_RO

1078 def groundrange(self):

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

1080 '''

1081 return self.xyzLocal.aer4.groundrange

1082

1083 @Property_RO

1084 def lam(self):

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

1086 '''

1087 return self.philam.lam

1088

1089 @Property_RO

1090 def latlon(self):

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

1092 '''

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

1094

1095 @Property_RO

1096 def latlonheight(self):

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

1098 '''

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

1100

1101 @Property_RO

1102 def north(self):

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

1104 '''

1105 return self.y

1106

1107 @Property_RO

1108 def phi(self):

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

1110 '''

1111 return self.philam.phi

1112

1113 @Property_RO

1114 def philam(self):

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

1116 '''

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

1118

1119 @Property_RO

1120 def philamheight(self):

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

1122 '''

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

1124

1125 @Property_RO

1126 def slantrange(self):

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

1128 '''

1129 return self.xyzLocal.aer4.slantrange

1130

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

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

1133

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

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

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

1137

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

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

1140 '''

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

1142

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

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

1145

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

1147 or C{None}.

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

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

1150

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

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

1153

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

1155 argument.

1156 '''

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

1158

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

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

1161

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

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

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

1165

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

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

1168 '''

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

1170

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

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

1173

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

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

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

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

1178

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

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

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

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

1183

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

1185 argument.

1186 '''

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

1188

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

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

1191

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

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

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

1195

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

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

1198 '''

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

1200

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

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

1203

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

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

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

1207

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

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

1210 '''

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

1212

1213 @Property_RO

1214 def up(self):

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

1216 '''

1217 return self.z

1218

1219 @Property_RO

1220 def xyz(self):

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

1222 '''

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

1224

1225 @Property_RO

1226 def xyzLocal(self):

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

1228 '''

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

1230

1231

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

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

1234

1235

1236 class Uvw(_Vector3d):

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

1238 '''

1239 _toStr = _uvw_

1240

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

1242 '''New L{Uvw}.

1243

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

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

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

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

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

1249

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

1251

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

1253 '''

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

1255

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

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

1258

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

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

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

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

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

1264

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

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

1267

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

1269

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

1271 '''

1272 try:

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

1274 except Exception as x:

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

1276 u, v, w = self.uvw

1277

1278 t = cb * u + sb * v

1279 E = cb * v - sb * u

1280 N = ca * w - sa * t

1281 U = ca * t + sa * w

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

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

1284

1285 u = Vector3d.x

1286

1287 @Property_RO

1288 def uvw(self):

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

1290 '''

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

1292

1293 v = Vector3d.y

1294 w = Vector3d.z

1295

1296

1297 class Uvw3Tuple(_NamedTuple):

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

1299 '''

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

1301 _Units_ = ( Meter, Meter, Meter)

1302

1303

1304 class Los(Aer):

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

1306 '''

1307

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

1309 '''New L{Los}.

1310

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

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

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

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

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

1316 B{C{azimuth_aer}}.

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

1318

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

1320

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

1322 '''

1323 t = Aer(azimuth_aer, elevation)

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

1325

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

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

1328

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

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

1331

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

1333 '''

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

1335

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

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

1338

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

1340 '''

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

1342

1343

1344 class ChLV9Tuple(Local9Tuple):

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

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

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

1348 otherwise like L{Local9Tuple}.

1349 '''

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

1351

1352 @Property_RO

1353 def E_LV95(self):

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

1355 '''

1356 return self.EN2_LV95.E_LV95

1357

1358 @Property_RO

1359 def EN2_LV95(self):

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

1361 '''

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

1363

1364 @Property_RO

1365 def h_LV03(self):

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

1367 '''

1368 return self.h_

1369

1370 @Property_RO

1371 def h_LV95(self):

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

1373 '''

1374 return self.h_

1375

1376 @property_RO

1377 def isChLV(self):

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

1379 '''

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

1381

1382 @property_RO

1383 def isChLVa(self):

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

1385 '''

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

1387

1388 @property_RO

1389 def isChLVe(self):

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

1391 '''

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

1393

1394 @Property_RO

1395 def N_LV95(self):

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

1397 '''

1398 return self.EN2_LV95.N_LV95

1399

1400 @Property_RO

1401 def x(self):

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

1403 '''

1404 return self.Y

1405

1406 @Property_RO

1407 def x_LV03(self):

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

1409 '''

1410 return self.yx2_LV03.x_LV03

1411

1412 @Property_RO

1413 def y(self):

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

1415 '''

1416 return self.X

1417

1418 @Property_RO

1419 def y_LV03(self):

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

1421 '''

1422 return self.yx2_LV03.y_LV03

1423

1424 @Property_RO

1425 def YX(self):

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

1427 '''

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

1429

1430 @Property_RO

1431 def yx2_LV03(self):

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

1433 '''

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

1435

1436 @Property_RO

1437 def z(self):

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

1439 '''

1440 return self.h_

1441

1442

1443 class ChLVYX2Tuple(_NamedTuple):

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

1445 in C{meter}.

1446 '''

1447 _Names_ = (_Y_, _X_)

1448 _Units_ = ( Meter, Meter)

1449

1450 def false2(self, LV95=True):

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

1452

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

1454 '''

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

1456

1457

1458 class ChLVEN2Tuple(_NamedTuple):

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

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

1461 '''

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

1463 _Units_ = ChLVYX2Tuple._Units_

1464

1465 def unfalse2(self):

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

1467

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

1469 '''

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

1471

1472

1473 class ChLVyx2Tuple(_NamedTuple):

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

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

1476 '''

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

1478 _Units_ = ChLVYX2Tuple._Units_

1479

1480 def unfalse2(self):

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

1482

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

1484 '''

1485 return _MODS.ltp.ChLV.unfalse2(*self, LV95=False, name=self.name)

1486

1487

1488 class Footprint5Tuple(_NamedTuple):

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

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

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

1492

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

1494 '''

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

1496 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)

1497

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

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

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

1501

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

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

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

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

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

1507

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

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

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

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

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

1513

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

1515

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

1517 '''

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

1519 LatLon=LatLon, name=self.name,)

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

1521

1522 def xyzLocal5(self, ltp=None):

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

1524

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

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

1527

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

1529

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

1531 '''

1532 if ltp is None:

1533 p = self

1534 else:

1535 p = _MODS.ltp._xLtp(ltp)

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

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

1538

1539

1540__all__ += _ALL_DOCS(_NamedAerNed)

1541

1542# **) MIT License

1543#

1544# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

1545#

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

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

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

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

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

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

1552#

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

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

1555#

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

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

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

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

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

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

1562# OTHER DEALINGS IN THE SOFTWARE.