1

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

3

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

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

6

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

8 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, INT0, PI2, _isfinite, isnear0, \

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

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

17 Transform, _WGS84, _xinstanceof

18# from pygeodesy.ecef import EcefKarney # _MODS

19 from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, \

20 _xdatum, _xkwds

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

22# from pygeodesy.formy import _hartzell # _MODS

23 from pygeodesy.fsums import fsumf_, Fmt

24 from pygeodesy.interns import _COMMASPACE_, _phi_

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

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

27 from pygeodesy.named import _name__, _name2__, _NamedTuple, _Pass

28 from pygeodesy.namedTuples import LatLon4Tuple, Vector3Tuple, Vector4Tuple, \

29 Bearing2Tuple # PYCHOK .sphericalBase

30# from pygeodesy.nvectorBase import _N_vector # _MODS

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

32 property_doc_, property_RO, _update_all

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

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

35# from pygeodesy.triaxials import Triaxial_ # _MODS

36 from pygeodesy.units import Degrees, Height, _heigHt, _isMeter, Meter, \

37 Radians, _toDegrees, _toRadians

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

39 from pygeodesy.vector3d import Vector3d, _xyzhdn3

40# from pygeodesy.vector3dBase import _xyz3 # _MODS

41# from pygeodesy import ltp, resections # _MODS

42

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

44

45__all__ = _ALL_LAZY.cartesianBase

46__version__ = '24.05.24'

47

48_r_ = 'r'

49_theta_ = 'theta'

50

51

52 class CartesianBase(Vector3d):

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

54 '''

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

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

57

58 def __init__(self, x_xyz, y=None, z=None, datum=None, ll=None, **name):

59 '''New C{Cartesian...}.

60

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

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

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

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

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

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

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

68 or L{a_f2Tuple}).

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

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

71

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

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

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

75 not a L{Datum}.

76 '''

77 h, d, n = _xyzhdn3(x_xyz, None, datum, ll, **name)

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

79 if h is not None:

80 self._height = Height(h)

81 if d is not None:

82 self.datum = d

83

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

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

86# '''

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

88# _NotImplemented(self, other)

89

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

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

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

93

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

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

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

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

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

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

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

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

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

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

104

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

106

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

108

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

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

111

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

113

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

115 '''

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

117 useZ=useZ, datum=self.datum)

118

119 @deprecated_method

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

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

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

123

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

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

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

127

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

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

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

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

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

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

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

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

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

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

138

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

140

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

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

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

144

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

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

147

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

149

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

151 '''

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

153 useZ=useZ, datum=self.datum)

154

155 @deprecated_method

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

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

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

159

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

161 def datum(self):

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

163 '''

164 return self._datum

165

166 @datum.setter # PYCHOK setter!

167 def datum(self, datum):

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

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

170

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

172 '''

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

174 if self._datum: # is not None

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

176 raise _IsnotError(_ellipsoidal_, datum=datum)

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

178 raise _IsnotError(_spherical_, datum=datum)

179 if self._datum != d:

180 _update_all(self)

181 self._datum = d

182

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

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

185

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

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

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

189 destination or C{None}.

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

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

192

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

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

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

196

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

198 B{C{Cartesian_kwds}}.

199 '''

200 if Cartesian is None:

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

202 else:

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

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

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

206

207 @property_RO

208 def Ecef(self):

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

210 '''

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

212 return E

213

214 @Property_RO

215 def _ecef9(self):

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

217 '''

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

219

220 @property_RO

221 def ellipsoidalCartesian(self):

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

223 '''

224 return False

225

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

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

228 (pov) and this cartesian's ellipsoid surface.

229

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

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

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

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

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

235 C{datum} ellipsoid.

236

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

238 this C{pov}.

239

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

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

242 the ellipsoid.

243

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

245

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

247 '''

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

249

250 @Property

251 def height(self):

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

253 '''

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

255

256 @height.setter # PYCHOK setter!

257 def height(self, height):

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

259

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

261

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

263 '''

264 h = Height(height)

265 if self._height != h:

266 _update_all(self)

267 self._height = h

268

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

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

271 '''

272 if Cartesian is not None:

273 r = Cartesian(r, **kwds)

274 if datum is not None:

275 r.datum = datum

