1

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

3

4 u'''(INTERNAL) Private base classes for elliposiodal, spherical and N-/vectorial

5 C{Cartesian}s.

6

7 After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**,

8 see U{https://www.Movable-Type.co.UK/scripts/latlong.html},

9 U{https://www.Movable-Type.co.UK/scripts/latlong-vectors.html} and

10 U{https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html}.

11'''

12

13# from pygeodesy.basics import _xinstanceof # from .datums

14 from pygeodesy.constants import EPS, EPS0, isnear0, _1_0, _N_1_0, \

15 _2_0, _4_0, _6_0

16 from pygeodesy.datums import Datum, _earth_ellipsoid, _spherical_datum, \

17 _WGS84, _xinstanceof

18 from pygeodesy.errors import _IsnotError, _ValueError, _xdatum, _xkwds

19 from pygeodesy.fmath import cbrt, hypot_, hypot2, sqrt # hypot

20 from pygeodesy.fsums import Fmt, fsumf_

21 from pygeodesy.interns import NN, _COMMASPACE_, _height_, _not_

22 from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!

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

24 from pygeodesy.namedTuples import LatLon4Tuple, Vector4Tuple, \

25 Bearing2Tuple # PYCHOK .sphericalBase

26 from pygeodesy.props import deprecated_method, Property, Property_RO, \

27 property_doc_, property_RO, _update_all

28# from pygeodesy.resections import cassini, collins5, pierlot, tienstra7

29# from pygeodesy.streprs import Fmt # from .fsums

30 from pygeodesy.units import Height, _heigHt

31 from pygeodesy.vector3d import Vector3d, _xyzhdn3

32

33# from math import sqrt # from .fmath

34

35__all__ = _ALL_LAZY.cartesianBase

36__version__ = '23.12.18'

37

38

39 class CartesianBase(Vector3d):

40 '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.

41 '''

42 _datum = None # L{Datum}, to be overriden

43 _height = None # height (L{Height}), set or approximated

44

45 def __init__(self, x_xyz, y=None, z=None, datum=None, ll=None, name=NN):

46 '''New C{Cartesian...}.

47

48 @arg x_xyz: Cartesian X coordinate (C{scalar}) or a C{Cartesian},

49 L{Ecef9Tuple}, L{Vector3Tuple} or L{Vector4Tuple}.

50 @kwarg y: Cartesian Y coordinate (C{scalar}), ignored if B{C{x_xyz}}

51 is not C{scalar}, otherwise same units as B{C{x_xyz}}.

52 @kwarg z: Cartesian Z coordinate (C{scalar}), ignored if B{C{x_xyz}}

53 is not C{scalar}, otherwise same units as B{C{x_xyz}}.

54 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}

55 or L{a_f2Tuple}).

56 @kwarg ll: Optional, original latlon (C{LatLon}).

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

58

59 @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}}

60 coordinate or B{C{x_xyz}} not an L{Ecef9Tuple},

61 L{Vector3Tuple} or L{Vector4Tuple}.

62 '''

63 h, d, n = _xyzhdn3(x_xyz, None, datum, ll)

64 Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=name or n)

65 if h is not None:

66 self._height = Height(h)

67 if d is not None:

68 self.datum = d

69

70# def __matmul__(self, other): # PYCHOK Python 3.5+

71# '''Return C{NotImplemented} for C{c_ = c @ datum} and C{c_ = c @ transform}.

72# '''

73# return NotImplemented if isinstance(other, (Datum, Transform)) else \

74# _NotImplemented(self, other)

75

76 def cassini(self, pointB, pointC, alpha, beta, useZ=False):

77 '''3-Point resection between this and 2 other points using U{Cassini

78 <https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.

79

80 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

81 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

82 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

83 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

84 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to

85 B{C{pointC}} (C{degrees}, non-negative).

86 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to

87 B{C{pointC}} (C{degrees}, non-negative).

88 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

89 force C{z=INT0} (C{bool}).

90

91 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.

92

93 @return: The survey point, an instance of this (sub-)class.

94

95 @raise ResectionError: Near-coincident, -colinear or -concyclic points

96 or negative or invalid B{C{alpha}} or B{C{beta}}.

97

98 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.

99

100 @see: Function L{pygeodesy.cassini} for references and more details.

101 '''

