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, _xattr, \

20 _xdatum, _xkwds, _xkwds_get, _xkwds_pop2

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_, _datum_, _no_, _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 _name2__, _Pass

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

29 Vector4Tuple, Bearing2Tuple # PYCHOK .sphericalBase

30# from pygeodesy.nvectorBase import _N_vector # _MODS

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

32 property_RO, property_ROnce, _update_all

33# from pygeodesy,resections import cassini, collins5, pierlot, pierlotx, \

34# tienstra7 # _MODS

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

36# from pygeodesy.triaxials import Triaxial_ # _MODS

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

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

39 from pygeodesy.vector3d import Vector3d, _xyzhdlln4

40# from pygeodesy.vector3dBase import _xyz3 # _MODS

41# from pygeodesy import ltp # _MODS

42

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

44

45__all__ = _ALL_LAZY.cartesianBase

46__version__ = '24.08.18'

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_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}), like B{C{y}}.

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

67 or L{a_f2Tuple}).

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

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

70

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

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

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

74 not a L{Datum}.

75 '''

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

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

78 if h is not None:

79 self._height = Height(h)

80 if d is not None:

81 self.datum = d

82

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

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

85# '''

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

87# _NotImplemented(self, other)

88

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

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

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

92

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

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

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

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

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

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

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

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

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

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

103

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

105

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

107

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

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

110

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

112

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

114 '''

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

116 useZ=useZ, datum=self.datum)

117

118 @deprecated_method

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

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

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

122

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

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

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

126

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

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

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

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

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

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

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

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

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

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

137

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

139

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

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

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

143

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

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

146

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

148

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

150 '''

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

152 useZ=useZ, datum=self.datum)

153

154 @deprecated_method

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

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

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

158

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

160 def datum(self):

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

162 '''

163 return self._datum

164

165 @datum.setter # PYCHOK setter!

166 def datum(self, datum):

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

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

169

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

171 '''

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

173 if self._datum: # is not None

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

175 raise _IsnotError(_ellipsoidal_, datum=datum)

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

177 raise _IsnotError(_spherical_, datum=datum)

178 if self._datum != d:

179 _update_all(self)

180 self._datum = d

181

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

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

184

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

186 or L{Local9Tuple}).

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

188 or C{None}.

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

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

191 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 B{C{Cartesian_kwds}}

198 item or C{datum} missing or incompatible.

199 '''

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

201 if Cartesian is None:

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

203 else:

204 d = self.datum

205 if not d:

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

207 t = _xkwds_get(kwds, datum=d)

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

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

210 c = self._Ltp._local2ecef(delta, nine=False)

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

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

213

214 @property_ROnce

215 def Ecef(self):

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

217 '''

218 return _MODS.ecef.EcefKarney

219

220 @Property_RO

221 def _ecef9(self):

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

223 '''

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

225

226 @property_RO

227 def ellipsoidalCartesian(self):

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

229 '''

230 return False

231

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

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

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

235

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

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

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

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

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

241 C{datum} ellipsoid.

242

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

244 this C{pov}.

245

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

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

248 the ellipsoid.

249

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

251

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

253 '''

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

255

256 @Property

257 def height(self):

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

259 '''

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

261

262 @height.setter # PYCHOK setter!

263 def height(self, height):

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

265

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

267

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

269 '''

270 h = Height(height)

271 if self._height != h:

272 _update_all(self)

273 self._height = h

274

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

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

277 '''

278 if Cartesian is not None:

279 r = Cartesian(r, **kwds)

280 if datum is not None:

281 r.datum = datum

282 if height is not None:

283 r.height = height # Height(height)

284 return r

285

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

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

288

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

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

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

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

293 cartesian's height.

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

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

296 keyword arguments.

297

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

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

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

301

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

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

304

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

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

307

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

309

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

311 '''

312 n = self.height3.__name__

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

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

315 if h and d:

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

317 if R > EPS0:

318 R = (R + h) / R

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

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

321

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

323 if Cartesian_and_kwds:

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

325 return r

326

327 @Property_RO

328 def _height4(self):

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

330 '''

331 try:

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

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

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

335 return r

336

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

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

339 this datum's ellipsoid surface.

340

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

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

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

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

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

346 ellipsoid's surface, otherwise the intersection of the

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

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

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

350 arguments.

351

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

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

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

355

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

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

358

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

360

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

362

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

364 '''

365 n = self.height4.__name__

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

367 if normal and d is self.datum:

368 r = self._height4

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

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

371 try:

372 d = d.toEllipsoid(name=n)

373 except (TypeError, ValueError): # TriaxialError

374 d = None

375 else:

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

377

378 if Cartesian_and_kwds:

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

380 d = _spherical_datum(d, name=n)

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

382 return r

383

384 @Property_RO

385 def isEllipsoidal(self):

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

387 '''

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

