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, _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, sincos2d_

33 from pygeodesy.vector3d import Vector3d

34

35 from math import cos, radians

36

37__all__ = _ALL_LAZY.ltpTuples

38__version__ = '23.05.26'

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_yaw_ = 'yaw'

50

51

52 def _er2gr(e, r):

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

54 '''

55 c = cos(radians(e))

56 return Meter_(groundrange=r * c)

57

58

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

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

61 '''

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

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

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

65 if sep:

66 t = sep.join(t)

67 if fmt:

68 t = fmt(t)

69 return a, t

70

71

72 def _4Tuple2Cls(inst, Cls, Cls_kwds):

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

74 '''

75 if Cls is None:

76 return inst

77 elif issubclassof(Cls, Aer):

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

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

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

81 elif issubclassof(Cls, Ned):

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

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

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

85 elif Cls is Local9Tuple: # PYCHOK no cover

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

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

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

89

90

91 def _xyz2aer4(inst):

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

93 '''

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

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

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

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

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

99

100

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

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

103 '''

104 n, inst = name_inst.popitem()

105 if isinstance(inst, Types):

106 return None

107 try:

108 return inst.xyzLocal

109 except (AttributeError, TypeError):

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

111

112

113 class _NamedAerNed(_NamedBase):

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

115 '''

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

117

118 @Property_RO

119 def ltp(self):

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

121 '''

122 return self._ltp

123

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

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

126

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

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

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

130

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

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

133 '''

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

135

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

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

138

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

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

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

142

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

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

145 '''

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

147

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

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

150

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

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

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

154

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

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

157 '''

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

159

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

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

162

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

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

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

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

167

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

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

170

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

172 '''

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

174

175 @Property_RO

176 def xyz(self):

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

178 '''

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

180

181 @property_RO

182 def xyz4(self):

183 '''(INTERNAL) I{Must be overloaded}, see function C{notOverloaded}.

184 '''

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 (C{degrees360})

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} horizon, I{above} B{C{ltp}}

211 (C{degrees90}) only used with scalar B{C{azimuth_aer}}.

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

213 B{C{azimuth_aer}}.

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

215 L{LocalCartesian}).

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

217

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

219

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

221 or B{C{slantrange}}.

222 '''

223 if isscalar(azimuth_aer):

224 self._azimuth = Bearing(azimuth=azimuth_aer)

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

226 self._slantrange = Meter_(slantrange=slantrange)

227 p, n = ltp, name

228 else: # PYCHOK no cover

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

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

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

232 aer.azimuth, aer.elevation, aer.slantrange

233 p = _xattr(aer, ltp=ltp)

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

235

236 if p:

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

238 if name:

239 self.name = n

240

241 @Property_RO

242 def aer4(self):

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

244 '''

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

246

247 @Property_RO

248 def azimuth(self):

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

250 '''

251 return self._azimuth

252

253 @Property_RO

254 def down(self):

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

256 '''

257 return self.xyzLocal.down

258

259 @Property_RO

260 def east(self):

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

262 '''

263 return self.xyzLocal.east

264

265 @Property_RO

266 def elevation(self):

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

268 '''

269 return self._elevation

270

271 @Property_RO

272 def groundrange(self):

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

274 '''

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

276

277 @Property_RO

278 def north(self):

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

280 '''

281 return self.xyzLocal.north

282

283 @Property_RO

284 def slantrange(self):

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

286 '''

287 return self._slantrange

288

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

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

291 (bearing), elevation and slant range.

292

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

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

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

296

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

298 '''

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

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

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

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

303

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

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

306

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

308 number of (decimal) digits, unstripped

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

310 backets format (C{str}) and separator

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

312

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

314 '''

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

316 return t

317

318 @Property_RO

319 def up(self):

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

321 '''

322 return self.xyzLocal.up

323

324 @Property_RO

325 def x(self):

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

327 '''

328 return self.xyz4.x

329

330 @Property_RO

331 def xyz4(self):

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

333 '''

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

335 R = self._slantrange

336 r = cE * R # ground range

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

338

339 @Property_RO

340 def y(self):

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

342 '''

343 return self.xyz4.y

344

345 @Property_RO

346 def z(self):

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

348 '''

349 return self.xyz4.z

350

351

352 class Aer4Tuple(_NamedTuple):

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

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

355 '''

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

357 _Units_ = ( Meter, Meter, Meter, _Pass)

358

359 def _toAer(self, Cls, Cls_kwds):

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

361 '''

362 if issubclassof(Cls, Aer):

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

364 else:

365 return _4Tuple2Cls(self, Cls, Cls_kwds)

366

367 @Property_RO

368 def groundrange(self):

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

370 '''

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

372

373 @Property_RO

374 def xyzLocal(self):

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

376 '''

377 return Aer(self).xyzLocal

378

379