102 return self._resections.cassini(self, pointB, pointC, alpha, beta,

103 useZ=useZ, datum=self.datum)

104

105 @deprecated_method

106 def collins(self, pointB, pointC, alpha, beta, useZ=False):

107 '''DEPRECATED, use method L{collins5}.'''

108 return self.collins5(pointB, pointC, alpha, beta, useZ=useZ)

109

110 def collins5(self, pointB, pointC, alpha, beta, useZ=False):

111 '''3-Point resection between this and 2 other points using U{Collins<https://Dokumen.tips/

112 documents/three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.

113

114 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

115 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

116 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

117 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

118 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to

119 B{C{pointC}} (C{degrees}, non-negative).

120 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to

121 B{C{pointC}} (C{degrees}, non-negative).

122 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

123 force C{z=INT0} (C{bool}).

124

125 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.

126

127 @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},

128 auxiliary C{pointH}, each an instance of this (sub-)class and

129 triangle sides C{a}, C{b} and C{c}.

130

131 @raise ResectionError: Near-coincident, -colinear or -concyclic points

132 or negative or invalid B{C{alpha}} or B{C{beta}}.

133

134 @raise TypeError: Invalid B{C{pointB}} or B{C{pointM}}.

135

136 @see: Function L{pygeodesy.collins5} for references and more details.

137 '''

138 return self._resections.collins5(self, pointB, pointC, alpha, beta,

139 useZ=useZ, datum=self.datum)

140

141 @deprecated_method

142 def convertDatum(self, datum2, **datum):

143 '''DEPRECATED, use method L{toDatum}.'''

144 return self.toDatum(datum2, **datum)

145

146 @property_doc_(''' this cartesian's datum (L{Datum}).''')

147 def datum(self):

148 '''Get this cartesian's datum (L{Datum}).

149 '''

150 return self._datum

151

152 @datum.setter # PYCHOK setter!

153 def datum(self, datum):

154 '''Set this cartesian's C{datum} I{without conversion}

155 (L{Datum}), ellipsoidal or spherical.

156

157 @raise TypeError: The B{C{datum}} is not a L{Datum}.

158 '''

159 d = _spherical_datum(datum, name=self.name)

160 if self._datum: # is not None

161 if d.isEllipsoidal and not self._datum.isEllipsoidal:

162 raise _IsnotError(_ellipsoidal_, datum=datum)

163 elif d.isSpherical and not self._datum.isSpherical:

164 raise _IsnotError(_spherical_, datum=datum)

165 if self._datum != d:

166 _update_all(self)

167 self._datum = d

168

169 def destinationXyz(self, delta, Cartesian=None, **Cartesian_kwds):

170 '''Calculate the destination using a I{local} delta from this cartesian.

171

172 @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu},

173 L{Ned} or L{Local9Tuple}).

174 @kwarg Cartesian: Optional (geocentric) class to return the

175 destination or C{None}.

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

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

178

179 @return: Destination as a C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})}

180 instance or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,

181 z, lat, lon, height, C, M, datum)} with C{M=None} always.

182

183 @raise TypeError: Invalid B{C{delta}}, B{C{Cartesian}} or

184 B{C{Cartesian_kwds}}.

185 '''

186 if Cartesian is None:

187 r = self._Ltp._local2ecef(delta, nine=True)

188 else:

189 r = self._Ltp._local2ecef(delta, nine=False)

190 r = Cartesian(*r, **_xkwds(Cartesian_kwds, datum=self.datum))

191 return r._xnamed(r) if self.name else r

192

193 @property_RO

194 def Ecef(self):

195 '''Get the ECEF I{class} (L{EcefKarney}), I{once}.

196 '''

197 CartesianBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO

198 return E

199

200 @Property_RO

201 def _ecef9(self):

202 '''(INTERNAL) Helper for L{toEcef}, L{toLocal} and L{toLtp} (L{Ecef9Tuple}).

203 '''

204 return self.Ecef(self.datum, name=self.name).reverse(self, M=True)

205

206 @property_RO

207 def ellipsoidalCartesian(self):

208 '''Get the C{Cartesian type} iff ellipsoidal, overloaded in L{CartesianEllipsoidalBase}.

209 '''

