1

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

3

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

5

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

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

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

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

10

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

12'''

13

14# from pygeodesy.basics import issubclassof # from .units

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

16 from pygeodesy.dms import F_D, toDMS

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

18 _xkwds, _xkwds_item2

19 from pygeodesy.fmath import hypot, hypot_

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

21 _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 Bearing, Degrees, Degrees_, Height, _isDegrees, \

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

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

34 from pygeodesy.vector3d import Vector3d

35

36 from math import cos, radians

37

38__all__ = _ALL_LAZY.ltpTuples

39__version__ = '24.05.31'

40

41_aer_ = 'aer'

42_alt_ = 'alt'

43_down_ = 'down'

44_east_ = 'east'

45_enu_ = 'enu'

46_h__ = 'h_'

47_ned_ = 'ned'

48_north_ = 'north'

49_local_ = 'local'

50_roll_ = 'roll'

51_slantrange_ = 'slantrange'

52_tilt_ = 'tilt'

53_uvw_ = 'uvw'

54_yaw_ = 'yaw'

55

56

57 def _er2gr(e, r):

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

59 '''

60 c = cos(radians(e))

61 return Meter_(groundrange=r * c)

62

63

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

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

66 '''

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

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

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

70 if sep:

71 t = sep.join(t)

72 if fmt:

73 t = fmt(t)

74 return a, t

75

76

77 def _4Tuple2Cls(inst, Cls, Cls_kwds):

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

79 '''

80 if Cls is None:

81 return inst

82 elif issubclassof(Cls, Aer):

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

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

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

86 elif issubclassof(Cls, Ned):

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

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

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

90 elif Cls is Local9Tuple: # PYCHOK no cover

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

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

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

94

95

96 def _xyz2aer4(inst):

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

98 '''

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

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

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

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

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

104

105

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

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

108 '''

109 n, inst = _xkwds_item2(name_inst)

110 if isinstance(inst, Types):

111 return None

112 try:

113 return inst.xyzLocal

114 except (AttributeError, TypeError):

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

116

117

118 class _NamedAerNed(_NamedBase):

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

120 '''

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

122

123 @Property_RO

124 def ltp(self):

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

126 '''

127 return self._ltp

128

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

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

131

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

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

134 optional, additional B{L{Aer}} keyword arguments,

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

136

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

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

139

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

141 '''

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

143

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

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

146

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

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

149 optional, additional B{L{Enu}} keyword arguments,

150 ignored if C{B{Enu} is None}.

151

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

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

154

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

156 '''

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

158

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

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

161

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

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

164 optional, additional B{L{Ned}} keyword arguments,

165 ignored if B{C{Ned}} is C{None}.

166

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

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

169

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

171 '''

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

173

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

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

176

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

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

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

180 optional, additional B{C{Xyz}} keyword arguments,

181 ignored if C{B{Xyz} is None}.

182

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

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

185

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

187 '''

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

189

190 @Property_RO

191 def xyz(self):

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

193 '''

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

195

196 @property_RO

197 def xyz4(self): # PYCHOK no cover

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

199 self._notOverloaded()

200

201 @Property_RO

202 def xyzLocal(self):

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

204 '''

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

206

207

208 class Aer(_NamedAerNed):

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

210 '''

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

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

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

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

215 _toStr = _aer_

216

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

218 '''New L{Aer}.

219

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

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

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

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

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

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

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

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

228 B{C{azimuth_aer}}.

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

230 L{LocalCartesian}).

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

232

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

234

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

236 or B{C{slantrange}}.

237 '''

238 if _isDegrees(azimuth_aer):

239 self._azimuth = Bearing(azimuth=azimuth_aer)

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

241 self._slantrange = Meter_(slantrange=slantrange)

242 p, n = ltp, _name__(**name)

243 else: # PYCHOK no cover

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

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

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

247 aer.azimuth, aer.elevation, aer.slantrange

248 p = _xattr(aer, ltp=ltp)

249 n = aer._name__(name)

250

251 if p:

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

253 if n:

254 self.name = n

255

256 @Property_RO

257 def aer4(self):

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

259 '''

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