389

390 @Property_RO

391 def isSpherical(self):

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

393 '''

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

395

396 @Property_RO

397 def latlon(self):

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

399 '''

400 return self.toEcef().latlon

401

402 @Property_RO

403 def latlonheight(self):

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

405 '''

406 return self.toEcef().latlonheight

407

408 @Property_RO

409 def latlonheightdatum(self):

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

411 '''

412 return self.toEcef().latlonheightdatum

413

414 @Property_RO

415 def _Ltp(self):

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

417 '''

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

419

420 @Property_RO

421 def _N_vector(self):

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

423 '''

424 _N = _MODS.nvectorBase._N_vector_

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

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

427

428 def _n_xyzh4(self, datum):

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

430 '''

431 def _ErrorEPS0(x):

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

433

434 _xinstanceof(Datum, datum=datum)

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

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

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

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

439 E = datum.ellipsoid

440 x, y, z = self.xyz3

441

442 # Kenneth Gade eqn 23

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

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

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

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

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

448 if isnear0(t):

449 raise _ErrorEPS0(t)

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

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

452 t = v * _2_0

453 if t < EPS0: # isnear0

454 raise _ErrorEPS0(t)

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

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

457 if isnear0(k):

458 raise _ErrorEPS0(k)

459 t = k + E.e2

460 if isnear0(t):

461 raise _ErrorEPS0(t)

462 e = k / t

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

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

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

466 if t < EPS0: # isnear0

467 raise _ErrorEPS0(t)

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

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

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

471

472 @Property_RO

473 def philam(self):

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

475 '''

476 return self.toEcef().philam

477

478 @Property_RO

479 def philamheight(self):

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

481 '''

482 return self.toEcef().philamheight

483

484 @Property_RO

485 def philamheightdatum(self):

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

487 '''

488 return self.toEcef().philamheightdatum

489

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

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

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

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

494

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

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

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

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

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

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

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

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

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

504 (C{bool}).

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

506

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

508

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

510

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

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

513

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

515

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

517 '''

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

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

520

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

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

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

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

525

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

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

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

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

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

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

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

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

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

535

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

537

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

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

540

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

542

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

544 '''

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

546 useZ=useZ, datum=self.datum)

547

548 def Roc2(self, earth=None):

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

550

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

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

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

554

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

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

557

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

559 '''

560 r = z = fabs( self.z)

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

562 if R < _0: # polar

563 R = z

564 elif z > _0: # non-equatorial

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

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

567 M = e.M # EcefMatrix

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

569 if ca < _0: # polar

570 R = z

571 else: # prime-vertical, normal roc R

572 R = R / ca # /= chokes PyChecker

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

574 return R, r

575

576 @property_RO

577 def sphericalCartesian(self):

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

579 '''

580 return False

581

582 @deprecated_method

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

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

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

586

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

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

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

590

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

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

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

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

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

596 non-negative).

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

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

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

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

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

602 (C{bool}).

603

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

605

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

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

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

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

610

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

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

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

614

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

616

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

618 '''

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

620 useZ=useZ, datum=self.datum)

621

622 @deprecated_method

623 def to2ab(self): # PYCHOK no cover

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

625

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

627 '''

628 return self.philam

629

630 @deprecated_method

631 def to2ll(self): # PYCHOK no cover

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

633

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

635 '''

636 return self.latlon

637

638 @deprecated_method

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

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

641

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

643

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

645 as its name may suggest.

646 '''

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

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

649

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

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

652# '''

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

654# if LL is not None:

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

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

657# setattr(r, n, v)

658# return r

659

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

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

662

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

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

665

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

667

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

669 invalid.

670 '''

671 _xinstanceof(Datum, datum2=datum2)

672

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

674 self.toDatum(datum)

675

676 i, d = False, c.datum

677 if d == datum2:

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

679

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

681 datum2.transform.isunity):

682 return c.dup(datum=datum2)

683

684 elif d == _WGS84:

685 d = datum2 # convert from WGS84 to datum2

686

687 elif datum2 == _WGS84:

688 i = True # convert to WGS84 by inverse transformation

689

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

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

692 d = datum2

693

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

695

696 def toEcef(self):

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

698

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

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

701

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

703 '''

704 return self._ecef9

705

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

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

708

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

710 or L{a_f2Tuple}).

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

712 (C{meter}), only if C{B{LatLon} is not None}.

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

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

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

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

717

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

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

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

721

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