380 class Attitude4Tuple(_NamedTuple):

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

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

383 the attitude of a plane or camera.

384 '''

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

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

387

388 @Property_RO

389 def atyr(self):

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

391 '''

392 return self

393

394 @Property_RO

395 def tyr3d(self):

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

397 '''

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

399

400

401 class Ned(_NamedAerNed):

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

403

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

405 '''

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

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

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

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

410 _toStr = _ned_

411

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

413 '''New L{Ned} vector.

414

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

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

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

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

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

420 scalar B{C{north_ned}}.

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

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

423 scalar B{C{north_ned}}.

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

425 L{LocalCartesian}).

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

427

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

429

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

431 '''

432 if isscalar(north_ned):

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

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

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

436 p, n = ltp, name

437 else: # PYCHOK no cover

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

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

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

441 p = _xattr(ned, ltp=ltp)

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

443

444 if p:

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

446 if n:

447 self.name = n

448

449 @Property_RO

450 def aer4(self):

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

452 '''

453 return _xyz2aer4(self)

454

455 @Property_RO

456 def azimuth(self):

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

458 '''

459 return self.aer4.azimuth

460

461 @deprecated_Property_RO

462 def bearing(self):

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

464 return self.azimuth

465

466 @Property_RO

467 def down(self):

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

469 '''

470 return self._down

471

472 @Property_RO

473 def east(self):

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

475 '''

476 return self._east

477

478 @Property_RO

479 def elevation(self):

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

481 '''

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

483

484 @Property_RO

485 def groundrange(self):

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

487 '''

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

489

490 @deprecated_Property_RO

491 def length(self):

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

493 return self.slantrange

494

495 @deprecated_Property_RO

496 def ned(self):

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

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

499

500 @Property_RO

501 def ned4(self):

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

503 '''

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

505

506 @Property_RO

507 def north(self):

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

509 '''

510 return self._north

511

512 @Property_RO

513 def slantrange(self):

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

515 '''

516 return self.aer4.slantrange

517

518 @deprecated_method

519 def to3ned(self): # PYCHOK no cover

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

521 return self.ned # XXX deprecated too

522

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

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

525

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

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

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

529

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

531 '''

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

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

534

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

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

537

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

539 number of (decimal) digits, unstripped

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

541 backets format (C{str}) and separator

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

543

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

545 '''

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

547 return t

548

549 @deprecated_method

550 def toVector3d(self):

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

552 return self.xyz

553

554 @Property_RO

555 def up(self):

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

557 '''

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

559

560 @Property_RO

561 def x(self):

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

563 '''

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

565

566 @Property_RO

567 def xyz4(self):

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

569 '''

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

571

572 @Property_RO

573 def y(self):

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

575 '''

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

577

578 @Property_RO

579 def z(self):

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

581 '''

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

583

584

585 class Ned4Tuple(_NamedTuple):

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

587 '''

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

589 _Units_ = ( Meter, Meter, Meter, _Pass)

590

591 def _toNed(self, Cls, Cls_kwds):

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

593 '''

594 if issubclassof(Cls, Ned):

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

596 else:

597 return _4Tuple2Cls(self, Cls, Cls_kwds)

598

599 @Property_RO

600 def xyzLocal(self):

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

602 '''

603 return Ned(self).xyzLocal

604

605

606 class XyzLocal(Vector3d):

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

608 also base class for local L{Enu}.

609 '''

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

611 _toStr = _xyz_

612

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

614 '''New L{XyzLocal}.

615

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

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

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

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

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

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

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

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

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

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

626 L{LocalCartesian}).

627

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

629

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

631 '''

632 if isscalar(x_xyz):

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

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

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

636 p, n = ltp, name

637 else:

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

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

640 p = _xattr(xyz, ltp=ltp)

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

642

643 if p:

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

645 if n:

646 self.name = n

647

648 def __str__(self):

649 return self.toStr()

650

651 @Property_RO

652 def aer4(self):

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

654 '''

655 return _xyz2aer4(self)

656

657 @Property_RO

658 def azimuth(self):

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

660

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

662 Transformations_between_ECEF_and_ENU_coordinates>}.

663 '''

664 return self.aer4.azimuth

665

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

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

668

669 @arg args: Optional, positional arguments.

670 @kwarg kwds: Optional, keyword arguments.

671

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

673 '''

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

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

676

677 @Property_RO

678 def down(self):

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

680 '''

681 return Meter(down=-self.z)

682

683 @property_RO

684 def ecef(self):

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

686 '''

687 return self.ltp.ecef

688

689 @Property_RO

690 def east(self):

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

692 '''

693 return Meter(east=self.x)

694

695 @Property_RO

696 def elevation(self):

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

698

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

700 Transformations_between_ECEF_and_ENU_coordinates>}.