261

262 @Property_RO

263 def azimuth(self):

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

265 '''

266 return self._azimuth

267

268 @Property_RO

269 def down(self):

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

271 '''

272 return self.xyzLocal.down

273

274 @Property_RO

275 def east(self):

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

277 '''

278 return self.xyzLocal.east

279

280 @Property_RO

281 def elevation(self):

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

283 '''

284 return self._elevation

285

286 @Property_RO

287 def groundrange(self):

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

289 '''

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

291

292 @Property_RO

293 def north(self):

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

295 '''

296 return self.xyzLocal.north

297

298 @Property_RO

299 def slantrange(self):

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

301 '''

302 return self._slantrange

303

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

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

306 (bearing), elevation and slant range.

307

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

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

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

311

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

313 '''

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

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

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

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

318

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

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

321

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

323 number of (decimal) digits, unstripped

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

325 backets format (C{str}) and separator

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

327

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

329 '''

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

331 return t

332

333 @Property_RO

334 def up(self):

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

336 '''

337 return self.xyzLocal.up

338

339 @Property_RO

340 def x(self):

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

342 '''

343 return self.xyz4.x

344

345 @Property_RO

346 def xyz4(self):

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

348 '''

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

350 R = self._slantrange

351 r = cE * R # ground range

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

353

354 @Property_RO

355 def y(self):

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

357 '''

358 return self.xyz4.y

359

360 @Property_RO

361 def z(self):

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

363 '''

364 return self.xyz4.z

365

366

367 class Aer4Tuple(_NamedTuple):

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

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

370 '''

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

372 _Units_ = ( Meter, Meter, Meter, _Pass)

373

374 def _toAer(self, Cls, Cls_kwds):

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

376 '''

377 if issubclassof(Cls, Aer):

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

379 else:

380 return _4Tuple2Cls(self, Cls, Cls_kwds)

381

382 @Property_RO

383 def groundrange(self):

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

385 '''

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

387

388 @Property_RO

389 def xyzLocal(self):

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

391 '''

392 return Aer(self).xyzLocal

393

394

395 class Attitude4Tuple(_NamedTuple):

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

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

398 the attitude of a plane or camera.

399 '''

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

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

402

403 @Property_RO

404 def atyr(self):

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

406 '''

407 return self

408

409 @Property_RO

410 def tyr3d(self):

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

412 '''

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

414

415

416 class Ned(_NamedAerNed):

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

418

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

420 '''

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

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

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

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

425 _toStr = _ned_

426

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

428 '''New L{Ned} vector.

429

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

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

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

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

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

435 scalar B{C{north_ned}}.

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

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

438 scalar B{C{north_ned}}.

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

440 L{LocalCartesian}).

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

442

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

444

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

446 '''

447 if _isMeter(north_ned):

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

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

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

451 p, n = ltp, _name__(**name)

452 else: # PYCHOK no cover

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

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

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

456 p = _xattr(ned, ltp=ltp)

457 n = ned._name__(name)

458

459 if p:

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

461 if n:

462 self.name = n

463

464 @Property_RO

465 def aer4(self):

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

467 '''

468 return _xyz2aer4(self)

469

470 @Property_RO

471 def azimuth(self):

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

473 '''

474 return self.aer4.azimuth

475

476 @deprecated_Property_RO

477 def bearing(self):

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

479 return self.azimuth

480

481 @Property_RO

482 def down(self):

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

484 '''

485 return self._down

486

487 @Property_RO

488 def east(self):

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

490 '''

491 return self._east

492

493 @Property_RO

494 def elevation(self):

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

496 '''

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

498

499 @Property_RO

500 def groundrange(self):

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

502 '''

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

504

505 @deprecated_Property_RO

506 def length(self):

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

508 return self.slantrange

509

510 @deprecated_Property_RO

511 def ned(self):

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

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

514

515 @Property_RO

516 def ned4(self):

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