276 if height is not None:

277 r.height = height # Height(height)

278 return r

279

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

281 '''Compute the cartesian at a height above or below this certesian's ellipsoid.

282

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

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

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

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

287 cartesian's height.

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

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

290 keyword arguments.

291

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

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

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

295

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

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

298

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

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

301

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

303

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

305 '''

306 n = self.height3.__name__

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

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

309 if h and d:

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

311 if R > EPS0:

312 R = (R + h) / R

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

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

315

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

317 if Cartesian_and_kwds:

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

319 return r

320

321 @Property_RO

322 def _height4(self):

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

324 '''

325 try:

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

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

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

329 return r

330

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

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

333 this datum's ellipsoid surface.

334

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

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

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

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

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

340 ellipsoid's surface, otherwise the intersection of the

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

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

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

344 arguments.

345

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

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

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

349

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

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

352

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

354

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

356

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

358 '''

359 n = self.height4.__name__

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

361 if normal and d is self.datum:

362 r = self._height4

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

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

365 try:

366 d = d.toEllipsoid(name=n)

367 except (TypeError, ValueError): # TriaxialError

368 d = None

369 else:

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

371

372 if Cartesian_and_kwds:

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

374 d = _spherical_datum(d, name=n)

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

376 return r

377

378 @Property_RO

379 def isEllipsoidal(self):

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

381 '''

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

383

384 @Property_RO

385 def isSpherical(self):

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

387 '''

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

389

390 @Property_RO

391 def latlon(self):

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

393 '''

394 return self.toEcef().latlon

395

396 @Property_RO

397 def latlonheight(self):

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

399 '''

400 return self.toEcef().latlonheight

401

402 @Property_RO

403 def latlonheightdatum(self):

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

405 '''

406 return self.toEcef().latlonheightdatum

407

408 @property_RO

409 def _ltp(self):

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

411 '''

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

413 return m

414

415 @Property_RO

416 def _Ltp(self):

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

418 '''

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

420

421 @Property_RO

422 def _N_vector(self):

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

424 '''

425 m = _MODS.nvectorBase

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

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

428

429 def _n_xyzh4(self, datum):

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

431 '''

432 def _ErrorEPS0(x):

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

434

435 _xinstanceof(Datum, datum=datum)

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

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

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

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

440 E = datum.ellipsoid

441 x, y, z = self.xyz

442

443 # Kenneth Gade eqn 23

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

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

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

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

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

449 if isnear0(t):

450 raise _ErrorEPS0(t)

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

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

453 t = v * _2_0

454 if t < EPS0: # isnear0

455 raise _ErrorEPS0(t)

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

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

458 if isnear0(k):

459 raise _ErrorEPS0(k)

460 t = k + E.e2

461 if isnear0(t):

462 raise _ErrorEPS0(t)

463 e = k / t

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

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

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

467 if t < EPS0: # isnear0

468 raise _ErrorEPS0(t)

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

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

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

472

473 @Property_RO

474 def philam(self):

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

476 '''

477 return self.toEcef().philam

478

479 @Property_RO

480 def philamheight(self):

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

482 '''

483 return self.toEcef().philamheight

484

485 @Property_RO

486 def philamheightdatum(self):

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

488 '''

489 return self.toEcef().philamheightdatum

490

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

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

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

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

495

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

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

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

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

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

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

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

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

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

505 (C{bool}).

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

507

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

509

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

511

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

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

514

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

516

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

518 '''

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

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

521

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

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

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

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

526

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

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

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

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

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

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

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

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

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

536

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

538

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

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

541

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

543

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

545 '''

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

547 useZ=useZ, datum=self.datum)

548

549 @property_RO

550 def _resections(self):

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

552 '''

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

554 return m

555

556 def Roc2(self, earth=None):

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

558

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

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

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

562

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

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

565

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

567 '''

568 r = z = fabs( self.z)

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

570 if R < _0: # polar

571 R = z

572 elif z > _0: # non-equatorial

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

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

575 M = e.M # EcefMatrix

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

577 if ca < _0: # polar

578 R = z

579 else: # prime-vertical, normal roc R

580 R = R / ca # /= chokes PyChecker

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

582 return R, r

583

584 @property_RO

585 def sphericalCartesian(self):

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