210 return False

211

212 def hartzell(self, los=None, earth=None):

213 '''Compute the intersection of a Line-Of-Sight (los) from this cartesian

214 Point-Of-View (pov) with this cartesian's ellipsoid surface.

215

216 @kwarg los: Line-Of-Sight, I{direction} to earth (L{Los}, L{Vector3d})

217 or C{None} to point to the ellipsoid's center.

218 @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2},

219 L{a_f2Tuple} or C{scalar} radius in C{meter}) overriding

220 this cartesian's C{datum} ellipsoid.

221

222 @return: The ellipsoid intersection (C{Cartesian}) with C{.height} set

223 to the distance to this C{pov}.

224

225 @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov}

226 is inside the ellipsoid or B{C{los}} points

227 outside or away from the ellipsoid.

228

229 @raise TypeError: Invalid B{C{los}} or no B{C{datum}}.

230

231 @see: Function C{hartzell} for further details.

232 '''

233 return _MODS.formy._hartzell(self, los, earth)

234

235 @Property

236 def height(self):

237 '''Get the height (C{meter}).

238 '''

239 return self._height4.h if self._height is None else self._height

240

241 @height.setter # PYCHOK setter!

242 def height(self, height):

243 '''Set the height (C{meter}).

244

245 @raise TypeError: Invalid B{C{height}} C{type}.

246

247 @raise ValueError: Invalid B{C{height}}.

248 '''

249 h = Height(height)

250 if self._height != h:

251 _update_all(self)

252 self._height = h

253

254 @Property_RO

255 def _height4(self):

256 '''(INTERNAL) Get this C{height4}-tuple.

257 '''

258 try:

259 r = self.datum.ellipsoid.height4(self, normal=True)

260 except (AttributeError, ValueError): # no datum, null cartesian,

261 r = Vector4Tuple(self.x, self.y, self.z, 0, name=self.height4.__name__)

262 return r

263

264 def height4(self, earth=None, normal=True, Cartesian=None, **Cartesian_kwds):

265 '''Compute the height of this cartesian above or below and the projection

266 on this datum's ellipsoid surface.

267

268 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius

269 I{overriding} this datum (L{Datum}, L{Ellipsoid},

270 L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},

271 L{JacobiConformal} or C{meter}, conventionally).

272 @kwarg normal: If C{True} the projection is the nearest point on the

273 ellipsoid's surface, otherwise the intersection of the

274 radial line to the center and the ellipsoid's surface.

275 @kwarg Cartesian: Optional class to return the height and projection

276 (C{Cartesian}) or C{None}.

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

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

279

280 @note: Use keyword argument C{height=0} to override C{B{Cartesian}.height}

281 to {0} or any other C{scalar}, conventionally in C{meter}.

282

283 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None}, a

284 L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}

285 and C{z} coordinates and height C{h} in C{meter}, conventionally.

286

287 @raise TriaxialError: No convergence in triaxial root finding.

288

289 @raise TypeError: Invalid B{C{earth}}.

290

291 @see: L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.

292 '''

293 d = self.datum if earth is None else earth

294 if normal and d is self.datum:

295 r = self._height4

296 elif isinstance(d, _MODS.triaxials.Triaxial_):

297 r = d.height4(self, normal=normal)

298 else:

299 r = _earth_ellipsoid(d).height4(self, normal=normal)

300 if Cartesian is not None:

301 kwds = Cartesian_kwds.copy()

302 h = kwds.pop(_height_, None)

303 r = Cartesian(r, **kwds)

304 if h is not None:

305 r.height = Height(height=h)

306 return r

307

308 @Property_RO

309 def isEllipsoidal(self):

310 '''Check whether this cartesian is ellipsoidal (C{bool} or C{None} if unknown).

311 '''

312 return self.datum.isEllipsoidal if self._datum else None

313

314 @Property_RO

315 def isSpherical(self):

316 '''Check whether this cartesian is spherical (C{bool} or C{None} if unknown).

317 '''

318 return self.datum.isSpherical if self._datum else None

319

320 @Property_RO

321 def latlon(self):

322 '''Get this cartesian's (geodetic) lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).

323 '''

324 return self.toEcef().latlon