518 '''

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

520

521 @Property_RO

522 def north(self):

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

524 '''

525 return self._north

526

527 @Property_RO

528 def slantrange(self):

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

530 '''

531 return self.aer4.slantrange

532

533 @deprecated_method

534 def to3ned(self): # PYCHOK no cover

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

536 return self.ned # XXX deprecated too

537

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

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

540

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

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

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

544

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

546 '''

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

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

549

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

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

552

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

554 number of (decimal) digits, unstripped

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

556 backets format (C{str}) and separator

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

558

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

560 '''

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

562 return t

563

564 @deprecated_method

565 def toVector3d(self):

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

567 return self.xyz

568

569 @Property_RO

570 def up(self):

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

572 '''

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

574

575 @Property_RO

576 def x(self):

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

578 '''

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

580

581 @Property_RO

582 def xyz4(self):

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

584 '''

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

586

587 @Property_RO

588 def y(self):

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

590 '''

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

592

593 @Property_RO

594 def z(self):

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

596 '''

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

598

599

600 class Ned4Tuple(_NamedTuple):

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

602 '''

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

604 _Units_ = ( Meter, Meter, Meter, _Pass)

605

606 def _toNed(self, Cls, Cls_kwds):

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

608 '''

609 if issubclassof(Cls, Ned):

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

611 else:

612 return _4Tuple2Cls(self, Cls, Cls_kwds)

613

614 @Property_RO

615 def xyzLocal(self):

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

617 '''

618 return Ned(self).xyzLocal

619

620

621 class _Vector3d(Vector3d):

622

623 _toStr = _xyz_

624

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

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

627

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

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

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

631

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

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

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

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

636 '''

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

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

639

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

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

642

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

644 number of (decimal) digits, unstripped

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

646 backets format (C{str}) and separator

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

648

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

650 '''

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

652 return t

653

654

655 class XyzLocal(_Vector3d):

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

657 also base class for local L{Enu}.

658 '''

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

660

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

662 '''New L{XyzLocal}.

663

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

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

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

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

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

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

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

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

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

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

674 L{LocalCartesian}).

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

676

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

678

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

680 '''

681 if _isMeter(x_xyz):

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

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

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

685 p, n = ltp, _name__(**name)

686 else:

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

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

689 p = _xattr(xyz, ltp=ltp)

690 n = xyz._name__(name)

691

692 if p:

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

694 if n:

695 self.name = n

696

697 def __str__(self):

698 return self.toStr()

699

700 @Property_RO

701 def aer4(self):

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

703 '''

704 return _xyz2aer4(self)

705

706 @Property_RO

707 def azimuth(self):

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

709

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

711 Transformations_between_ECEF_and_ENU_coordinates>}.

712 '''

713 return self.aer4.azimuth

714

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

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

717

718 @arg args: Optional, positional arguments.

719 @kwarg kwds: Optional, keyword arguments.

720

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

722 '''

723 kwds = _name1__(kwds, _or_nameof=self)

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

725

726 @Property_RO

727 def down(self):

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

729 '''

730 return Meter(down=-self.z)

731

732 @property_RO

733 def ecef(self):

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

735 '''

736 return self.ltp.ecef

737

738 @Property_RO

739 def east(self):

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

741 '''

742 return Meter(east=self.x)

743

744 @Property_RO

745 def elevation(self):

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

747

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

749 Transformations_between_ECEF_and_ENU_coordinates>}.

750 '''

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

752

753 @Property_RO

754 def enu4(self):

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

756 '''

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

758

759 @Property_RO

760 def groundrange(self):

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

762 '''

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

764

765 @Property_RO

766 def ltp(self):

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

768 '''

769 return self._ltp

770

771 def _ltp_kwds_name3(self, ltp, kwds):

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

773 '''

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

775 kwds = _name1__(kwds, _or_nameof=self)

776 kwds = _name1__(kwds, _or_nameof=ltp)

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

778

779 @Property_RO

780 def ned4(self):

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

782 '''

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

784

