Coverage for pygeodesy/cartesianBase.py: 92%

320 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-04 12:01 -0400

1 

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

3 

4u'''(INTERNAL) Private C{CartesianBase} class for elliposiodal, spherical and N-/vectorial 

5C{Cartesian}s and public functions L{rtp2xyz}, L{rtp2xyz_}, L{xyz2rtp} and L{xyz2rtp_}. 

6 

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

8U{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 

13from pygeodesy.basics import _isin, _xinstanceof, typename 

14from pygeodesy.constants import EPS, EPS0, INT0, PI2, _isfinite, isnear0, \ 

15 _0_0, _1_0, _N_1_0, _2_0, _4_0, _6_0 

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

17 Transform, _WGS84 

18# from pygeodesy.ecef import EcefKarney # _MODS 

19from pygeodesy.ecefLocals import _EcefLocal 

20from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, _xattr, \ 

21 _xdatum, _xkwds, _xkwds_get, _xkwds_pop2 

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

23# from pygeodesy.formy import _hartzell # _MODS 

24from pygeodesy.fsums import fsumf_, Fmt 

25# from pygeodesy.internals import typename # from .basics 

26from pygeodesy.interns import _COMMASPACE_, _datum_, _no_, _phi_ 

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

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

29from pygeodesy.named import _name2__, _Pass 

30from pygeodesy.namedTuples import LatLon4Tuple, _NamedTupleTo , Vector3Tuple, \ 

31 Vector4Tuple, Bearing2Tuple # PYCHOK .sphericalBase 

32# from pygeodesy.nvectorBase import _N_vector # _MODS 

33from pygeodesy.props import deprecated_method, Property, Property_RO, property_doc_, \ 

34 property_RO, _update_all 

35# from pygeodesy import resections as _resections # _MODS.into 

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

37# from pygeodesy.triaxials import Triaxial_ # _MODS 

38from pygeodesy.units import Degrees, Height, _heigHt, _isMeter, Meter, Radians 

39from pygeodesy.utily import acos1, atan2, sincos2d, sincos2_, degrees, radians 

40from pygeodesy.vector3d import Vector3d, _xyzhdlln4 

41# from pygeodesy.vector3dBase import _xyz3 # _MODS 

42# from pygeodesy import ltp # _MODS 

43 

44# from math import degrees, fabs, radians, sqrt # from .fmath, .utily 

45 

46__all__ = _ALL_LAZY.cartesianBase 

47__version__ = '25.04.28' 

48 

49_r_ = 'r' 

50_resections = _MODS.into(resections=__name__) 

51_theta_ = 'theta' 

52 

53 

54class CartesianBase(Vector3d, _EcefLocal): 

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

56 ''' 

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

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

59 

60 def __init__(self, x_xyz, y=None, z=None, datum=None, **ll_name): 

61 '''New C{Cartesian...}. 

62 

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

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

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

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

67 @kwarg z: Cartesian Z coordinate (C{scalar}), like B{C{y}}. 

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

69 or L{a_f2Tuple}). 

70 @kwarg ll_name: Optional C{B{name}=NN} (C{str}) and optional, original 

71 latlon C{B{ll}=None} (C{LatLon}). 

72 

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

74 or B{C{x_xyz}} not a C{Cartesian}, L{Ecef9Tuple}, 

75 L{Vector3Tuple} or L{Vector4Tuple} or B{C{datum}} is 

76 not a L{Datum}. 

77 ''' 

78 h, d, ll, n = _xyzhdlln4(x_xyz, None, datum, **ll_name) 

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

80 if h is not None: 

81 self._height = Height(h) 

82 if d is not None: 

83 self.datum = d 

84 

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

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

87# ''' 

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

89# _NotImplemented(self, other) 

90 

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

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

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

94 

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

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

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

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

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

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

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

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

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

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

105 

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

107 

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

109 

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

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

112 

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

114 

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