587 '''

588 return False

589

590 @deprecated_method

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

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

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

594

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

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

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

598

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

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

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

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

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

604 non-negative).

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

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

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

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

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

610 (C{bool}).

611

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

613

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

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

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

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

618

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

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

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

622

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

624

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

626 '''

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

628 useZ=useZ, datum=self.datum)

629

630 @deprecated_method

631 def to2ab(self): # PYCHOK no cover

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

633

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

635 '''

636 return self.philam

637

638 @deprecated_method

639 def to2ll(self): # PYCHOK no cover

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

641

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

643 '''

644 return self.latlon

645

646 @deprecated_method

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

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

649

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

651

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

653 as its name may suggest.

654 '''

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

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

657

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

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

660# '''

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

662# if LL is not None:

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

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

665# setattr(r, n, v)

666# return r

667

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

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

670

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

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

673

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

675

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

677 invalid.

678 '''

679 _xinstanceof(Datum, datum2=datum2)

680

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

682 self.toDatum(datum)

683

684 i, d = False, c.datum

685 if d == datum2:

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

687

688 elif datum2.transform.isunity and \

689 d.transform.isunity:

690 return c.dup(datum=datum2)

691

692 elif d == _WGS84:

693 d = datum2 # convert from WGS84 to datum2

694

695 elif datum2 == _WGS84:

696 i = True # convert to WGS84 by inverse transformation

697

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

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

700 d = datum2

701

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

703

704 def toEcef(self):

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

706

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

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

709

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

711 '''

712 return self._ecef9

713

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

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

716

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

718 or L{a_f2Tuple}).

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

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

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

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

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

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

725

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

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

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

729

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

731 '''

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

733 if d == self.datum:

734 r = self.toEcef()

735 else:

736 c = self.toDatum(d)

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

738

739 if LatLon: # class or .classof

740 h = _heigHt(r, height)

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

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

743 _xdatum(r.datum, d)

744 return r

745

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

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

748

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

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

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

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

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

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

755

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

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

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

759

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

761 '''

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

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

764

765 def toLtp(self, Ecef=None):

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

767

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

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

770 '''

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

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

773

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

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

776

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

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

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

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

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

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

783

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

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

786

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

788 B{C{datum}}.

789

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

791 '''

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

793 if datum is not None:

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

795 if d != self.datum:

796 r = self._n_xyzh4(d)

797

798 if Nvector is not None:

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

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

801 return r

802

803 def toRtp(self):

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

805

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

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

808

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

810 '''

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

812

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

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

815

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

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

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

819

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

821 '''

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

823

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

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

826

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

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

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

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

831

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

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

834

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

836 '''

837 _xinstanceof(Transform, transform=transform)

838 if transform.isunity:

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

840 else:

841 # if inverse and d != _WGS84:

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

843 # txt_not_=_WGS84.name)

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

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

846 return c

847

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

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

850

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

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

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

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

855

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

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

858

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

860 '''

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

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

863

864

865 class RadiusThetaPhi3Tuple(_NamedTuple):

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

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

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

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

870 '''

871 _Names_ = (_r_, _theta_, _phi_)

872 _Units_ = ( Meter, _Pass, _Pass)

873

874 def toCartesian(self, **name_Cartesian_and_kwds):

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

876

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

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

879 C{B{Cartesian}} keyword arguments.

880

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

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

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

884

885 @see: Function L{rtp2xyz_}.

886 '''

887 r, t, p = self

888 t, p, _ = _toRadians(self, t, p)

889 n, kwds = _name2__(name_Cartesian_and_kwds, _or_nameof=self)

890 return rtp2xyz_(r, t, p, name=n, **kwds)

891

892 def toDegrees(self, **name):

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

894

895 @kwarg name: Optional name (C{str}), overriding this name.

896

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

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

899 '''

900 r, t, p = self

901 t, p, _ = _toDegrees(self, t, p)

902 return _ or self.classof(r, Degrees(theta=t), Degrees(phi=p),

903 name=_name__(name, _or_nameof=self))

904

905 def toRadians(self, **name):

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

907

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

909

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

911 C{theta} and C{phi} both in L{Radians}.

912 '''

913 r, t, p = self

914 t, p, _ = _toRadians(self, t, p)

