1

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

3

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

5 C{Cartesian}s.

6

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

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

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

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

11'''

12

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

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

15 _2_0, _4_0, _6_0

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

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

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

19 from pygeodesy.fsums import Fmt, fsumf_

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

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

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

23 from pygeodesy.namedTuples import LatLon4Tuple, Vector4Tuple, \

24 Bearing2Tuple # PYCHOK .sphericalBase

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

26 property_doc_, _update_all

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

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

29 from pygeodesy.units import Height, _heigHt

30 from pygeodesy.vector3d import Vector3d, _xyzhdn3

31

32# from math import sqrt # from .fmath

33

34__all__ = _ALL_LAZY.cartesianBase

35__version__ = '23.06.02'

36

37

38 class CartesianBase(Vector3d):

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

40 '''

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

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

43

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

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

46

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

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

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

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

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

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

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

54 or L{a_f2Tuple}).

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

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

57

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

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

60 L{Vector3Tuple} or L{Vector4Tuple}.

61 '''

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

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

64 if h is not None:

65 self._height = Height(h)

66 if d is not None:

67 self.datum = d

68

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

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

71# '''

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

73# _NotImplemented(self, other)

74

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

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

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

78

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

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

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

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

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

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

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

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

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

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

89

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

91

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

93

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

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

96

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

98

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

100 '''

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

102 useZ=useZ, datum=self.datum)

103

104 @deprecated_method

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

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

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

108

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

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

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

112

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

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

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

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

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

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

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

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

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

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

123

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

125

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

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

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

129

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

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

132

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

134

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

136 '''

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

138 useZ=useZ, datum=self.datum)

139

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

141 def datum(self):

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

143 '''

144 return self._datum

145

146 @datum.setter # PYCHOK setter!

147 def datum(self, datum):

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

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

150

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

152 '''

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

154 if self._datum: # is not None

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

156 raise _IsnotError(_ellipsoidal_, datum=datum)

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

158 raise _IsnotError(_spherical_, datum=datum)

159 if self._datum != d:

160 _update_all(self)

161 self._datum = d

162

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

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

165

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

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

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

169 destination or C{None}.

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

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

172

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

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

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

176

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

178 B{C{Cartesian_kwds}}.

179 '''

180 if Cartesian is None:

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

182 else:

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

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

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

186

187 @Property_RO

188 def Ecef(self):

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

190 '''

191 return _MODS.ecef.EcefKarney # default

192

193 @Property_RO

194 def _ecef9(self):

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

196 '''

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

198

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

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

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

202

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

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

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

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

207 this cartesian's C{datum} ellipsoid.

208

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

210

211 @raise IntersectionError: Null C{pov} or B{C{los}} vector, this C{pov}

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

213 outside the ellipsoid or in an opposite direction.

214

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

216

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

218 '''

219 return _MODS.formy.hartzell(self, los=los, earth=earth or self.datum)

220

221 @Property

222 def height(self):

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

224 '''

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

226

227 @height.setter # PYCHOK setter!

228 def height(self, height):

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

230

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

232

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

234 '''

235 h = Height(height)

236 if self._height != h:

237 _update_all(self)

238 self._height = h

239

240 @Property_RO

241 def _height4(self):

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

243 '''

244 try:

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

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

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

248 return r

249

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

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

252 on this datum's ellipsoid surface.

253

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

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

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

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

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

259 ellipsoid's surface, otherwise the intersection of the

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

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

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

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

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

265

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

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

268

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

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

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

272

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

274

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

276

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

278 '''

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

280 if normal and d == self.datum:

281 r = self._height4

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

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

284 else:

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

286 if Cartesian is not None:

287 kwds = Cartesian_kwds.copy()

288 h = kwds.pop(_height_, None)

289 r = Cartesian(r, **kwds)

290 if h is not None:

291 r.height = Height(height=h)

292 return r

293

294 @Property_RO

295 def isEllipsoidal(self):

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

297 '''

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

299

300 @Property_RO

301 def isSpherical(self):

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

303 '''

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

305

306 @Property_RO

307 def latlon(self):

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

309 '''

310 return self.toEcef().latlon

311

312 @Property_RO

313 def latlonheight(self):

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

315 '''

316 return self.toEcef().latlonheight

317

318 @Property_RO

319 def latlonheightdatum(self):

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

321 '''

322 return self.toEcef().latlonheightdatum

323

324 @Property_RO

325 def _ltp(self):

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

327 '''

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

329

330 @Property_RO

331 def _N_vector(self):

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