116 ''' 

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

118 useZ=useZ, datum=self.datum) 

119 

120 @deprecated_method 

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

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

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

124 

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

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

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

128 

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

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

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

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

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

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

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

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

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

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

139 

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

141 

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

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

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

145 

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

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

148 

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

150 

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

152 ''' 

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

154 useZ=useZ, datum=self.datum) 

155 

156 @deprecated_method 

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

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

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

160 

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

162 def datum(self): 

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

164 ''' 

165 return self._datum 

166 

167 @datum.setter # PYCHOK setter! 

168 def datum(self, datum): 

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

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

171 

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

173 ''' 

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

175 if self._datum: # is not None 

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

177 raise _IsnotError(_ellipsoidal_, datum=datum) 

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

179 raise _IsnotError(_spherical_, datum=datum) 

180 if self._datum != d: 

181 _update_all(self) 

182 self._datum = d 

183 

184 def destinationXyz(self, delta, Cartesian=None, **name_Cartesian_kwds): 

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

186 

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

188 or L{Local9Tuple}). 

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

190 or C{None}. 

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

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

193 C{B{Cartesian} is None}. 

194 

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

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

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

198 

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

200 item or C{datum} missing or incompatible. 

201 ''' 

202 n, kwds = _name2__(name_Cartesian_kwds, _or_nameof=self) 

203 if Cartesian is None: 

204 r = self._ltp._local2ecef(delta, nine=True) # _EcefLocal._ltp 

205 else: 

206 d = self.datum 

207 if not d: 

208 raise _TypeError(delta=delta, txt=_no_(_datum_)) 

209 t = _xkwds_get(kwds, datum=d) 

210 if _xattr(t, ellipsoid=None) != d.ellipsoid: 

211 raise _TypeError(datum=t, txt=str(d)) 

212 c = self._ltp._local2ecef(delta, nine=False) # _EcefLocal._ltp 

213 r = Cartesian(*c, **kwds) 

214 return r.renamed(n) if n else r 

215 

216 @Property_RO 

217 def _ecef9(self): 

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

219 ''' 

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

221 

222 @property_RO 

223 def ellipsoidalCartesian(self): 

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

225 ''' 

226 return False 

227 

228 def hartzell(self, los=False, earth=None): 

229 '''Compute the intersection of a Line-Of-Sight from this cartesian Point-Of-View 

230 (pov) and this cartesian's C{datum} ellipsoid surface. 

231 

232 @kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}), 

233 C{True} for the I{normal, plumb} onto the surface or I{False} or 

234 C{None} to point to the center of the ellipsoid. 

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

236 or C{scalar} radius in C{meter}), overriding this cartesian's 

237 datum. 

238 

239 @return: The intersection (C{Cartesian}) with C{.height} set to the distance to 

240 this C{pov}. 

241 

242 @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside 

243 the ellipsoid or B{C{los}} points outside or away from 

244 the ellipsoid. 

245 

246 @raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}. 

247 

248 @see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details. 

249 ''' 

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

251 

252 @Property 

253 def height(self): 

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

255 ''' 

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

257 

258 @height.setter # PYCHOK setter! 

259 def height(self, height): 

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

261 

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

263 

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

265 ''' 

266 h = Height(height) 

267 if self._height != h: 

268 _update_all(self) 

269 self._height = h 

270 

271 def _height2C(self, r, Cartesian=None, datum=None, height=INT0, **kwds): 

272 '''(INTERNAL) Helper for methods C{.height3} and C{.height4}. 

273 ''' 

274 if Cartesian is not None: 

275 r = Cartesian(r, **kwds) 

276 if datum is not None: 

277 r.datum = datum 

278 if height is not None: 

279 r.height = height # Height(height) 

280 return r 

281 

282 def height3(self, earth=None, height=None, **Cartesian_and_kwds): 

283 '''Compute the cartesian at a height above or below this certesian's 

284 C{datum} ellipsoid surface. 

285 

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

287 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid}, 

288 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally). 

