Coverage for pygeodesy/cartesianBase.py: 95%

205 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-10-04 12:08 -0400

1 

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

3 

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

5C{Cartesian}s. 

6 

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

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

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

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

11''' 

12 

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

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

15 _2_0, _4_0, _6_0 

16from pygeodesy.datums import Datum, _spherical_datum, _WGS84, _xinstanceof 

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

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

19from pygeodesy.fsums import Fmt, fsumf_ 

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

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

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

23from pygeodesy.namedTuples import LatLon4Tuple, Vector4Tuple, \ 

24 Bearing2Tuple # PYCHOK .sphericalBase 

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

26 property_doc_, _update_all 

27# from pygeodesy.resections impoty cassini, collins5, pierlot, tienstra7 

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

29from pygeodesy.units import Height, _heigHt 

30from pygeodesy.vector3d import Vector3d, _xyzhdn3 

31 

32# from math import sqrt # from .fmath 

33 

34__all__ = _ALL_LAZY.cartesianBase 

35__version__ = '23.10.04' 

36 

37 

38class CartesianBase(Vector3d): 

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

40 ''' 

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

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

43 

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

45 '''New C{Cartesian...}. 

46 

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

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

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

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

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

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

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

54 or L{a_f2Tuple}). 

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

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

57 

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

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

60 L{Vector3Tuple} or L{Vector4Tuple}. 

61 ''' 

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

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

64 if h is not None: 

65 self._height = Height(h) 

66 if d is not None: 

67 self.datum = d 

68 

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

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

71# ''' 

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

73# _NotImplemented(self, other) 

74 

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

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

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

78 

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

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

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

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

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

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

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

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

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

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

89 

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

91 

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

93 

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

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

96 

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

98 

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

100 ''' 

101 return _MODS.resections.cassini(self, pointB, pointC, alpha, beta, 

102 useZ=useZ, datum=self.datum) 

103 

104 @deprecated_method 

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

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

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

108 

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

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

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

112 

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

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

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

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

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

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

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

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

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

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

123 

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

125 

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

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

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

129 

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

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

132 

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

134 

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

136 ''' 

137 return _MODS.resections.collins5(self, pointB, pointC, alpha, beta, 

138 useZ=useZ, datum=self.datum) 

139 

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

141 def datum(self): 

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

143 ''' 

144 return self._datum 

145 

146 @datum.setter # PYCHOK setter! 

147 def datum(self, datum): 

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

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

150 

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

152 ''' 

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

154 if self._datum: # is not None 

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

156 raise _IsnotError(_ellipsoidal_, datum=datum) 

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

158 raise _IsnotError(_spherical_, datum=datum) 

159 if self._datum != d: 

160 _update_all(self) 

161 self._datum = d 

162 

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

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

165 

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

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

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

169 destination or C{None}. 

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

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

172 

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

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

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

176 

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

178 B{C{Cartesian_kwds}}. 

179 ''' 

180 if Cartesian is None: 

181 r = self._ltp._local2ecef(delta, nine=True) 

182 else: 

183 r = self._ltp._local2ecef(delta, nine=False) 

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

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

186 

187 @Property_RO 

188 def Ecef(self): 

189 '''Get the ECEF I{class} (L{EcefKarney}), I{lazily}. 

190 ''' 

191 return _MODS.ecef.EcefKarney # default 

192 

193 @Property_RO 

194 def _ecef9(self): 

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

196 ''' 

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

198 

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

200 '''Compute the intersection of a Line-Of-Sight (los) from this certesian 

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

202 

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

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

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

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

207 this cartesian's C{datum} ellipsoid. 

208 

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

210 to the distance to this C{pov}. 

211 

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

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

214 points outside or away from the ellipsoid. 

215 

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

217 

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

219 ''' 

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

221 

222 @Property 

223 def height(self): 

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

225 ''' 

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

227 

228 @height.setter # PYCHOK setter! 

229 def height(self, height): 

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

231 

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

233 

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

235 ''' 

236 h = Height(height) 

237 if self._height != h: 

238 _update_all(self) 

239 self._height = h 

240 

241 @Property_RO 