325

326 @Property_RO

327 def latlonheight(self):

328 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height (L{LatLon3Tuple}C{(lat, lon, height)}).

329 '''

330 return self.toEcef().latlonheight

331

332 @Property_RO

333 def latlonheightdatum(self):

334 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).

335 '''

336 return self.toEcef().latlonheightdatum

337

338 @property_RO

339 def _ltp(self):

340 '''(INTERNAL) Get module C{.ltp}, I{once}.

341 '''

342 CartesianBase._ltp = m = _MODS.ltp # overwrite property_RO

343 return m

344

345 @Property_RO

346 def _Ltp(self):

347 '''(INTERNAL) Cache for L{toLtp}.

348 '''

349 return self._ltp.Ltp(self._ecef9, ecef=self.Ecef(self.datum), name=self.name)

350

351 @Property_RO

352 def _N_vector(self):

353 '''(INTERNAL) Get the (C{nvectorBase._N_vector_}).

354 '''

355 m = _MODS.nvectorBase

356 x, y, z, h = self._n_xyzh4(self.datum)

357 return m._N_vector_(x, y, z, h=h, name=self.name)

358

359 def _n_xyzh4(self, datum):

360 '''(INTERNAL) Get the n-vector components as L{Vector4Tuple}.

361 '''

362 def _ErrorEPS0(x):

363 return _ValueError(origin=self, txt=Fmt.PARENSPACED(EPS0=x))

364

365 _xinstanceof(Datum, datum=datum)

366 # <https://www.Movable-Type.co.UK/scripts/geodesy/docs/

367 # latlon-nvector-ellipsoidal.js.html#line309>,

368 # <https://GitHub.com/pbrod/nvector>/src/nvector/core.py>

369 # _equation23 and <https://www.NavLab.net/nvector>

370 E = datum.ellipsoid

371 x, y, z = self.xyz

372

373 # Kenneth Gade eqn 23

374 p = hypot2(x, y) * E.a2_

375 q = z**2 * E.e21 * E.a2_

376 r = fsumf_(p, q, -E.e4) / _6_0

377 s = (p * q * E.e4) / (_4_0 * r**3)

378 t = cbrt(fsumf_(_1_0, s, sqrt(s * (_2_0 + s))))

379 if isnear0(t):

380 raise _ErrorEPS0(t)

381 u = fsumf_(_1_0, t, _1_0 / t) * r

382 v = sqrt(u**2 + E.e4 * q)

383 t = v * _2_0

384 if t < EPS0: # isnear0

385 raise _ErrorEPS0(t)

386 w = fsumf_(u, v, -q) * E.e2 / t

387 k = sqrt(fsumf_(u, v, w**2)) - w

388 if isnear0(k):

389 raise _ErrorEPS0(k)

390 t = k + E.e2

391 if isnear0(t):

392 raise _ErrorEPS0(t)

393 e = k / t

394# d = e * hypot(x, y)

395# tmp = 1 / hypot(d, z) == 1 / hypot(e * hypot(x, y), z)

396 t = hypot_(x * e, y * e, z) # == 1 / tmp

397 if t < EPS0: # isnear0

398 raise _ErrorEPS0(t)

399 h = fsumf_(k, E.e2, _N_1_0) / k * t

400 s = e / t # == e * tmp

401 return Vector4Tuple(x * s, y * s, z / t, h, name=self.name)

402

403 @Property_RO

404 def philam(self):

405 '''Get this cartesian's (geodetic) lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).

406 '''

407 return self.toEcef().philam

408

409 @Property_RO

410 def philamheight(self):

411 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height (L{PhiLam3Tuple}C{(phi, lam, height)}).

412 '''

413 return self.toEcef().philamheight

414

415 @Property_RO

416 def philamheightdatum(self):

417 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).

418 '''

419 return self.toEcef().philamheightdatum

420

421 def pierlot(self, point2, point3, alpha12, alpha23, useZ=False, eps=EPS):