289 @kwarg height: The height (C{meter}, conventionally), overriding this 

290 cartesian's height. 

291 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return 

292 the cartesian I{at height} and additional B{C{Cartesian}} 

293 keyword arguments. 

294 

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

296 a L{Vector3Tuple}C{(x, y, z)} with the C{x}, C{y} and C{z} 

297 coordinates I{at height} in C{meter}, conventionally. 

298 

299 @note: This cartesian's coordinates are returned if B{C{earth}} and this 

300 datum or B{C{height}} and/or this height are C{None} or undefined. 

301 

302 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}} 

303 does not accept a B{C{datum}} keyword agument. 

304 

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

306 

307 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}. 

308 ''' 

309 n = typename(self.height3) 

310 d = self.datum if earth is None else _spherical_datum(earth, name=n) 

311 c, h = self, _heigHt(self, height) 

312 if h and d: 

313 R, r = self.Roc2(earth=d) 

314 if R > EPS0: 

315 R = (R + h) / R 

316 r = ((r + h) / r) if r > EPS0 else _1_0 

317 c = c.times_(R, R, r) 

318 

319 r = Vector3Tuple(c.x, c.y, c.z, name=n) 

320 if Cartesian_and_kwds: 

321 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d)) 

322 return r 

323 

324 @Property_RO 

325 def _height4(self): 

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

327 ''' 

328 try: 

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

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

331 r = Vector4Tuple(self.x, self.y, self.z, 0, name__=self.height4) 

332 return r 

333 

334 def height4(self, earth=None, normal=True, **Cartesian_and_kwds): 

335 '''Compute the projection of this point on and the height above or below 

336 this datum's ellipsoid surface. 

337 

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

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

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

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

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

343 ellipsoid's surface, otherwise the intersection of the 

344 radial line to the ellipsoid's center and surface C{bool}). 

345 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return 

346 the I{projection} and additional B{C{Cartesian}} keyword 

347 arguments. 

348 

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

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

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

352 

353 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}} 

354 does not accept a B{C{datum}} keyword agument. 

355 

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

357 

358 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}. 

359 

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

361 ''' 

362 n = typename(self.height4) 

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

364 if normal and d is self.datum: 

365 r = self._height4 

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

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

368 try: 

369 d = d.toEllipsoid(name=n) 

370 except (TypeError, ValueError): # TriaxialError 

371 d = None 

372 else: 

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

374 

375 if Cartesian_and_kwds: 

376 if d and not isinstance(d, Datum): 

377 d = _spherical_datum(d, name=n) 

378 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d)) 

379 return r 

380 

381 @Property_RO 

382 def isEllipsoidal(self): 

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

384 ''' 

385 return _xattr(self.datum, isEllipsoidal=None) 

386 

387 @Property_RO 

388 def isSpherical(self): 

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

390 ''' 

391 return _xattr(self.datum, isSpherical=None) 

392 

393 @Property_RO 

394 def latlon(self): 

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

396 ''' 

397 return self.toEcef().latlon 

398 

399 @Property_RO 

400 def latlonheight(self): 

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

402 ''' 

403 return self.toEcef().latlonheight 

404 

405 @Property_RO 

406 def latlonheightdatum(self): 

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

408 ''' 

409 return self.toEcef().latlonheightdatum 

410 

411 @Property_RO 

412 def _N_vector(self): 

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

414 ''' 

415 _N = _MODS.nvectorBase._N_vector_ 

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

417 return _N(x, y, z, h=h, name=self.name) 

418 

419 def _n_xyzh4(self, datum): 

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