701 '''

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

703

704 @Property_RO

705 def enu4(self):

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

707 '''

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

709

710 @Property_RO

711 def groundrange(self):

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

713 '''

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

715

716 @Property_RO

717 def ltp(self):

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

719 '''

720 return self._ltp

721

722 @Property_RO

723 def ned4(self):

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

725 '''

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

727

728 @Property_RO

729 def north(self):

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

731 '''

732 return Meter(north=self.y)

733

734 @Property_RO

735 def slantrange(self):

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

737 '''

738 return self.aer4.slantrange

739

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

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

742

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

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

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

746

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

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

749

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

751 '''

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

753

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

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

756

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

758 or C{None}.

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

760 overriding this C{ltp}.

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

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

763

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

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

766 with C{M=None}, always.

767

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

769 B{C{Cartesian_kwds}} argument.

770 '''

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

772 if Cartesian is None:

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

774 else:

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

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

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

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

779

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

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

782

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

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

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

786

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

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

789 '''

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

791

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

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

794

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

796 or C{None}.

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

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

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

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

801

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

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

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

805

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

807 B{C{LatLon_kwds}} argument.

808 '''

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

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

811 if LatLon is None:

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

813 else:

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

815 name=self.name or ltp.name)

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

817 return r

818

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

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

821

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

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

824

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

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

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

828 '''

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

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

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

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

833

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

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

836

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

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

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

840

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

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

843 '''

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

845

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

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

848

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

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

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

852

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

854 "[N:meter, E:meter, D:meter]" respectively

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

856 '''

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

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

859

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

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

862

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

864 number of (decimal) digits, unstripped

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

866 backets format (C{str}) and separator

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

868

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

870 '''

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

872 return t

873

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

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

876

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

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

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

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

881

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

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

884 '''

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

886

887 @Property_RO

888 def up(self):

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

890 '''

891 return Meter(up=self.z)

892

893# @Property_RO

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

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

896# '''

897# return self._x

898

899# @Property_RO

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

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

902# '''

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

904

905 @Property_RO

906 def xyz4(self):

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

908 '''

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

910

911 @Property_RO

912 def xyzLocal(self):

913 '''Get this L{XyzLocal}.

914 '''

915 return self

916

917# @Property_RO

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

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

920# '''

921# return self._y

922

923# @Property_RO

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

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

926# '''

927# return self._z

928

929

930 class Xyz4Tuple(_NamedTuple):

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

932 '''

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

934 _Units_ = ( Meter, Meter, Meter, _Pass)

935

936 def _toXyz(self, Cls, Cls_kwds):

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

938 '''

939 if issubclassof(Cls, XyzLocal):

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

941 else:

942 return _4Tuple2Cls(self, Cls, Cls_kwds)

943

944 @Property_RO

945 def xyzLocal(self):

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

947 '''

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

949

950

951 class Enu(XyzLocal):

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

953

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

955 Transformations_between_ECEF_and_ENU_coordinates>} coordinates.

956 '''

957 _toStr = _enu_

958

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

960 '''New L{Enu}.

961

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

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

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

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

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

967 scalar B{C{east_enu}}.

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

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

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

971 L{LocalCartesian}).

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

973

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

975

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

977 '''

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

979

980 @Property_RO

981 def xyzLocal(self):

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

983 '''

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

985

986

987 class Enu4Tuple(_NamedTuple):

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

989 '''

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

991 _Units_ = ( Meter, Meter, Meter, _Pass)

992

993 def _toEnu(self, Cls, Cls_kwds):

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

995 '''

996 if issubclassof(Cls, XyzLocal):

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

998 else:

999 return _4Tuple2Cls(self, Cls, Cls_kwds)

1000

1001 @Property_RO

1002 def xyzLocal(self):

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

1004 '''

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

1006

1007

1008 class Local9Tuple(_NamedTuple):

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

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

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

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

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

1014 '''

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

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

1017

1018 @Property_RO

1019 def azimuth(self):

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

1021 '''

1022 return self.xyzLocal.aer4.azimuth

1023

1024 @Property_RO

1025 def down(self):

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

1027 '''

1028 return -self.z

1029

1030 @Property_RO

1031 def east(self):

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

1033 '''

1034 return self.x

1035

1036 @Property_RO

1037 def elevation(self):

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

1039 '''

1040 return self.xyzLocal.aer4.elevation

1041

1042 @Property_RO

1043 def groundrange(self):

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

1045 '''

1046 return self.xyzLocal.aer4.groundrange

1047

1048 @Property_RO

1049 def lam(self):

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

1051 '''

1052 return self.philam.lam

1053

1054 @Property_RO

1055 def latlon(self):

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

1057 '''

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

1059

1060 @Property_RO

1061 def latlonheight(self):

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

1063 '''

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

1065

1066 @Property_RO

1067 def north(self):

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

1069 '''