422 '''3-Point resection between this and two other points using U{Pierlot

423 <http://www.Telecom.ULg.ac.Be/triangulation>}'s method C{ToTal} with

424 I{approximate} limits for the (pseudo-)singularities.

425

426 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

427 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

428 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

429 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

430 @arg alpha12: Angle subtended from this point to B{C{point2}} or

431 B{C{alpha2 - alpha}} (C{degrees}).

432 @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or

433 B{C{alpha3 - alpha2}} (C{degrees}).

434 @kwarg useZ: If C{True}, interpolate the Z component, otherwise use C{z=INT0}

435 (C{bool}).

436 @kwarg eps: Tolerance for C{cot} (pseudo-)singularities (C{float}).

437

438 @note: This point, B{C{point2}} and B{C{point3}} are ordered counter-clockwise.

439

440 @return: The survey (or robot) point, an instance of this (sub-)class.

441

442 @raise ResectionError: Near-coincident, -colinear or -concyclic points

443 or invalid B{C{alpha12}} or B{C{alpha23}}.

444

445 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.

446

447 @see: Function L{pygeodesy.pierlot} for references and more details.

448 '''

449 return self._resections.pierlot(self, point2, point3, alpha12, alpha23,

450 useZ=useZ, eps=eps, datum=self.datum)

451

452 def pierlotx(self, point2, point3, alpha1, alpha2, alpha3, useZ=False):

453 '''3-Point resection between this and two other points using U{Pierlot

454 <http://www.Telecom.ULg.ac.Be/publi/publications/pierlot/Pierlot2014ANewThree>}'s

455 method C{ToTal} with I{exact} limits for the (pseudo-)singularities.

456

457 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

458 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

459 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

460 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

461 @arg alpha1: Angle at B{C{point1}} (C{degrees}).

462 @arg alpha2: Angle at B{C{point2}} (C{degrees}).

463 @arg alpha3: Angle at B{C{point3}} (C{degrees}).

464 @kwarg useZ: If C{True}, interpolate the survey point's Z component,

465 otherwise use C{z=INT0} (C{bool}).

466

467 @return: The survey (or robot) point, an instance of this (sub-)class.

468

469 @raise ResectionError: Near-coincident, -colinear or -concyclic points or

470 invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.

471

472 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.

473

474 @see: Function L{pygeodesy.pierlotx} for references and more details.

475 '''

476 return self._resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3,

477 useZ=useZ, datum=self.datum)

478

479 @property_RO

480 def _resections(self):

481 '''(INTERNAL) Import the C{.resections} module, I{once}.

482 '''

483 CartesianBase._resections = m = _MODS.resections # overwrite property_RO

484 return m

485

486 @property_RO

487 def sphericalCartesian(self):

488 '''Get the C{Cartesian type} iff spherical, overloaded in L{CartesianSphericalBase}.

489 '''

490 return False

491

492 @deprecated_method

493 def tienstra(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):

494 '''DEPRECATED, use method L{tienstra7}.'''

495 return self.tienstra7(pointB, pointC, alpha, beta=beta, gamma=gamma, useZ=useZ)

496

497 def tienstra7(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):

498 '''3-Point resection between this and two other points using U{Tienstra

499 <https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.

500

501 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

502 C{Vector2Tuple} if C{B{useZ}=False}).

503 @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

504 C{Vector2Tuple} if C{B{useZ}=False}).

505 @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}} (C{degrees},

506 non-negative).

507 @kwarg beta: Angle subtended by triangle side C{b} from this to B{C{pointC}} (C{degrees},

508 non-negative) or C{None} if C{B{gamma} is not None}.

509 @kwarg gamma: Angle subtended by triangle side C{c} from this to B{C{pointB}} (C{degrees},

510 non-negative) or C{None} if C{B{beta} is not None}.

511 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}

512 (C{bool}).

513

514 @note: This point, B{C{pointB}} and B{C{pointC}} are ordered clockwise.

515

516 @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP},

517 an instance of this (sub-)class and triangle angle C{A} at this point,

518 C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees} and

519 triangle sides C{a}, C{b} and C{c}.

520

521 @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of

522 B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or

523 negative B{C{alpha}}, B{C{beta}} or B{C{gamma}}.

524

525 @raise TypeError: Invalid B{C{pointB}} or B{C{pointC}}.

526

527 @see: Function L{pygeodesy.tienstra7} for references and more details.

528 '''