421 ''' 

422 def _ErrorEPS0(x): 

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

424 

425 _xinstanceof(Datum, datum=datum) 

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

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

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

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

430 E = datum.ellipsoid 

431 x, y, z = self.xyz3 

432 

433 # Kenneth Gade eqn 23 

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

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

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

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

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

439 if isnear0(t): 

440 raise _ErrorEPS0(t) 

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

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

443 t = v * _2_0 

444 if t < EPS0: # isnear0 

445 raise _ErrorEPS0(t) 

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

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

448 if isnear0(k): 

449 raise _ErrorEPS0(k) 

450 t = k + E.e2 

451 if isnear0(t): 

452 raise _ErrorEPS0(t) 

453 e = k / t 

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

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

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

457 if t < EPS0: # isnear0 

458 raise _ErrorEPS0(t) 

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

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

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

462 

463 @Property_RO 

464 def philam(self): 

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

466 ''' 

467 return self.toEcef().philam 

468 

469 @Property_RO 

470 def philamheight(self): 

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

472 ''' 

473 return self.toEcef().philamheight 

474 

475 @Property_RO 

476 def philamheightdatum(self): 

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

478 ''' 

479 return self.toEcef().philamheightdatum 

480 

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

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

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

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

485 

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

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

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

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

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

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

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

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

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

495 (C{bool}). 

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

497 

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

499 

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

501 

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

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

504 

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

506 

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

508 ''' 

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

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

511 

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

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

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

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

516 

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

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

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

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

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

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

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

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

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

526 

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

528 

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

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

531 

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

533 

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

535 ''' 

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

537 useZ=useZ, datum=self.datum) 

538 

539 def Roc2(self, earth=None): 

540 '''Compute this cartesian's I{normal} and I{pseudo, z-based} radius of curvature. 

541 

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

543 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid}, 

544 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally). 

545 

546 @return: 2-Tuple C{(R, r)} with the I{normal} and I{pseudo, z-based} radius of 

547 curvature C{R} respectively C{r}, both in C{meter} conventionally. 

548 

549 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}. 

550 ''' 

551 r = z = fabs( self.z) 

552 R, _0 = hypot(self.x, self.y), EPS0 

553 if R < _0: # polar 

554 R = z 

555 elif z > _0: # non-equatorial 

556 d = self.datum if earth is None else _spherical_datum(earth) 

557 e = self.toLatLon(datum=d, height=0, LatLon=None) # Ecef9Tuple 

558 M = e.M # EcefMatrix 

559 sa, ca = map(fabs, (M._2_2_, M._2_1_) if M else sincos2d(e.lat)) 

560 if ca < _0: # polar 

561 R = z 

562 else: # prime-vertical, normal roc R 

563 R = R / ca # /= chokes PyChecker 

564 r = R if sa < _0 else (r / sa) # non-/equatorial 

565 return R, r 

566 

567 @property_RO 

568 def sphericalCartesian(self): 

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

570 ''' 

571 return False 

572 

573 @deprecated_method 

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

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

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

577 

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

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

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

581 

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

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

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

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

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

587 non-negative). 

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

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

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

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

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

593 (C{bool}). 

594 

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

596 

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

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

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

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

601 

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

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

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

605 

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

607 

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

609 ''' 

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

611 useZ=useZ, datum=self.datum) 

612 

613 @deprecated_method 

614 def to2ab(self): # PYCHOK no cover 

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

616 

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

618 ''' 

619 return self.philam 

620 

621 @deprecated_method 

622 def to2ll(self): # PYCHOK no cover 

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

624 

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

626 ''' 

627 return self.latlon 

628 

629 @deprecated_method 

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

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

632 

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

634 

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

636 as its name may suggest. 

637 ''' 

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

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

640 

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

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

643# ''' 

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

645# if LL is not None: 

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

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

648# setattr(r, n, v) 

649# return r 

650 

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

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

653 

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

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

656 

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

658 

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

660 invalid. 