242 def _height4(self): 

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

244 ''' 

245 try: 

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

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

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

249 return r 

250 

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

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

253 on this datum's ellipsoid surface. 

254 

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

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

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

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

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

260 ellipsoid's surface, otherwise the intersection of the 

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

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

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

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

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

266 

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

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

269 

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

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

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

273 

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

275 

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

277 

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

279 ''' 

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

281 if normal and d == self.datum: 

282 r = self._height4 

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

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

285 else: 

286 r = _spherical_datum(d).ellipsoid.height4(self, normal=normal) 

287 if Cartesian is not None: 

288 kwds = Cartesian_kwds.copy() 

289 h = kwds.pop(_height_, None) 

290 r = Cartesian(r, **kwds) 

291 if h is not None: 

292 r.height = Height(height=h) 

293 return r 

294 

295 @Property_RO 

296 def isEllipsoidal(self): 

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

298 ''' 

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

300 

301 @Property_RO 

302 def isSpherical(self): 

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

304 ''' 

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

306 

307 @Property_RO 

308 def latlon(self): 

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

310 ''' 

311 return self.toEcef().latlon 

312 

313 @Property_RO 

314 def latlonheight(self): 

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

316 ''' 

317 return self.toEcef().latlonheight 

318 

319 @Property_RO 

320 def latlonheightdatum(self): 

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

322 ''' 

323 return self.toEcef().latlonheightdatum 

324 

325 @Property_RO 

326 def _ltp(self): 

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

328 ''' 

329 return _MODS.ltp.Ltp(self._ecef9, ecef=self.Ecef(self.datum), name=self.name) 

330 

331 @Property_RO 

332 def _N_vector(self): 

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

334 ''' 

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

336 return _MODS.nvectorBase._N_vector_(x, y, z, h=h, name=self.name) 

337 

338 def _n_xyzh4(self, datum): 

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

340 ''' 

341 def _ErrorEPS0(x): 

342 return _ValueError(origin=self, txt=Fmt.PARENTSPACED(EPS0=x)) 

343 

344 _xinstanceof(Datum, datum=datum) 

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

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

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

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

349 E = datum.ellipsoid 

350 x, y, z = self.xyz 

351 

352 # Kenneth Gade eqn 23 

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

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

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

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

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

358 if isnear0(t): 

359 raise _ErrorEPS0(t) 

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

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

362 t = v * _2_0 

363 if t < EPS0: # isnear0 

364 raise _ErrorEPS0(t) 

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

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

367 if isnear0(k): 

368 raise _ErrorEPS0(k) 

369 t = k + E.e2 

370 if isnear0(t): 

371 raise _ErrorEPS0(t) 

372 e = k / t 

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

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

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

376 if t < EPS0: # isnear0 

377 raise _ErrorEPS0(t) 

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

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

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

381 

382 @Property_RO 

383 def philam(self): 

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

385 ''' 

386 return self.toEcef().philam 

387 

388 @Property_RO 

389 def philamheight(self): 

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

391 ''' 

392 return self.toEcef().philamheight 

393 

394 @Property_RO 

395 def philamheightdatum(self): 

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

397 ''' 

398 return self.toEcef().philamheightdatum 

399 

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

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

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

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

404 

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

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

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

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

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

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

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

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

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

414 (C{bool}). 

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

416 

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

418 

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

420 

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

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

423 

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

425 

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

427 ''' 