529 return self._resections.tienstra7(self, pointB, pointC, alpha, beta, gamma,

530 useZ=useZ, datum=self.datum)

531

532 @deprecated_method

533 def to2ab(self): # PYCHOK no cover

534 '''DEPRECATED, use property C{philam}.

535

536 @return: A L{PhiLam2Tuple}C{(phi, lam)}.

537 '''

538 return self.philam

539

540 @deprecated_method

541 def to2ll(self): # PYCHOK no cover

542 '''DEPRECATED, use property C{latlon}.

543

544 @return: A L{LatLon2Tuple}C{(lat, lon)}.

545 '''

546 return self.latlon

547

548 @deprecated_method

549 def to3llh(self, datum=None): # PYCHOK no cover

550 '''DEPRECATED, use property L{latlonheight} or L{latlonheightdatum}.

551

552 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.

553

554 @note: This method returns a B{C{-4Tuple}} I{and not a} C{-3Tuple}

555 as its name may suggest.

556 '''

557 t = self.toLatLon(datum=datum, LatLon=None)

558 return LatLon4Tuple(t.lat, t.lon, t.height, t.datum, name=self.name)

559

560# def _to3LLh(self, datum, LL, **pairs): # OBSOLETE

561# '''(INTERNAL) Helper for C{subclass.toLatLon} and C{.to3llh}.

562# '''

563# r = self.to3llh(datum) # LatLon3Tuple

564# if LL is not None:

565# r = LL(r.lat, r.lon, height=r.height, datum=datum, name=self.name)

566# for n, v in pairs.items():

567# setattr(r, n, v)

568# return r

569

570 def toDatum(self, datum2, datum=None):

571 '''Convert this cartesian from one datum to an other.

572

573 @arg datum2: Datum to convert I{to} (L{Datum}).

574 @kwarg datum: Datum to convert I{from} (L{Datum}).

575

576 @return: The converted point (C{Cartesian}).

577

578 @raise TypeError: B{C{datum2}} or B{C{datum}}

579 invalid.

580 '''

581 _xinstanceof(Datum, datum2=datum2)

582

583 c = self if datum in (None, self.datum) else \

584 self.toDatum(datum)

585

586 i, d = False, c.datum

587 if d == datum2:

588 return c.copy() if c is self else c

589

590 elif d == _WGS84:

591 d = datum2 # convert from WGS84 to datum2

592

593 elif datum2 == _WGS84:

594 i = True # convert to WGS84 by inverse transformation

595

596 else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first

597 c = c.toTransform(d.transform, inverse=True, datum=_WGS84)

598 d = datum2

599

600 return c.toTransform(d.transform, inverse=i, datum=datum2)

601

602 def toEcef(self):

603 '''Convert this cartesian to I{geodetic} (lat-/longitude) coordinates.

604

605 @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height,

606 C, M, datum)} with C{C} and C{M} if available.

607

608 @raise EcefError: A C{.datum} or an ECEF issue.

609 '''

610 return self._ecef9

611

612 def toLatLon(self, datum=None, height=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.toDatum

613 '''Convert this cartesian to a I{geodetic} (lat-/longitude) point.

614

615 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}

616 or L{a_f2Tuple}).

617 @kwarg height: Optional height, overriding the converted height

618 (C{meter}), iff B{C{LatLon}} is not C{None}.

619 @kwarg LatLon: Optional class to return the geodetic point

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

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

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

623

624 @return: The geodetic point (B{C{LatLon}}) or if B{C{LatLon}}

625 is C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon,

626 height, C, M, datum)} with C{C} and C{M} if available.

627

628 @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}.

629 '''

630 d = _spherical_datum(datum or self.datum, name=self.name)

631 if d == self.datum:

632 r = self.toEcef()

633 else:

634 c = self.toDatum(d)

635 r = c.Ecef(d, name=self.name).reverse(c, M=LatLon is None)

636

637 if LatLon: # class or .classof

638 h = _heigHt(r, height)

639 r = LatLon(r.lat, r.lon, datum=r.datum, height=h,

640 **_xkwds(LatLon_kwds, name=r.name))

641 _xdatum(r.datum, d)

642 return r

643

644 def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):