723 '''

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

725 if d == self.datum:

726 r = self.toEcef()

727 else:

728 c = self.toDatum(d)

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

730

731 if LatLon: # class or .classof

732 h = _heigHt(r, height)

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

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

735 _xdatum(r.datum, d)

736 return r

737

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

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

740

741 @kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z} (L{XyzLocal},

742 L{Enu}, L{Ned}) or C{None}.

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

744 cartesian's LTP (L{Ltp}).

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

746 ignored if C{B{Xyz} is None}.

747

748 @return: An B{C{Xyz}} instance or a L{Local9Tuple}C{(x, y, z, lat, lon,

749 height, ltp, ecef, M)} if C{B{Xyz} is None} (with C{M=None}).

750

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

752 '''

753 return _MODS.ltp._toLocal(self, ltp, Xyz, Xyz_kwds) # self._ecef9

754

755 def toLtp(self, Ecef=None, **name):

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

757

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

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

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

761 '''

762 return _MODS.ltp._toLtp(self, Ecef, self._ecef9, name) # self._Ltp

763

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

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

766

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

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

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

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

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

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

773 C{B{Nvector} is None}.

774

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

776 C{B{Nvector} is None}.

777

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

779 B{C{name_Nvector_kwds}} item.

780

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

782 '''

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

784 if datum is not None:

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

786 if d != self.datum:

787 r = self._n_xyzh4(d)

788

789 if Nvector is None:

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

791 if n:

792 r = r.dup(name=n)

793 else:

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

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

796 return r

797

798 def toRtp(self):

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

800

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

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

803

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

805 '''

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

807

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

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

810

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

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

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

814

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

816 '''

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

818

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

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

821

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

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

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

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

826

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

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

829

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

831 '''

832 _xinstanceof(Transform, transform=transform)

833 if transform.isunity:

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

835 else:

836 # if inverse and d != _WGS84:

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

838 # txt_not_=_WGS84.name)

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

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

841 return c

842

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

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

845

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

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

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

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

850

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

852 C{B{Vector} is None}.

853

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

855 '''

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

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

858

859

860 class RadiusThetaPhi3Tuple(_NamedTupleTo):

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

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

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

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

865 '''

866 _Names_ = (_r_, _theta_, _phi_)

867 _Units_ = ( Meter, _Pass, _Pass)

868

869 def toCartesian(self, **name_Cartesian_and_kwds):

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

871

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

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

874 C{B{Cartesian}} keyword arguments.

875

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

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

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

879

880 @see: Function L{rtp2xyz_}.

881 '''

882 r, t, p = self

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

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

885

886 def toDegrees(self, **name):

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

888

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

890

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

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

893 '''

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

895

896 def toRadians(self, **name):

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

898

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

900

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

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

903 '''

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

905

906 def _toU(self, U):

907 M = RadiusThetaPhi3Tuple._Units_[0] # Meter

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

909

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

911 r, t, p = self

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

913 if s is None or name:

914 n = self._name__(name)

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

916 return s

917

918

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

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

921

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

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

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

925

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

927 '''

928 if isinstance(r_rtp, RadiusThetaPhi3Tuple):

929 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

930 else:

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

932 return c

933

934

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

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

937

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

939 L{RadiusThetaPhi3Tuple} instance.

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

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

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

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

944 class to return the coordinates and optional, additional C{B{Cartesian}}

945 keyword arguments.

946

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

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

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

950

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

952 B{C{name_Cartesian_and_kwds}} item.

953

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

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

956 and L{xyz2rtp}.

957 '''

958 if isinstance(r_rtp, RadiusThetaPhi3Tuple):

959 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

960 elif _isMeter(r_rtp):

961 r = r_rtp

962 if r and _isfinite(r):

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

964 s *= r

965 z *= r

966 y *= s

967 x *= s

968 else:

969 x = y = z = r

970

971 n, kwds = _name2__(**name_Cartesian_and_kwds)

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

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

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

975 else:

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

977 return c

978

979

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

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

982 '''

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

984 r = hypot_(x, y, z)

985 if r > 0:

986 t = acos1(z / r)

987 p = atan2(y, x)

988 while p < 0:

989 p += PI2

990 if U is Degrees:

991 t = degrees(t)

992 p = degrees(p)

993 else:

994 t = p = _0_0

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

996

997

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

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

1000

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

1002 in L{Degrees}.

1003

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

1005 '''

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

1007

1008

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

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

1011

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

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

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

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

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

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

1018

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

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

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

1022

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

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

1025 '''

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

1027

1028

1029__all__ += _ALL_DOCS(CartesianBase)

1030

1031# **) MIT License

1032#

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

1034#

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

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

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

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

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

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

1041#

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

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

1044#

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

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

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

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

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

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

1051# OTHER DEALINGS IN THE SOFTWARE.