428 return _MODS.resections.pierlot(self, point2, point3, alpha12, alpha23, 

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

430 

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

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

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

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

435 

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

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

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

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

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

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

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

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

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

445 

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

447 

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

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

450 

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

452 

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

454 ''' 

455 return _MODS.resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3, 

456 useZ=useZ, datum=self.datum) 

457 

458 @deprecated_method 

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

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

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

462 

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

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

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

466 

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

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

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

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

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

472 non-negative). 

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

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

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

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

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

478 (C{bool}). 

479 

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

481 

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

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

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

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

486 

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

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

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

490 

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

492 

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

494 ''' 

495 return _MODS.resections.tienstra7(self, pointB, pointC, alpha, beta, gamma, 

496 useZ=useZ, datum=self.datum) 

497 

498 @deprecated_method 

499 def to2ab(self): # PYCHOK no cover 

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

501 

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

503 ''' 

504 return self.philam 

505 

506 @deprecated_method 

507 def to2ll(self): # PYCHOK no cover 

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

509 

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

511 ''' 

512 return self.latlon 

513 

514 @deprecated_method 

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

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

517 

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

519 

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

521 as its name may suggest. 

522 ''' 

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

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

525 

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

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

528# ''' 

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

530# if LL is not None: 

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

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

533# setattr(r, n, v) 

534# return r 

535 

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

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

538 

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

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

541 

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

543 

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

545 invalid. 

546 ''' 

547 _xinstanceof(Datum, datum2=datum2) 

548 

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

550 self.toDatum(datum) 

551 

552 i, d = False, c.datum 

553 if d == datum2: 

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

555 

556 elif d == _WGS84: 

557 d = datum2 # convert from WGS84 to datum2 

558 

559 elif datum2 == _WGS84: 

560 i = True # convert to WGS84 by inverse transformation 

561 

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

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

564 d = datum2 

565 

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

567 

568 convertDatum = toDatum # for backward compatibility 

569 

570 def toEcef(self): 

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

572 

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

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

575 

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

577 ''' 

578 return self._ecef9 

579 

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

581 '''Convert this cartesian to a geodetic (lat-/longitude) point. 

582 

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

584 or L{a_f2Tuple}). 

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

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

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

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

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

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

591 

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

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

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

595 

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

597 ''' 

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

599 if d == self.datum: 

600 r = self.toEcef() 

601 else: 

602 c = self.toDatum(d) 

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

604 

605 if LatLon: # class or .classof 

606 h = _heigHt(r, height) 

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

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

609 _xdatum(r.datum, d) 

610 return r 

611 

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

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

614 

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

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

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

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

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

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

621 

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

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

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

625 

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

627 ''' 

628 p = _MODS.ltp._xLtp(ltp, self._ltp) 

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

630 

631 def toLtp(self, Ecef=None): 

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

633 

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

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

636 ''' 

637 return self._ltp if Ecef in (None, self.Ecef) else _MODS.ltp.Ltp( 

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

639 

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

641 '''Convert this cartesian to C{n-vector} components. 

642 

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

644 components (C{Nvector}) or C{None}. 

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

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

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

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

649 

650 @return: The C{unit, n-vector} components (B{C{Nvector}}) or a 

651 L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector}} is C{None}. 

652 

653 @raise TypeError: Invalid B{C{datum}}. 

654 

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

656 

657 @example: 

658 

659 >>> c = Cartesian(3980581, 97, 4966825) 

660 >>> n = c.toNvector() # (x=0.622818, y=0.00002, z=0.782367, h=0.242887) 

661 ''' 

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

663 r = self._N_vector.xyzh if self.datum == d else self._n_xyzh4(d) 

664 

665 if Nvector is not None: 

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

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

668 return r 

669 

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

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

672 

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

674 @kwarg fmt: Enclosing backets format (string). 

675 @kwarg sep: Separator to join (string). 

676 

677 @return: Cartesian represented as "[x, y, z]" (string). 

678 ''' 

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

680 

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

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

683 to this cartesian. 

684 

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

686 @kwarg inverse: Apply the inverse of the Helmert 

687 transform (C{bool}). 

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

689 overriding this cartesian's datum. 

690 

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

692 

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

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

695 ''' 

696 d = datum or self.datum 

697 if inverse and d != _WGS84: 

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

699 txt=_not_(_WGS84.name)) 

700 

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

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

703 

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

705 '''Return this cartesian's components as vector. 

706 

707 @kwarg Vector: Optional class to return the C{n-vector} 

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

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

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

711 

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

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

714 

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

716 ''' 

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

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

719 

720 

721__all__ += _ALL_DOCS(CartesianBase) 

722 

723# **) MIT License 

724# 

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

726# 

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

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

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

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

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

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

733# 

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

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

736# 

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

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

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

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

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

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

743# OTHER DEALINGS IN THE SOFTWARE.