661 ''' 

662 _xinstanceof(Datum, datum2=datum2) 

663 

664 c = self if _isin(datum, None, self.datum) else \ 

665 self.toDatum(datum) 

666 

667 i, d = False, c.datum 

668 if d == datum2: 

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

670 

671 elif d is None or (d.transform.isunity and 

672 datum2.transform.isunity): 

673 return c.dup(datum=datum2) 

674 

675 elif d == _WGS84: 

676 d = datum2 # convert from WGS84 to datum2 

677 

678 elif datum2 == _WGS84: 

679 i = True # convert to WGS84 by inverse transformation 

680 

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

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

683 d = datum2 

684 

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

686 

687 def toEcef(self): 

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

689 

690 @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} 

691 with C{C} and C{M} if available. 

692 

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

694 ''' 

695 return self._ecef9 

696 

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

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

699 

700 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). 

701 @kwarg height: Optional height, overriding the converted height (C{meter}), only if 

702 C{B{LatLon} is not None}. 

703 @kwarg LatLon: Optional class to return the geodetic point (C{LatLon}) or C{None}. 

704 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if 

705 C{B{LatLon} is None}. 

706 

707 @return: The geodetic point (B{C{LatLon}}) or if C{B{LatLon}is None}, an 

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

709 and C{M} if available. 

710 

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

712 ''' 

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

714 if d == self.datum: 

715 r = self.toEcef() 

716 else: 

717 c = self.toDatum(d) 

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

719 

720 if LatLon: # class or .classof 

721 h = _heigHt(r, height) 

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

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

724 _xdatum(r.datum, d) 

725 return r 

726 

727 def toNvector(self, Nvector=None, datum=None, **name_Nvector_kwds): 

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

729 

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

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

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

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

734 @kwarg name_Nvector_kwds: Optional C{B{name}=NN} (C{str}) and optionally, 

735 additional B{C{Nvector}} keyword arguments, ignored if 

736 C{B{Nvector} is None}. 

737 

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

739 C{B{Nvector} is None}. 

740 

741 @raise TypeError: Invalid B{C{Nvector}}, B{C{datum}} or 

742 B{C{name_Nvector_kwds}} item. 

743 

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

745 ''' 

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

747 if datum is not None: 

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

749 if d != self.datum: 

750 r = self._n_xyzh4(d) 

751 

752 if Nvector is None: 

753 n, _ = _name2__(name_Nvector_kwds, _or_nameof=self) 

754 if n: 

755 r = r.dup(name=n) 

756 else: 

757 kwds = _xkwds(name_Nvector_kwds, h=r.h, datum=d) 

758 r = Nvector(r.x, r.y, r.z, **self._name1__(kwds)) 

759 return r 

760 

761 def toRtp(self): 

762 '''Convert this cartesian to I{spherical, polar} coordinates. 

763 

764 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} 

765 and C{phi}, both in L{Degrees}. 

766 

767 @see: Function L{xyz2rtp_} and class L{RadiusThetaPhi3Tuple}. 

768 ''' 

769 return _rtp3(self.toRtp, Degrees, self, name=self.name) 

770 

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

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

773 

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

775 @kwarg fmt: Enclosing backets format (C{letter}). 

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

777 

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

779 ''' 

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

781 

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

783 '''Apply a Helmert transform to this cartesian. 

784 

785 @arg transform: Transform to apply (L{Transform} or L{TransformXform}). 

786 @kwarg inverse: Apply the inverse of the C{B{transform}} (C{bool}). 

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

788 this cartesian's datum but I{not} taken it into account. 

789 

790 @return: A transformed cartesian (C{Cartesian}) or a copy of this 

791 cartesian if C{B{transform}.isunity}. 

792 

793 @raise TypeError: Invalid B{C{transform}}. 

794 ''' 

795 _xinstanceof(Transform, transform=transform) 

796 if transform.isunity: 

797 c = self.dup(datum=datum or self.datum) 

798 else: 

799 # if inverse and d != _WGS84: 

800 # raise _ValueError(inverse=inverse, datum=d, 

801 # txt_not_=_WGS84.name) 

802 xyz = transform.transform(*self.xyz3, inverse=inverse) 

803 c = self.dup(xyz=xyz, datum=datum or self.datum) 

804 return c 

805 

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

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

808 

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

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

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

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

813 

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

815 C{B{Vector} is None}. 

816 

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

818 ''' 