785 @Property_RO

786 def north(self):

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

788 '''

789 return Meter(north=self.y)

790

791 @Property_RO

792 def slantrange(self):

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

794 '''

795 return self.aer4.slantrange

796

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

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

799

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

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

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

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

804

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

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

807

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

809 '''

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

811

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

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

814

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

816 or C{None}.

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

818 overriding this C{ltp}.

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

820 optional, additional B{C{Cartesian}} keyword arguments,

821 ignored if C{B{Cartesian} is None}.

822

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

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

825 with C{M=None}, always.

826

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

828 B{C{name_Cartesian_kwds}}.

829 '''

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

831 if Cartesian is None:

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

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

834 else:

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

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

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

838 return r

839

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

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

842

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

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

845 optional, additional B{C{Enu}} keyword arguments,

846 ignored if C{B{Enu} is None}.

847

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

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

850

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

852 '''

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

854

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

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

857

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

859 or C{None}.

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

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

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

863 optional, additional B{C{LatLon}} keyword arguments,

864 ignored if C{B{LatLon} is None}.

865

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

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

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

869

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

871 B{C{name_LatLon_kwds}}.

872 '''

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

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

875 if LatLon is None:

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

877 else:

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

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

880 return r

881

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

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

884

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

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

887

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

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

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

891 '''

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

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

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

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

896

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

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

899

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

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

902 optional, additional B{C{Ned}} keyword arguments,

903 ignored if C{B{Ned} is None}.

904

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

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

907

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

909 '''

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

911

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

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

914

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

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

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

918 optional, additional B{C{Xyz}} keyword arguments,

919 ignored if C{B{Xyz} is None}.

920

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

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

923

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

925 '''

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

927

928 @Property_RO

929 def up(self):

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

931 '''

932 return Meter(up=self.z)

933

934# @Property_RO

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

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

937# '''

938# return self._x

939

940# @Property_RO

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

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

943# '''

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

945

946 @Property_RO

947 def xyz4(self):

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

949 '''

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

951

952 @Property_RO

953 def xyzLocal(self):

954 '''Get this L{XyzLocal}.

955 '''

956 return self

957

958# @Property_RO

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

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

961# '''

962# return self._y

963

964# @Property_RO

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

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

967# '''

968# return self._z

969

970

971 class Xyz4Tuple(_NamedTuple):

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

973 '''

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

975 _Units_ = ( Meter, Meter, Meter, _Pass)

976

977 def _toXyz(self, Cls, Cls_kwds):

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

979 '''

980 kwds = _name1__(Cls_kwds, _or_nameof=self)

981 if issubclassof(Cls, XyzLocal):

982 return Cls(*self, **kwds)

983 else:

984 return _4Tuple2Cls(self, Cls, kwds)

985

986 @Property_RO

987 def xyzLocal(self):

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

989 '''

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

991

992

993 class Enu(XyzLocal):

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

995

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

997 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.

998 '''

999 _toStr = _enu_

1000

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

1002 '''New L{Enu}.

1003

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

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

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

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

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

1009 scalar B{C{east_enu}}.

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

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

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

1013 L{LocalCartesian}).

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

1015

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

1017

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

1019 '''

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

1021

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

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

1024

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

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

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

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

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

1030 C{B{Uvw} is None}.

1031

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

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

1034

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

1036

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

1038 '''

1039 try:

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

1041 except Exception as x:

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

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

1044

1045 t = ca * u - sa * n

1046 U = cb * t - sb * e

1047 V = cb * e + sb * t

1048 W = ca * n + sa * u

1049

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

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

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

1053

1054 @Property_RO

1055 def xyzLocal(self):

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

1057 '''

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

1059

1060

1061 class Enu4Tuple(_NamedTuple):

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

1063 '''

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

1065 _Units_ = ( Meter, Meter, Meter, _Pass)

1066

1067 def _toEnu(self, Cls, Cls_kwds):

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

1069 '''

1070 kwds = _name1__(Cls_kwds, _or_nameof=self)