915 return _ or self.classof(r, Radians(theta=t), Radians(phi=p),

916 name=_name__(name, _or_nameof=self))

917

918

919 def rtp2xyz(r_rtp, *theta_phi, **name_Cartesian_and_kwds):

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

921

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

923 L{RadiusThetaPhi3Tuple} instance.

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

925 and azimuthal angle B{C{phi}} (C{degrees}), ignored if C{B{r_rtp}} is a

926 L{RadiusThetaPhi3Tuple}.

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

928 class to return the coordinates and additional C{B{Cartesian}}

929 keyword arguments.

930

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

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

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

934

935 @raise TypeError: Invalid B{C{r_rtp}}.

936

937 @see: Functions L{rtp2xyz_} and L{xyz2rtp}.

938 '''

939 if isinstance(r_rtp, RadiusThetaPhi3Tuple):

940 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

941 else:

942 c = rtp2xyz_(r_rtp, *map(radians, theta_phi), **name_Cartesian_and_kwds)

943 return c

944

945

946 def rtp2xyz_(r_rtp, *theta_phi, **name_Cartesian_and_kwds):

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

948

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

950 L{RadiusThetaPhi3Tuple} instance.

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

952 and azimuthal angle B{C{phi}} (C{degrees}), ignored is C{B{r_rtp}} is a

953 L{RadiusThetaPhi3Tuple}.

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

955 class to return the coordinates and additional C{B{Cartesian}}

956 keyword arguments.

957

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

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

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

961

962 @raise TypeError: Invalid B{C{r_rtp}} or B{C{theta_phi}}.

963

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

965 (ISO 80000-2:2019).

966 '''

967 if _isMeter(r_rtp) and len(theta_phi) == 2:

968 r = r_rtp

969 if r and _isfinite(r):

970 s, z, y, x = sincos2_(*theta_phi)

971 s *= r

972 z *= r

973 y *= s

974 x *= s

975 else:

976 x = y = z = r

977

978 def _n_C_kwds3(Cartesian=None, **name_kwds):

979 n, kwds = _name2__(**name_kwds)

980 return n, Cartesian, kwds

981

982 n, C, kwds = _n_C_kwds3(**name_Cartesian_and_kwds)

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

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

985

986 elif isinstance(r_rtp, RadiusThetaPhi3Tuple):

987 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

988 else:

989 raise _TypeError(r_rtp=r_rtp, theta_phi=theta_phi)

990 return c

991

992

993 def _rtp3(where, Unit, *x_y_z, **name):

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

995 '''

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

997 r = hypot_(x, y, z)

998 if r > 0:

999 t = acos1(z / r)

1000 p = atan2(y, x)

1001 while p < 0:

1002 p += PI2

1003 if Unit is Degrees:

1004 t = degrees(t)

1005 p = degrees(p)

1006 else:

1007 t = p = _0_0

1008 return RadiusThetaPhi3Tuple(r, Unit(theta=t), Unit(phi=p), **name)

1009

1010

1011 def xyz2rtp(x_xyz, *y_z, **name):

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

1013

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

1015 both in L{Degrees}.

1016

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

1018 '''

1019 return _rtp3(xyz2rtp, Degrees, x_xyz, *y_z, **name)

1020

1021

1022 def xyz2rtp_(x_xyz, *y_z, **name):

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

1024

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

1026 C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} or a

1027 C{tuple} or C{list} of 3+ C{scalar} items) if no C{y_z} specified.

1028 @arg y_z: Y and Z component (C{scalar}s), ignored if C{x_xyz} is not a C{scalar}.

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

1030

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

1032 (C{meter}, same units as C{x}, C{y} and C{z}), inclination C{theta}

1033 (with respect to the positive z-axis) and azimuthal angle C{phi},

1034 both in L{Radians}.

1035

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

1037 (ISO 80000-2:2019).

1038 '''

1039 return _rtp3(xyz2rtp_, Radians, x_xyz, *y_z, **name)

1040

1041

1042__all__ += _ALL_DOCS(CartesianBase)

1043

1044# **) MIT License

1045#

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

1047#

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

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

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

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

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

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

1054#

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

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

1057#

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

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

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

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

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

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

1064# OTHER DEALINGS IN THE SOFTWARE.