333 '''

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

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

336

337 def _n_xyzh4(self, datum):

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

339 '''

340 def _ErrorEPS0(x):

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

342

343 _xinstanceof(Datum, datum=datum)

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

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

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

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

348 E = datum.ellipsoid

349 x, y, z = self.xyz

350

351 # Kenneth Gade eqn 23

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

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

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

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

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

357 if isnear0(t):

358 raise _ErrorEPS0(t)

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

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

361 t = v * _2_0

362 if t < EPS0: # isnear0

363 raise _ErrorEPS0(t)

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

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

366 if isnear0(k):

367 raise _ErrorEPS0(k)

368 t = k + E.e2

369 if isnear0(t):

370 raise _ErrorEPS0(t)

371 e = k / t

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

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

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

375 if t < EPS0: # isnear0

376 raise _ErrorEPS0(t)

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

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

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

380

381 @Property_RO

382 def philam(self):

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

384 '''

385 return self.toEcef().philam

386

387 @Property_RO

388 def philamheight(self):

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

390 '''

391 return self.toEcef().philamheight

392

393 @Property_RO

394 def philamheightdatum(self):

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

396 '''

397 return self.toEcef().philamheightdatum

398

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

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

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

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

403

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

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

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

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

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

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

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

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

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

413 (C{bool}).

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

415

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

417

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

419

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

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

422

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

424

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

426 '''

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

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

429

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

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

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

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

434

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

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

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

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

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

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

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

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

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

444

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

446

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

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

449

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

451

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

453 '''

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

455 useZ=useZ, datum=self.datum)

456

457 @deprecated_method

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

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

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

461

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

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

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

465

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

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

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

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

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

471 non-negative).

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

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

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

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

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

477 (C{bool}).

478

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

480

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

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

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

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

485

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

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

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

489

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

491

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

493 '''

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

495 useZ=useZ, datum=self.datum)

496

497 @deprecated_method

498 def to2ab(self): # PYCHOK no cover

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

500

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

502 '''

503 return self.philam

504

505 @deprecated_method

506 def to2ll(self): # PYCHOK no cover

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

508

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

510 '''

511 return self.latlon

512

513 @deprecated_method

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

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

516

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

518

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

520 as its name may suggest.

521 '''

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

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

524

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

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

527# '''

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

529# if LL is not None:

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

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

532# setattr(r, n, v)

533# return r

534

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

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

537

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

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

540

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

542

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

544 invalid.

545 '''

546 _xinstanceof(Datum, datum2=datum2)

547

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

549 self.toDatum(datum)

550

551 i, d = False, c.datum

552 if d == datum2:

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

554

555 elif d == _WGS84:

556 d = datum2 # convert from WGS84 to datum2

557

558 elif datum2 == _WGS84:

559 i = True # convert to WGS84 by inverse transformation

560

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

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

563 d = datum2

564

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

566

567 convertDatum = toDatum # for backward compatibility

568

569 def toEcef(self):

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

571

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

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

574

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

576 '''

577 return self._ecef9

578

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

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

581

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

583 or L{a_f2Tuple}).

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

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

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

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

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

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

590

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

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

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

594

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

596 '''

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

598 if d == self.datum:

599 r = self.toEcef()

600 else:

601 c = self.toDatum(d)

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

603

604 if LatLon: # class or .classof

605 h = _heigHt(r, height)

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

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

608 _xdatum(r.datum, d)

609 return r

610

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

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

613

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

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

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

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

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

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

620

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

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

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

624

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

626 '''

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

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

629

630 def toLtp(self, Ecef=None):

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

632

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

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

635 '''

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

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

638

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

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

641

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

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

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

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

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

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

648

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

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

651

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

653

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

655

656 @example:

657

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

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

660 '''

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

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

663

664 if Nvector is not None:

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

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

667 return r

668

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

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

671

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

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

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

675

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

677 '''

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

679

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

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

682 to this cartesian.

683

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

685 @kwarg inverse: Apply the inverse of the Helmert

686 transform (C{bool}).

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

688 overriding this cartesian's datum.

689

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

691

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

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

694 '''

695 d = datum or self.datum

696 if inverse and d != _WGS84:

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

698 txt=_not_(_WGS84.name))

699

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

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

702

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

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

705

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

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

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

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

710

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

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

713

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

715 '''

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

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

718

719

720__all__ += _ALL_DOCS(CartesianBase)

721

722# **) MIT License

723#

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

725#

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

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

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

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

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

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

732#

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

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

735#

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

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

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

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

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

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

742# OTHER DEALINGS IN THE SOFTWARE.