1070 return self.y

1071

1072 @Property_RO

1073 def phi(self):

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

1075 '''

1076 return self.philam.phi

1077

1078 @Property_RO

1079 def philam(self):

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

1081 '''

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

1083

1084 @Property_RO

1085 def philamheight(self):

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

1087 '''

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

1089

1090 @Property_RO

1091 def slantrange(self):

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

1093 '''

1094 return self.xyzLocal.aer4.slantrange

1095

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

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

1098

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

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

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

1102

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

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

1105 '''

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

1107

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

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

1110

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

1112 or C{None}.

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

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

1115

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

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

1118

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

1120 argument.

1121 '''

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

1123

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

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

1126

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

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

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

1130

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

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

1133 '''

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

1135

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

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

1138

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

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

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

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

1143

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

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

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

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

1148

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

1150 argument.

1151 '''

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

1153

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

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

1156

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

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

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

1160

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

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

1163 '''

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

1165

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

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

1168

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

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

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

1172

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

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

1175 '''

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

1177

1178 @Property_RO

1179 def up(self):

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

1181 '''

1182 return self.z

1183

1184 @Property_RO

1185 def xyz(self):

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

1187 '''

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

1189

1190 @Property_RO

1191 def xyzLocal(self):

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

1193 '''

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

1195

1196

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

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

1199

1200

1201 class ChLV9Tuple(Local9Tuple):

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

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

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

1205 otherwise like L{Local9Tuple}.

1206 '''

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

1208

1209 @Property_RO

1210 def E_LV95(self):

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

1212 '''

1213 return self.EN2_LV95.E_LV95

1214

1215 @Property_RO

1216 def EN2_LV95(self):

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

1218 '''

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

1220

1221 @Property_RO

1222 def h_LV03(self):

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

1224 '''

1225 return self.h_

1226

1227 @Property_RO

1228 def h_LV95(self):

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

1230 '''

1231 return self.h_

1232

1233 @property_RO

1234 def isChLV(self):

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

1236 '''

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

1238

1239 @property_RO

1240 def isChLVa(self):

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

1242 '''

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

1244

1245 @property_RO

1246 def isChLVe(self):

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

1248 '''

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

1250

1251 @Property_RO

1252 def N_LV95(self):

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

1254 '''

1255 return self.EN2_LV95.N_LV95

1256

1257 @Property_RO

1258 def x(self):

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

1260 '''

1261 return self.Y

1262

1263 @Property_RO

1264 def x_LV03(self):

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

1266 '''

1267 return self.yx2_LV03.x_LV03

1268

1269 @Property_RO

1270 def y(self):

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

1272 '''

1273 return self.X

1274

1275 @Property_RO

1276 def y_LV03(self):

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

1278 '''

1279 return self.yx2_LV03.y_LV03

1280

1281 @Property_RO

1282 def YX(self):

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

1284 '''

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

1286

1287 @Property_RO

1288 def yx2_LV03(self):

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

1290 '''

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

1292

1293 @Property_RO

1294 def z(self):

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

1296 '''

1297 return self.h_

1298

1299

1300 class ChLVYX2Tuple(_NamedTuple):

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

1302 in C{meter}.

1303 '''

1304 _Names_ = (_Y_, _X_)

1305 _Units_ = ( Meter, Meter)

1306

1307 def false2(self, LV95=True):

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

1309

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

1311 '''

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

1313

1314

1315 class ChLVEN2Tuple(_NamedTuple):

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

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

1318 '''

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

1320 _Units_ = ChLVYX2Tuple._Units_

1321

1322 def unfalse2(self):

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

1324

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

1326 '''

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

1328

1329

1330 class ChLVyx2Tuple(_NamedTuple):

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

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

1333 '''

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

1335 _Units_ = ChLVYX2Tuple._Units_

1336

1337 def unfalse2(self):

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

1339

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

1341 '''

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

1343

1344

1345 class Footprint5Tuple(_NamedTuple):

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

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

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

1349

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

1351 '''

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

1353 _Units_ = (_Pass, _Pass, _Pass, _Pass, _Pass)

1354

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

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

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

1358

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

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

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

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

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

1364

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

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

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

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

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

1370

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

1372

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

1374 '''

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

1376 LatLon=LatLon, name=self.name,)

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

1378

1379 def xyzLocal5(self, ltp=None):

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

1381

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

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

1384

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

1386

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

1388 '''

1389 if ltp is None:

1390 p = self

1391 else:

1392 p = _MODS.ltp._xLtp(ltp)

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

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

1395

1396

1397__all__ += _ALL_DOCS(_NamedAerNed)

1398

1399# **) MIT License

1400#

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

1402#

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

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

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

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

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

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

1409#

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

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

1412#

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

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

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

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

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

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

1419# OTHER DEALINGS IN THE SOFTWARE.