1071 if issubclassof(Cls, XyzLocal):

1072 return Cls(*self, **kwds)

1073 else:

1074 return _4Tuple2Cls(self, Cls, kwds)

1075

1076 @Property_RO

1077 def xyzLocal(self):

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

1079 '''

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

1081

1082

1083 class Local9Tuple(_NamedTuple):

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

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

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

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

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

1089 '''

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

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

1092

1093 @Property_RO

1094 def azimuth(self):

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

1096 '''

1097 return self.xyzLocal.aer4.azimuth

1098

1099 @Property_RO

1100 def down(self):

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

1102 '''

1103 return -self.z

1104

1105 @Property_RO

1106 def east(self):

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

1108 '''

1109 return self.x

1110

1111 @Property_RO

1112 def elevation(self):

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

1114 '''

1115 return self.xyzLocal.aer4.elevation

1116

1117 @Property_RO

1118 def groundrange(self):

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

1120 '''

1121 return self.xyzLocal.aer4.groundrange

1122

1123 @Property_RO

1124 def lam(self):

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

1126 '''

1127 return self.philam.lam

1128

1129 @Property_RO

1130 def latlon(self):

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

1132 '''

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

1134

1135 @Property_RO

1136 def latlonheight(self):

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

1138 '''

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

1140

1141 @Property_RO

1142 def north(self):

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

1144 '''

1145 return self.y

1146

1147 @Property_RO

1148 def phi(self):

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

1150 '''

1151 return self.philam.phi

1152

1153 @Property_RO

1154 def philam(self):

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

1156 '''

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

1158

1159 @Property_RO

1160 def philamheight(self):

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

1162 '''

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

1164

1165 @Property_RO

1166 def slantrange(self):

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

1168 '''

1169 return self.xyzLocal.aer4.slantrange

1170

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

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

1173

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

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

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

1177 B{C{Aer}} is C{None}.

1178

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

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

1181

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

1183 '''

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

1185

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

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

1188

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

1190 or C{None}.

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

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

1193 C{B{Cartesian} is None}.

1194

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

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

1197

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

1199 '''

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

1201

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

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

1204

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

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

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

1208 C{B{Enu} is None}.

1209

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

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

1212

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

1214 '''

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

1216

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

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

1219

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

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

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

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

1224 C{B{LatLon} is None}.

1225

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

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

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

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

1230

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

1232 '''

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

1234

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

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

1237

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

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

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

1241 B{C{Ned}} is C{None}.

1242

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

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

1245

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

1247 '''

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

1249

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

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

1252

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

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

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

1256 C{B{Xyz} is None}.

1257

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

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

1260

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

1262 '''

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

1264

1265 @Property_RO

1266 def up(self):

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

1268 '''

1269 return self.z

1270

1271 @Property_RO

1272 def xyz(self):

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

1274 '''

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

1276

1277 @Property_RO

1278 def xyzLocal(self):

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

1280 '''

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

1282

1283

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

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

1286

1287

1288 class Uvw(_Vector3d):

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

1290 '''

1291 _toStr = _uvw_

1292

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

1294 '''New L{Uvw}.

1295

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

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

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

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

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

1301

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

1303

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

1305 '''

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

1307

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

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

1310

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

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

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

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

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

1316 C{B{Enu} is None}.

1317

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

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

1320

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

1322

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

1324 '''

1325 try:

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

1327 except Exception as x:

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

1329 u, v, w = self.uvw

1330

1331 t = cb * u + sb * v

1332 E = cb * v - sb * u

1333 N = ca * w - sa * t

1334 U = ca * t + sa * w

1335

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

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

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

1339

1340 u = Vector3d.x

1341

1342 @Property_RO

1343 def uvw(self):

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

1345 '''

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

1347

1348 v = Vector3d.y

1349 w = Vector3d.z

1350

1351

1352 class Uvw3Tuple(_NamedTuple):

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

1354 '''

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

1356 _Units_ = ( Meter, Meter, Meter)

1357

1358

1359 class Los(Aer):

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