645 '''Convert this I{geocentric} cartesian to I{local} C{X}, C{Y} and C{Z}.

646

647 @kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z}

648 (L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.

649 @kwarg ltp: The I{local tangent plane} (LTP) to use,

650 overriding this cartesian's LTP (L{Ltp}).

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

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

653

654 @return: An B{C{Xyz}} instance or if C{B{Xyz} is None},

655 a L{Local9Tuple}C{(x, y, z, lat, lon, height,

656 ltp, ecef, M)} with C{M=None} always.

657

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

659 '''

660 p = self._ltp._xLtp(ltp, self._Ltp)

661 return p._ecef2local(self._ecef9, Xyz, Xyz_kwds)

662

663 def toLtp(self, Ecef=None):

664 '''Return the I{local tangent plane} (LTP) for this cartesian.

665

666 @kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ...

667 L{EcefYou}), overriding this cartesian's C{Ecef}.

668 '''

669 return self._Ltp if Ecef in (None, self.Ecef) else self._ltp.Ltp(

670 self._ecef9, ecef=Ecef(self.datum), name=self.name)

671

672 def toNvector(self, Nvector=None, datum=None, **Nvector_kwds):

673 '''Convert this cartesian to C{n-vector} components, I{including height}.

674

675 @kwarg Nvector: Optional class to return the C{n-vector} components

676 (C{Nvector}) or C{None}.

677 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}

678 or L{a_f2Tuple}) overriding this cartesian's datum.

679 @kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword

680 arguments, ignored if C{B{Nvector} is None}.

681

682 @return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if

683 B{C{Nvector}} is C{None}.

684

685 @raise TypeError: Invalid B{C{Nvector}}, B{C{Nvector_kwds}} or

686 B{C{datum}}.

687

688 @raise ValueError: B{C{Cartesian}} at origin.

689 '''

690 r, d = self._N_vector.xyzh, self.datum

691 if datum is not None:

692 d = _spherical_datum(datum, name=self.name)

693 if d != self.datum:

694 r = self._n_xyzh4(d)

695

696 if Nvector is not None:

697 kwds = _xkwds(Nvector_kwds, h=r.h, datum=d)

698 r = self._xnamed(Nvector(r.x, r.y, r.z, **kwds))

699 return r

700

701 def toStr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_): # PYCHOK expected

702 '''Return the string representation of this cartesian.

703

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

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

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

707

708 @return: Cartesian represented as "[x, y, z]" (C{str}).

709 '''

710 return Vector3d.toStr(self, prec=prec, fmt=fmt, sep=sep)

711

712 def toTransform(self, transform, inverse=False, datum=None):

713 '''Return a new cartesian by applying a Helmert transform

714 to this cartesian.

715

716 @arg transform: Transform to apply (L{Transform}).

717 @kwarg inverse: Apply the inverse of the Helmert

718 transform (C{bool}).

719 @kwarg datum: Datum for the transformed cartesian (L{Datum}),

720 overriding this cartesian's datum.

721

722 @return: The transformed cartesian (C{Cartesian}).

723

724 @raise Valuerror: If C{B{inverse}=True} and B{C{datum}}

725 is not L{Datums}C{.WGS84}.

726 '''

727 d = datum or self.datum

728 if inverse and d != _WGS84:

729 raise _ValueError(inverse=inverse, datum=d,

730 txt=_not_(_WGS84.name))

731

732 xyz = transform.transform(*self.xyz, inverse=inverse)

733 return self.classof(xyz, datum=d)

734

735 def toVector(self, Vector=None, **Vector_kwds):

736 '''Return this cartesian's I{geocentric} components as vector.

737

738 @kwarg Vector: Optional class to return the I{geocentric}

739 components (L{Vector3d}) or C{None}.

740 @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword

741 arguments, ignored if C{B{Vector} is None}.

742

743 @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)} if

744 B{C{Vector}} is C{None}.

745

746 @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}}.

747 '''

748 return self.xyz if Vector is None else self._xnamed(

749 Vector(self.x, self.y, self.z, **Vector_kwds))

750

751

752__all__ += _ALL_DOCS(CartesianBase)

753

754# **) MIT License

755#

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

757#

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

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

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

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

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

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

764#

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

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

767#

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

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

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

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

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

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

774# OTHER DEALINGS IN THE SOFTWARE.