Coverage for pygeodesy/cartesianBase.py: 92%
320 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-29 12:40 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-29 12:40 -0400
2# -*- coding: utf-8 -*-
4u'''(INTERNAL) Private C{CartesianBase} class for elliposiodal, spherical and N-/vectorial
5C{Cartesian}s and public functions L{rtp2xyz}, L{rtp2xyz_}, L{xyz2rtp} and L{xyz2rtp_}.
7After I{(C) Chris Veness 2011-2024} published under the same MIT Licence**, see
8U{https://www.Movable-Type.co.UK/scripts/latlong.html},
9U{https://www.Movable-Type.co.UK/scripts/latlong-vectors.html} and
10U{https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html}.
11'''
13from pygeodesy.basics import _isin, _xinstanceof, typename
14from pygeodesy.constants import EPS, EPS0, INT0, PI2, _isfinite, isnear0, \
15 _0_0, _1_0, _N_1_0, _2_0, _4_0, _6_0
16from pygeodesy.datums import Datum, _earth_ellipsoid, _spherical_datum, \
17 Transform, _WGS84
18# from pygeodesy.ecef import EcefKarney # _MODS
19from pygeodesy.ecefLocals import _EcefLocal
20from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, _xattr, \
21 _xdatum, _xkwds, _xkwds_get, _xkwds_pop2
22from pygeodesy.fmath import cbrt, hypot, hypot_, hypot2, fabs, sqrt # hypot
23# from pygeodesy.formy import _hartzell # _MODS
24from pygeodesy.fsums import fsumf_, Fmt
25# from pygeodesy.internals import typename # from .basics
26from pygeodesy.interns import _COMMASPACE_, _datum_, _no_, _phi_
27from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!
28from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
29from pygeodesy.named import _name2__, _Pass
30from pygeodesy.namedTuples import LatLon4Tuple, _NamedTupleTo , Vector3Tuple, \
31 Vector4Tuple
32# from pygeodesy.nvectorBase import _N_vector # _MODS
33from pygeodesy.props import deprecated_method, Property, Property_RO, property_doc_, \
34 property_RO, _update_all
35# from pygeodesy import resections as _resections # _MODS.into
36# from pygeodesy.streprs import Fmt # from .fsums
37# from pygeodesy.triaxials import Triaxial_ # _MODS
38from pygeodesy.units import Degrees, Height, _heigHt, _isMeter, Meter, Radians
39from pygeodesy.utily import acos1, atan2, sincos2d, sincos2_, degrees, radians
40from pygeodesy.vector3d import Vector3d, _xyzhdlln4
41# from pygeodesy.vector3dBase import _xyz3 # _MODS
42# from pygeodesy import ltp # _MODS
44# from math import degrees, fabs, radians, sqrt # from .fmath, .utily
46__all__ = _ALL_LAZY.cartesianBase
47__version__ = '25.05.07'
49_r_ = 'r'
50_resections = _MODS.into(resections=__name__)
51_theta_ = 'theta'
54class CartesianBase(Vector3d, _EcefLocal):
55 '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.
56 '''
57 _datum = None # L{Datum}, to be overriden
58 _height = None # height (L{Height}), set or approximated
60 def __init__(self, x_xyz, y=None, z=None, datum=None, **ll_name):
61 '''New C{Cartesian...}.
63 @arg x_xyz: Cartesian X coordinate (C{scalar}) or a C{Cartesian},
64 L{Ecef9Tuple}, L{Vector3Tuple} or L{Vector4Tuple}.
65 @kwarg y: Cartesian Y coordinate (C{scalar}), ignored if B{C{x_xyz}}
66 is not C{scalar}, otherwise same units as B{C{x_xyz}}.
67 @kwarg z: Cartesian Z coordinate (C{scalar}), like B{C{y}}.
68 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
69 or L{a_f2Tuple}).
70 @kwarg ll_name: Optional C{B{name}=NN} (C{str}) and optional, original
71 latlon C{B{ll}=None} (C{LatLon}).
73 @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}} coordinate
74 or B{C{x_xyz}} not a C{Cartesian}, L{Ecef9Tuple},
75 L{Vector3Tuple} or L{Vector4Tuple} or B{C{datum}} is
76 not a L{Datum}.
77 '''
78 h, d, ll, n = _xyzhdlln4(x_xyz, None, datum, **ll_name)
79 Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=n)
80 if h is not None:
81 self._height = Height(h)
82 if d is not None:
83 self.datum = d
85# def __matmul__(self, other): # PYCHOK Python 3.5+
86# '''Return C{NotImplemented} for C{c_ = c @ datum} and C{c_ = c @ transform}.
87# '''
88# return NotImplemented if isinstance(other, (Datum, Transform)) else \
89# _NotImplemented(self, other)
91 def cassini(self, pointB, pointC, alpha, beta, useZ=False):
92 '''3-Point resection between this and 2 other points using U{Cassini
93 <https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.
95 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
96 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
97 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
98 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
99 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
100 B{C{pointC}} (C{degrees}, non-negative).
101 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
102 B{C{pointC}} (C{degrees}, non-negative).
103 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
104 force C{z=INT0} (C{bool}).
106 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
108 @return: The survey point, an instance of this (sub-)class.
110 @raise ResectionError: Near-coincident, -colinear or -concyclic points
111 or negative or invalid B{C{alpha}} or B{C{beta}}.
113 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.
115 @see: Function L{pygeodesy.cassini} for references and more details.
116 '''
117 return _resections.cassini(self, pointB, pointC, alpha, beta,
118 useZ=useZ, datum=self.datum)
120 @deprecated_method
121 def collins(self, pointB, pointC, alpha, beta, useZ=False):
122 '''DEPRECATED, use method L{collins5}.'''
123 return self.collins5(pointB, pointC, alpha, beta, useZ=useZ)
125 def collins5(self, pointB, pointC, alpha, beta, useZ=False):
126 '''3-Point resection between this and 2 other points using U{Collins<https://Dokumen.tips/
127 documents/three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.
129 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
130 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
131 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
132 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
133 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to
134 B{C{pointC}} (C{degrees}, non-negative).
135 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to
136 B{C{pointC}} (C{degrees}, non-negative).
137 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise
138 force C{z=INT0} (C{bool}).
140 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.
142 @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},
143 auxiliary C{pointH}, each an instance of this (sub-)class and
144 triangle sides C{a}, C{b} and C{c}.
146 @raise ResectionError: Near-coincident, -colinear or -concyclic points
147 or negative or invalid B{C{alpha}} or B{C{beta}}.
149 @raise TypeError: Invalid B{C{pointB}} or B{C{pointM}}.
151 @see: Function L{pygeodesy.collins5} for references and more details.
152 '''
153 return _resections.collins5(self, pointB, pointC, alpha, beta,
154 useZ=useZ, datum=self.datum)
156 @deprecated_method
157 def convertDatum(self, datum2, **datum):
158 '''DEPRECATED, use method L{toDatum}.'''
159 return self.toDatum(datum2, **datum)
161 @property_doc_(''' this cartesian's datum (L{Datum}).''')
162 def datum(self):
163 '''Get this cartesian's datum (L{Datum}).
164 '''
165 return self._datum
167 @datum.setter # PYCHOK setter!
168 def datum(self, datum):
169 '''Set this cartesian's C{datum} I{without conversion}
170 (L{Datum}), ellipsoidal or spherical.
172 @raise TypeError: The B{C{datum}} is not a L{Datum}.
173 '''
174 d = _spherical_datum(datum, name=self.name)
175 if self._datum: # is not None
176 if d.isEllipsoidal and not self._datum.isEllipsoidal:
177 raise _IsnotError(_ellipsoidal_, datum=datum)
178 elif d.isSpherical and not self._datum.isSpherical:
179 raise _IsnotError(_spherical_, datum=datum)
180 if self._datum != d:
181 _update_all(self)
182 self._datum = d
184 def destinationXyz(self, delta, Cartesian=None, **name_Cartesian_kwds):
185 '''Calculate the destination using a I{local} delta from this cartesian.
187 @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu}, L{Ned}
188 or L{Local9Tuple}).
189 @kwarg Cartesian: Optional (geocentric) class to return the destination
190 or C{None}.
191 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
192 additional B{C{Cartesian}} keyword arguments, ignored if
193 C{B{Cartesian} is None}.
195 @return: Destination as a C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})}
196 instance or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,
197 z, lat, lon, height, C, M, datum)} with C{M=None} always.
199 @raise TypeError: Invalid B{C{delta}}, B{C{Cartesian}} or B{C{Cartesian_kwds}}
200 item or C{datum} missing or incompatible.
201 '''
202 n, kwds = _name2__(name_Cartesian_kwds, _or_nameof=self)
203 if Cartesian is None:
204 r = self._ltp._local2ecef(delta, nine=True) # _EcefLocal._ltp
205 else:
206 d = self.datum
207 if not d:
208 raise _TypeError(delta=delta, txt=_no_(_datum_))
209 t = _xkwds_get(kwds, datum=d)
210 if _xattr(t, ellipsoid=None) != d.ellipsoid:
211 raise _TypeError(datum=t, txt=str(d))
212 c = self._ltp._local2ecef(delta, nine=False) # _EcefLocal._ltp
213 r = Cartesian(*c, **kwds)
214 return r.renamed(n) if n else r
216 @Property_RO
217 def _ecef9(self):
218 '''(INTERNAL) Helper for L{toEcef}, L{toLocal} and L{toLtp} (L{Ecef9Tuple}).
219 '''
220 return self.Ecef(self.datum, name=self.name).reverse(self, M=True)
222 @property_RO
223 def ellipsoidalCartesian(self):
224 '''Get the C{Cartesian type} iff ellipsoidal, overloaded in L{CartesianEllipsoidalBase}.
225 '''
226 return False
228 def hartzell(self, los=False, earth=None):
229 '''Compute the intersection of a Line-Of-Sight from this cartesian Point-Of-View
230 (pov) and this cartesian's C{datum} ellipsoid surface.
232 @kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}),
233 C{True} for the I{normal, plumb} onto the surface or I{False} or
234 C{None} to point to the center of the ellipsoid.
235 @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}
236 or C{scalar} radius in C{meter}), overriding this cartesian's
237 datum.
239 @return: The intersection (C{Cartesian}) with C{.height} set to the distance to
240 this C{pov}.
242 @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside
243 the ellipsoid or B{C{los}} points outside or away from
244 the ellipsoid.
246 @raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}.
248 @see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details.
249 '''
250 return _MODS.formy._hartzell(self, los, earth)
252 @Property
253 def height(self):
254 '''Get the height (C{meter}).
255 '''
256 return self._height4.h if self._height is None else self._height
258 @height.setter # PYCHOK setter!
259 def height(self, height):
260 '''Set the height (C{meter}).
262 @raise TypeError: Invalid B{C{height}} C{type}.
264 @raise ValueError: Invalid B{C{height}}.
265 '''
266 h = Height(height)
267 if self._height != h:
268 _update_all(self)
269 self._height = h
271 def _height2C(self, r, Cartesian=None, datum=None, height=INT0, **kwds):
272 '''(INTERNAL) Helper for methods C{.height3} and C{.height4}.
273 '''
274 if Cartesian is not None:
275 r = Cartesian(r, **kwds)
276 if datum is not None:
277 r.datum = datum
278 if height is not None:
279 r.height = height # Height(height)
280 return r
282 def height3(self, earth=None, height=None, **Cartesian_and_kwds):
283 '''Compute the cartesian at a height above or below this certesian's
284 C{datum} ellipsoid surface.
286 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
287 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},
288 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).
289 @kwarg height: The height (C{meter}, conventionally), overriding this
290 cartesian's height.
291 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return
292 the cartesian I{at height} and additional B{C{Cartesian}}
293 keyword arguments.
295 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None},
296 a L{Vector3Tuple}C{(x, y, z)} with the C{x}, C{y} and C{z}
297 coordinates I{at height} in C{meter}, conventionally.
299 @note: This cartesian's coordinates are returned if B{C{earth}} and this
300 datum or B{C{height}} and/or this height are C{None} or undefined.
302 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}
303 does not accept a B{C{datum}} keyword agument.
305 @raise TriaxialError: No convergence in triaxial root finding.
307 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
308 '''
309 n = typename(self.height3)
310 d = self.datum if earth is None else _spherical_datum(earth, name=n)
311 c, h = self, _heigHt(self, height)
312 if h and d:
313 R, r = self.Roc2(earth=d)
314 if R > EPS0:
315 R = (R + h) / R
316 r = ((r + h) / r) if r > EPS0 else _1_0
317 c = c.times_(R, R, r)
319 r = Vector3Tuple(c.x, c.y, c.z, name=n)
320 if Cartesian_and_kwds:
321 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))
322 return r
324 @Property_RO
325 def _height4(self):
326 '''(INTERNAL) Get this C{height4}-tuple.
327 '''
328 try:
329 r = self.datum.ellipsoid.height4(self, normal=True)
330 except (AttributeError, ValueError): # no datum, null cartesian,
331 r = Vector4Tuple(self.x, self.y, self.z, 0, name__=self.height4)
332 return r
334 def height4(self, earth=None, normal=True, **Cartesian_and_kwds):
335 '''Compute the projection of this point on and the height above or below
336 this datum's ellipsoid surface.
338 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
339 I{overriding} this datum (L{Datum}, L{Ellipsoid},
340 L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},
341 L{JacobiConformal} or C{meter}, conventionally).
342 @kwarg normal: If C{True}, the projection is the nearest point on the
343 ellipsoid's surface, otherwise the intersection of the
344 radial line to the ellipsoid's center and surface C{bool}).
345 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return
346 the I{projection} and additional B{C{Cartesian}} keyword
347 arguments.
349 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None}, a
350 L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}
351 and C{z} coordinates and height C{h} in C{meter}, conventionally.
353 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}
354 does not accept a B{C{datum}} keyword agument.
356 @raise TriaxialError: No convergence in triaxial root finding.
358 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
360 @see: Methods L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.
361 '''
362 n = typename(self.height4)
363 d = self.datum if earth is None else earth
364 if normal and d is self.datum:
365 r = self._height4
366 elif isinstance(d, _MODS.triaxials.Triaxial_):
367 r = d.height4(self, normal=normal)
368 try:
369 d = d.toEllipsoid(name=n)
370 except (TypeError, ValueError): # TriaxialError
371 d = None
372 else:
373 r = _earth_ellipsoid(d).height4(self, normal=normal)
375 if Cartesian_and_kwds:
376 if d and not isinstance(d, Datum):
377 d = _spherical_datum(d, name=n)
378 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))
379 return r
381 @Property_RO
382 def isEllipsoidal(self):
383 '''Check whether this cartesian is ellipsoidal (C{bool} or C{None} if unknown).
384 '''
385 return _xattr(self.datum, isEllipsoidal=None)
387 @Property_RO
388 def isSpherical(self):
389 '''Check whether this cartesian is spherical (C{bool} or C{None} if unknown).
390 '''
391 return _xattr(self.datum, isSpherical=None)
393 @Property_RO
394 def latlon(self):
395 '''Get this cartesian's (geodetic) lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
396 '''
397 return self.toEcef().latlon
399 @Property_RO
400 def latlonheight(self):
401 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height (L{LatLon3Tuple}C{(lat, lon, height)}).
402 '''
403 return self.toEcef().latlonheight
405 @Property_RO
406 def latlonheightdatum(self):
407 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
408 '''
409 return self.toEcef().latlonheightdatum
411 @Property_RO
412 def _N_vector(self):
413 '''(INTERNAL) Get the (C{nvectorBase._N_vector_}).
414 '''
415 _N = _MODS.nvectorBase._N_vector_
416 x, y, z, h = self._n_xyzh4(self.datum)
417 return _N(x, y, z, h=h, name=self.name)
419 def _n_xyzh4(self, datum):
420 '''(INTERNAL) Get the n-vector components as L{Vector4Tuple}.
421 '''
422 def _ErrorEPS0(x):
423 return _ValueError(origin=self, txt=Fmt.PARENSPACED(EPS0=x))
425 _xinstanceof(Datum, datum=datum)
426 # <https://www.Movable-Type.co.UK/scripts/geodesy/docs/
427 # latlon-nvector-ellipsoidal.js.html#line309>,
428 # <https://GitHub.com/pbrod/nvector>/src/nvector/core.py>
429 # _equation23 and <https://www.NavLab.net/nvector>
430 E = datum.ellipsoid
431 x, y, z = self.xyz3
433 # Kenneth Gade eqn 23
434 p = hypot2(x, y) * E.a2_
435 q = z**2 * E.e21 * E.a2_
436 r = fsumf_(p, q, -E.e4) / _6_0
437 s = (p * q * E.e4) / (_4_0 * r**3)
438 t = cbrt(fsumf_(_1_0, s, sqrt(s * (_2_0 + s))))
439 if isnear0(t):
440 raise _ErrorEPS0(t)
441 u = fsumf_(_1_0, t, _1_0 / t) * r
442 v = sqrt(u**2 + E.e4 * q)
443 t = v * _2_0
444 if t < EPS0: # isnear0
445 raise _ErrorEPS0(t)
446 w = fsumf_(u, v, -q) * E.e2 / t
447 k = sqrt(fsumf_(u, v, w**2)) - w
448 if isnear0(k):
449 raise _ErrorEPS0(k)
450 t = k + E.e2
451 if isnear0(t):
452 raise _ErrorEPS0(t)
453 e = k / t
454# d = e * hypot(x, y)
455# tmp = 1 / hypot(d, z) == 1 / hypot(e * hypot(x, y), z)
456 t = hypot_(x * e, y * e, z) # == 1 / tmp
457 if t < EPS0: # isnear0
458 raise _ErrorEPS0(t)
459 h = fsumf_(k, E.e2, _N_1_0) / k * t
460 s = e / t # == e * tmp
461 return Vector4Tuple(x * s, y * s, z / t, h, name=self.name)
463 @Property_RO
464 def philam(self):
465 '''Get this cartesian's (geodetic) lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
466 '''
467 return self.toEcef().philam
469 @Property_RO
470 def philamheight(self):
471 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height (L{PhiLam3Tuple}C{(phi, lam, height)}).
472 '''
473 return self.toEcef().philamheight
475 @Property_RO
476 def philamheightdatum(self):
477 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
478 '''
479 return self.toEcef().philamheightdatum
481 def pierlot(self, point2, point3, alpha12, alpha23, useZ=False, eps=EPS):
482 '''3-Point resection between this and two other points using U{Pierlot
483 <http://www.Telecom.ULg.ac.Be/triangulation>}'s method C{ToTal} with
484 I{approximate} limits for the (pseudo-)singularities.
486 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
487 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
488 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
489 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
490 @arg alpha12: Angle subtended from this point to B{C{point2}} or
491 B{C{alpha2 - alpha}} (C{degrees}).
492 @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or
493 B{C{alpha3 - alpha2}} (C{degrees}).
494 @kwarg useZ: If C{True}, interpolate the Z component, otherwise use C{z=INT0}
495 (C{bool}).
496 @kwarg eps: Tolerance for C{cot} (pseudo-)singularities (C{float}).
498 @note: This point, B{C{point2}} and B{C{point3}} are ordered counter-clockwise.
500 @return: The survey (or robot) point, an instance of this (sub-)class.
502 @raise ResectionError: Near-coincident, -colinear or -concyclic points
503 or invalid B{C{alpha12}} or B{C{alpha23}}.
505 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
507 @see: Function L{pygeodesy.pierlot} for references and more details.
508 '''
509 return _resections.pierlot(self, point2, point3, alpha12, alpha23,
510 useZ=useZ, eps=eps, datum=self.datum)
512 def pierlotx(self, point2, point3, alpha1, alpha2, alpha3, useZ=False):
513 '''3-Point resection between this and two other points using U{Pierlot
514 <http://www.Telecom.ULg.ac.Be/publi/publications/pierlot/Pierlot2014ANewThree>}'s
515 method C{ToTal} with I{exact} limits for the (pseudo-)singularities.
517 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
518 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
519 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
520 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
521 @arg alpha1: Angle at B{C{point1}} (C{degrees}).
522 @arg alpha2: Angle at B{C{point2}} (C{degrees}).
523 @arg alpha3: Angle at B{C{point3}} (C{degrees}).
524 @kwarg useZ: If C{True}, interpolate the survey point's Z component,
525 otherwise use C{z=INT0} (C{bool}).
527 @return: The survey (or robot) point, an instance of this (sub-)class.
529 @raise ResectionError: Near-coincident, -colinear or -concyclic points or
530 invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.
532 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
534 @see: Function L{pygeodesy.pierlotx} for references and more details.
535 '''
536 return _resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3,
537 useZ=useZ, datum=self.datum)
539 def Roc2(self, earth=None):
540 '''Compute this cartesian's I{normal} and I{pseudo, z-based} radius of curvature.
542 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
543 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},
544 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).
546 @return: 2-Tuple C{(R, r)} with the I{normal} and I{pseudo, z-based} radius of
547 curvature C{R} respectively C{r}, both in C{meter} conventionally.
549 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.
550 '''
551 r = z = fabs( self.z)
552 R, _0 = hypot(self.x, self.y), EPS0
553 if R < _0: # polar
554 R = z
555 elif z > _0: # non-equatorial
556 d = self.datum if earth is None else _spherical_datum(earth)
557 e = self.toLatLon(datum=d, height=0, LatLon=None) # Ecef9Tuple
558 M = e.M # EcefMatrix
559 sa, ca = map(fabs, (M._2_2_, M._2_1_) if M else sincos2d(e.lat))
560 if ca < _0: # polar
561 R = z
562 else: # prime-vertical, normal roc R
563 R = R / ca # /= chokes PyChecker
564 r = R if sa < _0 else (r / sa) # non-/equatorial
565 return R, r
567 @property_RO
568 def sphericalCartesian(self):
569 '''Get the C{Cartesian type} iff spherical, overloaded in L{CartesianSphericalBase}.
570 '''
571 return False
573 @deprecated_method
574 def tienstra(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
575 '''DEPRECATED, use method L{tienstra7}.'''
576 return self.tienstra7(pointB, pointC, alpha, beta=beta, gamma=gamma, useZ=useZ)
578 def tienstra7(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):
579 '''3-Point resection between this and two other points using U{Tienstra
580 <https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.
582 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
583 C{Vector2Tuple} if C{B{useZ}=False}).
584 @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or
585 C{Vector2Tuple} if C{B{useZ}=False}).
586 @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}} (C{degrees},
587 non-negative).
588 @kwarg beta: Angle subtended by triangle side C{b} from this to B{C{pointC}} (C{degrees},
589 non-negative) or C{None} if C{B{gamma} is not None}.
590 @kwarg gamma: Angle subtended by triangle side C{c} from this to B{C{pointB}} (C{degrees},
591 non-negative) or C{None} if C{B{beta} is not None}.
592 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}
593 (C{bool}).
595 @note: This point, B{C{pointB}} and B{C{pointC}} are ordered clockwise.
597 @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP},
598 an instance of this (sub-)class and triangle angle C{A} at this point,
599 C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees} and
600 triangle sides C{a}, C{b} and C{c}.
602 @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of
603 B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or
604 negative B{C{alpha}}, B{C{beta}} or B{C{gamma}}.
606 @raise TypeError: Invalid B{C{pointB}} or B{C{pointC}}.
608 @see: Function L{pygeodesy.tienstra7} for references and more details.
609 '''
610 return _resections.tienstra7(self, pointB, pointC, alpha, beta, gamma,
611 useZ=useZ, datum=self.datum)
613 @deprecated_method
614 def to2ab(self): # PYCHOK no cover
615 '''DEPRECATED, use property C{philam}.
617 @return: A L{PhiLam2Tuple}C{(phi, lam)}.
618 '''
619 return self.philam
621 @deprecated_method
622 def to2ll(self): # PYCHOK no cover
623 '''DEPRECATED, use property C{latlon}.
625 @return: A L{LatLon2Tuple}C{(lat, lon)}.
626 '''
627 return self.latlon
629 @deprecated_method
630 def to3llh(self, datum=None): # PYCHOK no cover
631 '''DEPRECATED, use property L{latlonheight} or L{latlonheightdatum}.
633 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
635 @note: This method returns a B{C{-4Tuple}} I{and not a} C{-3Tuple}
636 as its name may suggest.
637 '''
638 t = self.toLatLon(datum=datum, LatLon=None)
639 return LatLon4Tuple(t.lat, t.lon, t.height, t.datum, name=self.name)
641# def _to3LLh(self, datum, LL, **pairs): # OBSOLETE
642# '''(INTERNAL) Helper for C{subclass.toLatLon} and C{.to3llh}.
643# '''
644# r = self.to3llh(datum) # LatLon3Tuple
645# if LL is not None:
646# r = LL(r.lat, r.lon, height=r.height, datum=datum, name=self.name)
647# for n, v in pairs.items():
648# setattr(r, n, v)
649# return r
651 def toDatum(self, datum2, datum=None):
652 '''Convert this cartesian from one datum to an other.
654 @arg datum2: Datum to convert I{to} (L{Datum}).
655 @kwarg datum: Datum to convert I{from} (L{Datum}).
657 @return: The converted point (C{Cartesian}).
659 @raise TypeError: B{C{datum2}} or B{C{datum}}
660 invalid.
661 '''
662 _xinstanceof(Datum, datum2=datum2)
664 c = self if _isin(datum, None, self.datum) else \
665 self.toDatum(datum)
667 i, d = False, c.datum
668 if d == datum2:
669 return c.copy() if c is self else c
671 elif d is None or (d.transform.isunity and
672 datum2.transform.isunity):
673 return c.dup(datum=datum2)
675 elif d == _WGS84:
676 d = datum2 # convert from WGS84 to datum2
678 elif datum2 == _WGS84:
679 i = True # convert to WGS84 by inverse transformation
681 else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first
682 c = c.toTransform(d.transform, inverse=True, datum=_WGS84)
683 d = datum2
685 return c.toTransform(d.transform, inverse=i, datum=datum2)
687 def toEcef(self):
688 '''Convert this cartesian to I{geodetic} (lat-/longitude) coordinates.
690 @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
691 with C{C} and C{M} if available.
693 @raise EcefError: A C{.datum} or an ECEF issue.
694 '''
695 return self._ecef9
697 def toLatLon(self, datum=None, height=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.toDatum
698 '''Convert this cartesian to a I{geodetic} (lat-/longitude) point.
700 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
701 @kwarg height: Optional height, overriding the converted height (C{meter}), only if
702 C{B{LatLon} is not None}.
703 @kwarg LatLon: Optional class to return the geodetic point (C{LatLon}) or C{None}.
704 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if
705 C{B{LatLon} is None}.
707 @return: The geodetic point (B{C{LatLon}}) or if C{B{LatLon}is None}, an
708 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with C{C}
709 and C{M} if available.
711 @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}.
712 '''
713 d = _spherical_datum(datum or self.datum, name=self.name)
714 if d == self.datum:
715 r = self.toEcef()
716 else:
717 c = self.toDatum(d)
718 r = c.Ecef(d, name=self.name).reverse(c, M=LatLon is None)
720 if LatLon: # class or .classof
721 h = _heigHt(r, height)
722 r = LatLon(r.lat, r.lon, datum=r.datum, height=h,
723 **_xkwds(LatLon_kwds, name=r.name))
724 _xdatum(r.datum, d)
725 return r
727 def toNvector(self, Nvector=None, datum=None, **name_Nvector_kwds):
728 '''Convert this cartesian to C{n-vector} components, I{including height}.
730 @kwarg Nvector: Optional class to return the C{n-vector} components
731 (C{Nvector}) or C{None}.
732 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
733 or L{a_f2Tuple}) overriding this cartesian's datum.
734 @kwarg name_Nvector_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
735 additional B{C{Nvector}} keyword arguments, ignored if
736 C{B{Nvector} is None}.
738 @return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if
739 C{B{Nvector} is None}.
741 @raise TypeError: Invalid B{C{Nvector}}, B{C{datum}} or
742 B{C{name_Nvector_kwds}} item.
744 @raise ValueError: B{C{Cartesian}} at origin.
745 '''
746 r, d = self._N_vector.xyzh, self.datum
747 if datum is not None:
748 d = _spherical_datum(datum, name=self.name)
749 if d != self.datum:
750 r = self._n_xyzh4(d)
752 if Nvector is None:
753 n, _ = _name2__(name_Nvector_kwds, _or_nameof=self)
754 if n:
755 r = r.dup(name=n)
756 else:
757 kwds = _xkwds(name_Nvector_kwds, h=r.h, datum=d)
758 r = Nvector(r.x, r.y, r.z, **self._name1__(kwds))
759 return r
761 def toRtp(self):
762 '''Convert this cartesian to I{spherical, polar} coordinates.
764 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
765 and C{phi}, both in L{Degrees}.
767 @see: Function L{xyz2rtp_} and class L{RadiusThetaPhi3Tuple}.
768 '''
769 return _rtp3(self.toRtp, Degrees, self, name=self.name)
771 def toStr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_): # PYCHOK expected
772 '''Return the string representation of this cartesian.
774 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
775 @kwarg fmt: Enclosing backets format (C{letter}).
776 @kwarg sep: Separator to join (C{str}).
778 @return: Cartesian represented as "[x, y, z]" (C{str}).
779 '''
780 return Vector3d.toStr(self, prec=prec, fmt=fmt, sep=sep)
782 def toTransform(self, transform, inverse=False, datum=None):
783 '''Apply a Helmert transform to this cartesian.
785 @arg transform: Transform to apply (L{Transform} or L{TransformXform}).
786 @kwarg inverse: Apply the inverse of the C{B{transform}} (C{bool}).
787 @kwarg datum: Datum for the transformed cartesian (L{Datum}), overriding
788 this cartesian's datum but I{not} taken it into account.
790 @return: A transformed cartesian (C{Cartesian}) or a copy of this
791 cartesian if C{B{transform}.isunity}.
793 @raise TypeError: Invalid B{C{transform}}.
794 '''
795 _xinstanceof(Transform, transform=transform)
796 if transform.isunity:
797 c = self.dup(datum=datum or self.datum)
798 else:
799 # if inverse and d != _WGS84:
800 # raise _ValueError(inverse=inverse, datum=d,
801 # txt_not_=_WGS84.name)
802 xyz = transform.transform(*self.xyz3, inverse=inverse)
803 c = self.dup(xyz=xyz, datum=datum or self.datum)
804 return c
806 def toVector(self, Vector=None, **Vector_kwds):
807 '''Return this cartesian's I{geocentric} components as vector.
809 @kwarg Vector: Optional class to return the I{geocentric}
810 components (L{Vector3d}) or C{None}.
811 @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
812 arguments, ignored if C{B{Vector} is None}.
814 @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)} if
815 C{B{Vector} is None}.
817 @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}}.
818 '''
819 return self.xyz if Vector is None else Vector(
820 self.x, self.y, self.z, **self._name1__(Vector_kwds))
823class RadiusThetaPhi3Tuple(_NamedTupleTo):
824 '''3-Tuple C{(r, theta, phi)} with radial distance C{r} in C{meter}, inclination
825 C{theta} (with respect to the positive z-axis) and azimuthal angle C{phi} in
826 L{Degrees} I{or} L{Radians} representing a U{spherical, polar position
827 <https://WikiPedia.org/wiki/Spherical_coordinate_system>}.
828 '''
829 _Names_ = (_r_, _theta_, _phi_)
830 _Units_ = ( Meter, _Pass, _Pass)
832 def toCartesian(self, **name_Cartesian_and_kwds):
833 '''Convert this L{RadiusThetaPhi3Tuple} to a cartesian C{(x, y, z)} vector.
835 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN}, overriding this
836 name and optional class C{B{Cartesian}=None} and additional
837 C{B{Cartesian}} keyword arguments.
839 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword
840 argument is given, a L{Vector3Tuple}C{(x, y, z)} with C{x}, C{y}
841 and C{z} in the same units as radius C{r}, C{meter} conventionally.
843 @see: Function L{rtp2xyz_}.
844 '''
845 r, t, p = self
846 t, p, _ = _NamedTupleTo._Radians3(self, t, p)
847 return rtp2xyz_(r, t, p, **name_Cartesian_and_kwds)
849 def toDegrees(self, **name):
850 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Degrees}.
852 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
854 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
855 and C{phi} both in L{Degrees}.
856 '''
857 return self._toX3U(_NamedTupleTo._Degrees3, Degrees, name)
859 def toRadians(self, **name):
860 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Radians}.
862 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
864 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}
865 and C{phi} both in L{Radians}.
866 '''
867 return self._toX3U(_NamedTupleTo._Radians3, Radians, name)
869 def _toU(self, U):
870 M = RadiusThetaPhi3Tuple._Units_[0] # Meter
871 return self.reUnit(M, U, U).toUnits()
873 def _toX3U(self, _X3, U, name):
874 r, t, p = self
875 t, p, s = _X3(self, t, p)
876 if s is None or name:
877 n = self._name__(name)
878 s = self.classof(r, t, p, name=n)._toU(U)
879 return s
882def rtp2xyz(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds):
883 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.
885 @arg theta: Inclination B{C{theta}} (C{degrees} with respect to the positive z-axis),
886 required if C{B{r_rtp}} is C{scalar}, ignored otherwise.
887 @arg phi: Azimuthal angle B{C{phi}} (C{degrees}), like B{C{theta}}.
889 @see: Function L{rtp2xyz_} for further details.
890 '''
891 if isinstance(r_rtp, RadiusThetaPhi3Tuple):
892 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)
893 else:
894 c = rtp2xyz_(r_rtp, radians(theta), radians(phi), **name_Cartesian_and_kwds)
895 return c
898def rtp2xyz_(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds):
899 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.
901 @arg r_rtp: Radial distance (C{scalar}, conventially C{meter}) or a previous
902 L{RadiusThetaPhi3Tuple} instance.
903 @arg theta: Inclination B{C{theta}} (C{radians} with respect to the positive z-axis),
904 required if C{B{r_rtp}} is C{scalar}, ignored otherwise.
905 @arg phi: Azimuthal angle B{C{phi}} (C{radians}), like B{C{theta}}.
906 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN} (C{str}), C{B{Cartesian}=None}
907 class to return the coordinates and optionally, additional C{B{Cartesian}}
908 keyword arguments.
910 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword argument
911 is given a L{Vector3Tuple}C{(x, y, z)}, with C{x}, C{y} and C{z} in the same
912 units as radius C{r}, C{meter} conventionally.
914 @raise TypeError: Invalid B{C{r_rtp}}, B{C{theta}}, B{C{phi}} or
915 B{C{name_Cartesian_and_kwds}} item.
917 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}
918 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and functions L{rtp2xyz}
919 and L{xyz2rtp}.
920 '''
921 if isinstance(r_rtp, RadiusThetaPhi3Tuple):
922 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)
923 elif _isMeter(r_rtp):
924 r = r_rtp
925 if r and _isfinite(r):
926 s, z, y, x = sincos2_(theta, phi)
927 s *= r
928 z *= r
929 y *= s
930 x *= s
931 else:
932 x = y = z = r
934 n, kwds = _name2__(**name_Cartesian_and_kwds)
935 C, kwds = _xkwds_pop2(kwds, Cartesian=None)
936 c = Vector3Tuple(x, y, z, name=n) if C is None else \
937 C(x, y, z, name=n, **kwds)
938 else:
939 raise _TypeError(r_rtp=r_rtp, theta=theta, phi=phi)
940 return c
943def _rtp3(where, U, *x_y_z, **name):
944 '''(INTERNAL) Helper for C{.toRtp}, C{xyz2rtp} and C{xyz2rtp_}.
945 '''
946 x, y, z = _MODS.vector3dBase._xyz3(where, *x_y_z)
947 r = hypot_(x, y, z)
948 if r > 0:
949 t = acos1(z / r)
950 p = atan2(y, x)
951 while p < 0:
952 p += PI2
953 if U is Degrees:
954 t = degrees(t)
955 p = degrees(p)
956 else:
957 t = p = _0_0
958 return RadiusThetaPhi3Tuple(r, t, p, **name)._toU(U)
961def xyz2rtp(x_xyz, y=0, z=0, **name):
962 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.
964 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} and C{phi}, both
965 in L{Degrees}.
967 @see: Function L{xyz2rtp_} for further details.
968 '''
969 return _rtp3(xyz2rtp, Degrees, x_xyz, y, z, **name)
972def xyz2rtp_(x_xyz, y=0, z=0, **name):
973 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.
975 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
976 C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} or a C{tuple} or
977 C{list} of 3+ C{scalar} items) if no C{y_z} specified.
978 @arg y: Y component (C{scalar}), required if C{B{x_xyz}} is C{scalar}, ignored otherwise.
979 @arg z: Z component (C{scalar}), like B{C{y}}.
980 @kwarg name: Optional C{B{name}=NN} (C{str}).
982 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with radial distance C{r} (C{meter},
983 same units as C{x}, C{y} and C{z}), inclination C{theta} (with respect to the
984 positive z-axis) and azimuthal angle C{phi}, both in L{Radians}.
986 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}
987 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and function L{xyz2rtp}.
988 '''
989 return _rtp3(xyz2rtp_, Radians, x_xyz, y, z, **name)
992__all__ += _ALL_DOCS(CartesianBase)
994# **) MIT License
995#
996# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
997#
998# Permission is hereby granted, free of charge, to any person obtaining a
999# copy of this software and associated documentation files (the "Software"),
1000# to deal in the Software without restriction, including without limitation
1001# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1002# and/or sell copies of the Software, and to permit persons to whom the
1003# Software is furnished to do so, subject to the following conditions:
1004#
1005# The above copyright notice and this permission notice shall be included
1006# in all copies or substantial portions of the Software.
1007#
1008# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1009# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1010# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1011# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1012# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1013# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1014# OTHER DEALINGS IN THE SOFTWARE.