1361 '''

1362

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

1364 '''New L{Los}.

1365

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

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

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

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

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

1371 B{C{azimuth_aer}}.

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

1373

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

1375

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

1377 '''

1378 t = Aer(azimuth_aer, elevation)

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

1380

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

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

1383

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

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

1386

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

1388 '''

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

1390

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

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

1393

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

1395 '''

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

1397

1398

1399 class ChLV9Tuple(Local9Tuple):

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

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

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

1403 otherwise like L{Local9Tuple}.

1404 '''

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

1406

1407 @Property_RO

1408 def E_LV95(self):

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

1410 '''

1411 return self.EN2_LV95.E_LV95

1412

1413 @Property_RO

1414 def EN2_LV95(self):

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

1416 '''

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

1418

1419 @Property_RO

1420 def h_LV03(self):

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

1422 '''

1423 return self.h_

1424

1425 @Property_RO

1426 def h_LV95(self):

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

1428 '''

1429 return self.h_

1430

1431 @property_RO

1432 def isChLV(self):

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

1434 '''

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

1436

1437 @property_RO

1438 def isChLVa(self):

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

1440 '''

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

1442

1443 @property_RO

1444 def isChLVe(self):

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

1446 '''

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

1448

1449 @Property_RO

1450 def N_LV95(self):

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

1452 '''

1453 return self.EN2_LV95.N_LV95

1454

1455 @Property_RO

1456 def x(self):

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

1458 '''

1459 return self.Y

1460

1461 @Property_RO

1462 def x_LV03(self):

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

1464 '''

1465 return self.yx2_LV03.x_LV03

1466

1467 @Property_RO

1468 def y(self):

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

1470 '''

1471 return self.X

1472

1473 @Property_RO

1474 def y_LV03(self):

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

1476 '''

1477 return self.yx2_LV03.y_LV03

1478

1479 @Property_RO

1480 def YX(self):

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

1482 '''

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

1484

1485 @Property_RO

1486 def yx2_LV03(self):

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

1488 '''

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

1490

1491 @Property_RO

1492 def z(self):

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

1494 '''

1495 return self.h_

1496

1497

1498 class ChLVYX2Tuple(_NamedTuple):

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

1500 in C{meter}.

1501 '''

1502 _Names_ = (_Y_, _X_)

1503 _Units_ = ( Meter, Meter)

1504

1505 def false2(self, LV95=True):

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

1507

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

1509 '''

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

1511

1512

1513 class ChLVEN2Tuple(_NamedTuple):

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

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

1516 '''

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

1518 _Units_ = ChLVYX2Tuple._Units_

1519

1520 def unfalse2(self):

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

1522

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

1524 '''

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

1526

1527

1528 class ChLVyx2Tuple(_NamedTuple):

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

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

1531 '''

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

1533 _Units_ = ChLVYX2Tuple._Units_

1534

1535 def unfalse2(self):

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

1537

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

1539 '''

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

1541

1542

1543 class Footprint5Tuple(_NamedTuple):

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

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

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

1547

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

1549 '''

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

1551 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)

1552

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

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

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

1556

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

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

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

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

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

1562 C{B{LatLon} is None}.

1563

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

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

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

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

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

1569

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

1571

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

1573 '''

1574 ltp = _MODS.ltp._xLtp(ltp, self.center.ltp) # PYCHOK .center

1575 kwds = _name1__(name_LatLon_kwds, _or_nameof=self)

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

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

1578

1579 def xyzLocal5(self, ltp=None):

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

1581

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

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

1584

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

1586

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

1588 '''

1589 if ltp is None:

1590 p = self

1591 else:

1592 p = _MODS.ltp._xLtp(ltp)

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

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

1595

1596

1597__all__ += _ALL_DOCS(_NamedAerNed)

1598

1599# **) MIT License

1600#

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

1602#

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

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

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

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

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

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

1609#

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

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

1612#

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

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

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

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

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

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

1619# OTHER DEALINGS IN THE SOFTWARE.