819 return self.xyz if Vector is None else Vector( 

820 self.x, self.y, self.z, **self._name1__(Vector_kwds)) 

821 

822 

823class RadiusThetaPhi3Tuple(_NamedTupleTo): 

824 '''3-Tuple C{(r, theta, phi)} with radial distance C{r} in C{meter}, inclination 

825 C{theta} (with respect to the positive z-axis) and azimuthal angle C{phi} in 

826 L{Degrees} I{or} L{Radians} representing a U{spherical, polar position 

827 <https://WikiPedia.org/wiki/Spherical_coordinate_system>}. 

828 ''' 

829 _Names_ = (_r_, _theta_, _phi_) 

830 _Units_ = ( Meter, _Pass, _Pass) 

831 

832 def toCartesian(self, **name_Cartesian_and_kwds): 

833 '''Convert this L{RadiusThetaPhi3Tuple} to a cartesian C{(x, y, z)} vector. 

834 

835 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN}, overriding this 

836 name and optional class C{B{Cartesian}=None} and additional 

837 C{B{Cartesian}} keyword arguments. 

838 

839 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword 

840 argument is given, a L{Vector3Tuple}C{(x, y, z)} with C{x}, C{y} 

841 and C{z} in the same units as radius C{r}, C{meter} conventionally. 

842 

843 @see: Function L{rtp2xyz_}. 

844 ''' 

845 r, t, p = self 

846 t, p, _ = _NamedTupleTo._Radians3(self, t, p) 

847 return rtp2xyz_(r, t, p, **name_Cartesian_and_kwds) 

848 

849 def toDegrees(self, **name): 

850 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Degrees}. 

851 

852 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name. 

853 

854 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} 

855 and C{phi} both in L{Degrees}. 

856 ''' 

857 return self._toX3U(_NamedTupleTo._Degrees3, Degrees, name) 

858 

859 def toRadians(self, **name): 

860 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Radians}. 

861 

862 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name. 

863 

864 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} 

865 and C{phi} both in L{Radians}. 

866 ''' 

867 return self._toX3U(_NamedTupleTo._Radians3, Radians, name) 

868 

869 def _toU(self, U): 

870 M = RadiusThetaPhi3Tuple._Units_[0] # Meter 

871 return self.reUnit(M, U, U).toUnits() 

872 

873 def _toX3U(self, _X3, U, name): 

874 r, t, p = self 

875 t, p, s = _X3(self, t, p) 

876 if s is None or name: 

877 n = self._name__(name) 

878 s = self.classof(r, t, p, name=n)._toU(U) 

879 return s 

880 

881 

882def rtp2xyz(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds): 

883 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates. 

884 

885 @arg theta: Inclination B{C{theta}} (C{degrees} with respect to the positive z-axis), 

886 required if C{B{r_rtp}} is C{scalar}, ignored otherwise. 

887 @arg phi: Azimuthal angle B{C{phi}} (C{degrees}), like B{C{theta}}. 

888 

889 @see: Function L{rtp2xyz_} for further details. 

890 ''' 

891 if isinstance(r_rtp, RadiusThetaPhi3Tuple): 

892 c = r_rtp.toCartesian(**name_Cartesian_and_kwds) 

893 else: 

894 c = rtp2xyz_(r_rtp, radians(theta), radians(phi), **name_Cartesian_and_kwds) 

895 return c 

896 

897 

898def rtp2xyz_(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds): 

899 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates. 

900 

901 @arg r_rtp: Radial distance (C{scalar}, conventially C{meter}) or a previous 

902 L{RadiusThetaPhi3Tuple} instance. 

903 @arg theta: Inclination B{C{theta}} (C{radians} with respect to the positive z-axis), 

904 required if C{B{r_rtp}} is C{scalar}, ignored otherwise. 

905 @arg phi: Azimuthal angle B{C{phi}} (C{radians}), like B{C{theta}}. 

906 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN} (C{str}), C{B{Cartesian}=None} 

907 class to return the coordinates and optionally, additional C{B{Cartesian}} 

908 keyword arguments. 

909 

910 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword argument 

911 is given a L{Vector3Tuple}C{(x, y, z)}, with C{x}, C{y} and C{z} in the same 

912 units as radius C{r}, C{meter} conventionally. 

913 

914 @raise TypeError: Invalid B{C{r_rtp}}, B{C{theta}}, B{C{phi}} or 

915 B{C{name_Cartesian_and_kwds}} item. 

916 

917 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>} 

918 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and functions L{rtp2xyz} 

919 and L{xyz2rtp}. 

920 ''' 

921 if isinstance(r_rtp, RadiusThetaPhi3Tuple): 

922 c = r_rtp.toCartesian(**name_Cartesian_and_kwds) 

923 elif _isMeter(r_rtp): 

924 r = r_rtp 

925 if r and _isfinite(r): 

926 s, z, y, x = sincos2_(theta, phi) 

927 s *= r 

928 z *= r 

929 y *= s 

930 x *= s 

931 else: 

932 x = y = z = r 

933 

934 n, kwds = _name2__(**name_Cartesian_and_kwds) 

935 C, kwds = _xkwds_pop2(kwds, Cartesian=None) 

936 c = Vector3Tuple(x, y, z, name=n) if C is None else \ 

937 C(x, y, z, name=n, **kwds) 

938 else: 

939 raise _TypeError(r_rtp=r_rtp, theta=theta, phi=phi) 

940 return c 

941 

942 

943def _rtp3(where, U, *x_y_z, **name): 

944 '''(INTERNAL) Helper for C{.toRtp}, C{xyz2rtp} and C{xyz2rtp_}. 

945 ''' 

946 x, y, z = _MODS.vector3dBase._xyz3(where, *x_y_z) 

947 r = hypot_(x, y, z) 

948 if r > 0: 

949 t = acos1(z / r) 

950 p = atan2(y, x) 

951 while p < 0: 

952 p += PI2 

953 if U is Degrees: 

954 t = degrees(t) 

955 p = degrees(p) 

956 else: 

957 t = p = _0_0 

958 return RadiusThetaPhi3Tuple(r, t, p, **name)._toU(U) 

959 

960 

961def xyz2rtp(x_xyz, y=0, z=0, **name): 

962 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates. 

963 

964 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} and C{phi}, both 

965 in L{Degrees}. 

966 

967 @see: Function L{xyz2rtp_} for further details. 

968 ''' 

969 return _rtp3(xyz2rtp, Degrees, x_xyz, y, z, **name) 

970 

971 

972def xyz2rtp_(x_xyz, y=0, z=0, **name): 

973 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates. 

974 

975 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple}, 

976 C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} or a C{tuple} or 

977 C{list} of 3+ C{scalar} items) if no C{y_z} specified. 

978 @arg y: Y component (C{scalar}), required if C{B{x_xyz}} is C{scalar}, ignored otherwise. 

979 @arg z: Z component (C{scalar}), like B{C{y}}. 

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

981 

982 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with radial distance C{r} (C{meter}, 

983 same units as C{x}, C{y} and C{z}), inclination C{theta} (with respect to the 

984 positive z-axis) and azimuthal angle C{phi}, both in L{Radians}. 

985 

986 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>} 

987 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and function L{xyz2rtp}. 

988 ''' 

989 return _rtp3(xyz2rtp_, Radians, x_xyz, y, z, **name) 

990 

991 

992__all__ += _ALL_DOCS(CartesianBase) 

993 

994# **) MIT License 

995# 

996# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

997# 

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

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

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

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

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

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

1004# 

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

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

1007# 

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

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

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

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

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

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

1014# OTHER DEALINGS IN THE SOFTWARE.