Coverage for pygeodesy / triaxials / triaxial3.py: 90%
442 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-08-27 13:47 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-08-27 13:47 -0400
2# -*- coding: utf-8 -*-
4u'''I{Ordered} triaxial ellipsoid classes L{Triaxial3} and L{Triaxial3B} for conversion between
5variuos lat-/longitudal and cartesian coordinates on a triaxial ellipsoid using L{Ang}, L{Deg},
6L{Rad} lat-, longitude and heading angles.
8Transcoded to pure Python from I{Karney}'s GeographicLib 2.7 C++ classes U{Ellipsoidal3<https://
9GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Ellipsoidal3.html>} and U{Cartesian3
10<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>}.
12Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2024-2025) and licensed under the MIT/X11
13License. For more information, see the U{GeographicLib 2.7 <https://GeographicLib.SourceForge.io/>}
14documentation.
16@var Triaxial3s.Amalthea: Triaxial3(name='Amalthea', a=125000, b=73000, c=64000, k2=0.106947697, kp2=0.893052303, volume=2446253479595252, area=93239507787.490356445, R2=86138.05359954)
17@var Triaxial3s.Ariel: Triaxial3(name='Ariel', a=581100, b=577900, c=577700, k2=0.05866109, kp2=0.94133891, volume=812633172614203904, area=4211301462766.580078125, R2=578899.578791275)
18@var Triaxial3s.Earth: Triaxial3(name='Earth', a=6378173.435, b=6378103.9, c=6356754.399999999, k2=0.996748146, kp2=0.003251854, volume=1083208241574987694080, area=510065911057440.9375, R2=6371008.987886564)
19@var Triaxial3s.Enceladus: Triaxial3(name='Enceladus', a=256600, b=251400, c=248300, k2=0.369647336, kp2=0.630352664, volume=67094551514082248, area=798618496278.596679688, R2=252095.300756832)
20@var Triaxial3s.Europa: Triaxial3(name='Europa', a=1564130, b=1561230, c=1560930, k2=0.093663002, kp2=0.906336998, volume=15966575194402123776, area=30663773697323.51953125, R2=1562096.533153486)
21@var Triaxial3s.Io: Triaxial3(name='Io', a=1829400, b=1819300, c=1815700, k2=0.262045618, kp2=0.737954382, volume=25313121117889765376, area=41691875849096.734375, R2=1821464.812747882)
22@var Triaxial3s.Mars: Triaxial3(name='Mars', a=3394600, b=3393300, c=3376300, k2=0.92878339, kp2=0.07121661, volume=162907283585817247744, area=144249140795107.4375, R2=3388064.624110653)
23@var Triaxial3s.Mimas: Triaxial3(name='Mimas', a=207400, b=196800, c=190600, k2=0.359218713, kp2=0.640781287, volume=32587072869017956, area=493855762247.691833496, R2=198241.75359411)
24@var Triaxial3s.Miranda: Triaxial3(name='Miranda', a=240400, b=234200, c=232900, k2=0.171062751, kp2=0.828937249, volume=54926187094835456, area=698880863325.757080078, R2=235828.692095158)
25@var Triaxial3s.Moon: Triaxial3(name='Moon', a=1735550, b=1735324, c=1734898, k2=0.653331685, kp2=0.346668315, volume=21886698675223740416, area=37838824729886.09375, R2=1735257.329122863)
26@var Triaxial3s.Tethys: Triaxial3(name='Tethys', a=535600, b=528200, c=525800, k2=0.243190549, kp2=0.756809451, volume=623086233855821440, area=3528073490771.393554688, R2=529863.348254881)
27@var Triaxial3s.WGS84_3: Triaxial3(name='WGS84_3', a=6378171.36, b=6378101.609999999, c=6356751.84, k2=0.996738165, kp2=0.003261835, volume=1083207064030173855744, area=510065541435967.5, R2=6371006.679496506)
28@var Triaxial3s.WGS84_35: Triaxial3(name='WGS84_35', a=6378172, b=6378102, c=6356752.314245179, k2=0.996726499, kp2=0.003273501, volume=1083207319768789942272, area=510065621722018.25, R2=6371007.180905545)
29@var Triaxial3s.WGS84_3r: Triaxial3(name='WGS84_3r', a=6378172, b=6378102, c=6356752, k2=0.996726547, kp2=0.003273453, volume=1083207266220584468480, area=510065604942135.875, R2=6371007.076110449)
30'''
31# make sure int/int division yields float quotient, see .basics
32from __future__ import division as _; del _ # noqa: E702 ;
34from pygeodesy.angles import Ang, Ang_, _Ang3Tuple, atan2, sincos2, _SinCos2
35from pygeodesy.basics import _copysign, map1
36from pygeodesy.constants import EPS, EPS_2, EPS02, EPS8, INT0, NAN, \
37 _EPSqrt, _SQRT3, _copysign_0_0, _copysign_1_0, \
38 _flipsign, _isfinite, _over, _1_over, _0_0, \
39 _0_5, _N_1_0, _1_0, _2_0, _3_0, _4_0, _9_0
40from pygeodesy.errors import _xattr, _xkwds, _xkwds_get, _xkwds_pop2
41from pygeodesy.fmath import cbrt2, fdot, hypot, hypot2, norm2, fabs, sqrt
42from pygeodesy.fsums import Fsum, fsumf_, Fmt
43from pygeodesy.interns import NN, _azimuth_, _DMAIN_, _h_, _lam_, _lat_, \
44 _lon_, _phi_
45# from pygeodesy.lazily import _ALL_LAZY # from .vector3d
46# from pygeodesy.named import _Pass # from .namedTuples
47from pygeodesy.namedTuples import _NamedTuple, _xyzh_Tuple, _Pass, Property_RO
48# from pygeodesy.props import Property_RO # from .namedTuples
49# from pygeodesy.streprs import Fmt # from .fsums
50from pygeodesy.triaxials.bases import _bet_, _HeightINT0, LLK, _llk_, \
51 _MAXIT, _omg_, _otherV3d_, _sqrt0, \
52 _Triaxial3Base, TriaxialError, \
53 _TriaxialsBase
54from pygeodesy.units import Degrees, Lat, Lon, Meter, Radians, Radius_
55# from pygeodesy.utily import atan2, sincos2 # from .triaxials.angles
56from pygeodesy.vector3d import Vector3d, _ALL_LAZY
58# from math import fabs, sqrt # from .fmath
59from random import random
61__all__ = _ALL_LAZY.triaxials_triaxial3
62__version__ = '26.08.06'
64_alp_ = 'alp'
65_NAN3d = Vector3d(NAN, NAN, NAN)
66_TOL = cbrt2(EPS)
67_TOL2 = _TOL**2 # cbrt(EPS)**4
68_zet_ = 'zet'
69_27_0 = 27.0
72class BetOmgAlp5Tuple(_Ang3Tuple):
73 '''5-Tuple C{(bet, omg, alp, h, llk)} with I{ellipsoidal}
74 lat- C{bet}, longitude C{omg} and azimuth C{alp}, all
75 in L{Ang}les on and height C{h} off the triaxial's
76 surface and kind C{llk} set to C{LLK.ELLIPSOIDAL}.
77 '''
78 _Names_ = (_bet_, _omg_, _alp_, _h_, _llk_)
79 _Units_ = ( Ang, Ang, _Pass, _HeightINT0, _Pass)
82class Cartesian5Tuple(_xyzh_Tuple):
83 '''5-Tuple C{(x, y, z, h, llk)} with I{cartesian} C{x}, C{y}
84 and C{z} coordinates on and height C{h} above or below the
85 triaxial's surface and kind C{llk} set to the original
86 C{LLK} or C{None}.
87 '''
88 _Names_ = _xyzh_Tuple._Names_ + (_llk_,)
89 _Units_ = (Meter, Meter, Meter, _HeightINT0, _Pass)
91 def __new__(cls, x, y, z, h=0, llk=None, **kwds): # **iteration_name
92 args = x, y, z, (h or INT0), llk
93 return _xyzh_Tuple.__new__(cls, args, **kwds)
96class _Fp2(object):
97 '''(INTERNAL) Function and derivate evaluation.
98 '''
99 def __init__(self, rs, ls, n=1):
100 # assert 0 < n <= 2
101 self._2 = n == 2
102 self._rls = tuple((p, q) for p, q in zip(rs, ls) if p)
104 def __call__(self, p):
105 # Evaluate C{f(p) = sum((rs[k] / (p + ls[k]))**n,
106 # k=0..2) - 1} and its derivative C{fp}.
107 f = _N_1_0
108 fc = fp = _0_0
109 _D = EPS_2
110 _2 = self._2
111 for g, q in self._rls:
112 q = _1_over(p + q)
113 g *= q
114 if _2:
115 g *= g
116 q += q
117 r = round(g / _D) * _D
118 f += r
119 fc += g - r
120 fp -= g * q
121 return (f + fc), fp
124class LatLonAzi5Tuple(_NamedTuple):
125 '''5-Tuple C{(lat, lon, azimuth, h, llk)} with triaxial
126 C{lat}-, C{lon}gitude and C{azimuth}, all in C{degrees},
127 height C{h} off the triaxial's surface and kind C{llk}
128 either C{LLK.GEODETIC} or C{LLK.GEODESIC_LON0}.
129 '''
130 _Names_ = (_lat_, _lon_, _azimuth_, _h_, _llk_)
131 _Units_ = ( Lat, Lon, _Pass, _HeightINT0, _Pass)
134class PhiLamZet5Tuple(_Ang3Tuple):
135 '''5-Tuple C{(phi, lam, zet, h, llk)} with triaxial lat-
136 lat- C{phi}, longitude C{lam} and azimuth C{zet}, all
137 in L{Ang}les on and height C{h} off the triaxial's
138 surface and kind C{llk} set to an C{LLK}.
139 '''
140 _Names_ = (_phi_, _lam_, _zet_, _h_, _llk_)
141 _Units_ = ( Ang, Ang, _Pass, _HeightINT0, _Pass)
143 def toLatLonAzi5(self, **name):
144 '''Return this tuple as L{LatLonAzi5Tuple} with
145 C{lat}, C{lon} and C{azimuth} in C{degrees} or
146 C{azimuth} C{INT0}.
147 '''
148 u = self # .toUnits(Error=TriaxialError)
149 n = _xkwds_get(name, name=u.name)
150 z = u.zet # None or isAng with turns
151 z = INT0 if z is None else z.degrees
152 t = u.phi.degrees0, u.lam.degrees0, z, u.h, u.llk
153 return LatLonAzi5Tuple(t, name=n or NN)
156class Triaxial3(_Triaxial3Base):
157 '''I{Ordered} triaxial ellipsoid convering between cartesian and
158 lat-/longitudes using using class L{Ang}.
160 @see: L{Triaxial<triaxials.triaxial5.Triaxial>} for details.
161 '''
162 def _cardinal2(self, v, mer, llk): # cardinaldir
163 '''(INTERNAL) Get 2-tuple C{(n, e)} at C{mer}idian.
164 '''
165 # assert isinstance(v, Vector3d) and isinstance(mer, Ang) \
166 # and isinstance(llk, LLK.__class__)
167 a2, b2, c2 = self._a2b2c23
168 if llk._X:
169 a2, c2 = c2, a2
170 v = v._roty(True) # +1
171 x, y, z = v.xyz3
172 if x or y:
173 s = (-z) / c2
174 z = x**2 / a2 + y**2 / b2
175 else:
176 y, x, _ = mer.scn3
177 s = _copysign_1_0(-z)
178 z = _0_0
179 n = Vector3d(x * s, y * s, z).unit()
180 e = v.dividedBy_(a2, b2, c2).unit() # normvec
181 e = n.cross(e).unit()
182 if llk._X:
183 e = e._roty(False) # -1
184 n = n._roty(False) # -1
185 return n, e
187 def forward(self, lat, lon, **height_unit_name):
188 '''Convert a I{geodetic} lat- and longitude to a cartesian
189 on this triaxial's surface.
191 @arg lat: Geodetic latitude (C{degrees} or B{C{unit}}).
192 @arg lon: Geodetic longitude (C{degrees} or B{C{unit}}).
193 @kwarg height_unit_name: Optional C{B{height}=0} (C{meter}),
194 scalar C{B{unit}=}L{Radians} (or L{Degrees})
195 and C{B{name}=NN} (C{str}).
197 @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with
198 C{h=B{height}} and kind C{llk=LLK.GEODETIC} or
199 C{llk=LLK.GEODETIC_LON0}.
201 @see: Methods L{Triaxial3.forwardPhiLam} and L{Triaxial3.reverse}.
202 '''
203 llk = LLK.GEODETIC_LON0 if self.Lon0 else LLK.GEODETIC
204 height_unit_name = _xkwds(height_unit_name, unit=Degrees)
205 return self.forwardPhiLam(lat, lon, llk=llk, **height_unit_name)
207 def forwardBetOmg(self, bet, omg, height=0, **unit_name): # elliptocart2
208 '''Convert an I{ellipsoidal} lat- and longitude to a cartesian
209 on this triaxial's surface.
211 @arg bet: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
212 @arg omg: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
213 @kwarg height: Height above or below this triaxial's surface (C{meter},
214 same units as this triaxial's semi-axes).
215 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar
216 C{B{unit}=}L{Radians} (or L{Degrees}).
218 @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with C{h=B{height}}
219 and kind C{llk=LLK.ELLIPSOIDAL}.
221 @see: Method L{Triaxial3.reverseBetOmg}.
222 '''
223 ct, _ = self.forwardBetOmgAlp2(bet, omg, None, height, **unit_name)
224 return ct
226 forwardBetaOmega = forwardBetOmg # for backward compatibility
228 def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
229 '''DEPRECATED on 2025.11.15, like C{Triaxial.forwardBetaOmega_}.'''
230 return self.forwardBetaOmega(Ang_(sbeta, cbeta),
231 Ang_(somega, comega), **name)
233 def forwardBetOmgAlp2(self, bet, omg, alp, height=0, **unit_name): # elliptocart2
234 '''Convert an I{ellipsoidal} lat-, longitude and heading to a
235 cartesian and a direction on this triaxial's surface.
237 @arg bet: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
238 @arg omg: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
239 @arg alp: Azimuth of the heading (C{Ang}, B{C{unit}} or C{None}).
240 @kwarg height: Height above or below this triaxial's surface (C{meter},
241 same units as this triaxial's semi-axes).
242 @kwarg unit_name: Optional C{B{name}=NN} (C{str}), scalar
243 C{B{unit}=}L{Radians} (or L{Degrees}).
245 @return: 2-Tuple C{(cartesian, direction)} with C{cartesian} a
246 L{Cartesian5Tuple}C{(x, y, z, h, llk)} with C{h=B{height}},
247 kind C{llk=LLK.ELLIPSOIDAL} and C{direction} a C{Vector3d}
248 tangent to this triaxial's surface or C{None}.
250 @see: Method L{Triaxial3.reverseBetOmgAlp}.
251 '''
252 h, llk, unit, name = _h_llk_unit_name(height, **unit_name)
253 a, b, c = self._abc3
254 if h: # Cartesian.elliptocart
255 a, b, _ = self._a2b2c23
256 h = _HeightINT0(h)
257 s = (c * _2_0 + h) * h
258 if s < 0:
259 s = -min(a, b, -s)
260 a = sqrt(a + s)
261 b = sqrt(b + s)
262 c += h
263 sb, cb = _SinCos2(bet, unit)
264 so, co = _SinCos2(omg, unit)
265 k, kp = self._k_kp
266 tx, tz = _txtz2(cb, so, k, kp)
267 ct = Cartesian5Tuple(a * co * tx,
268 b * cb * so,
269 c * sb * tz,
270 h, llk, **name)
272 if alp is None: # or h?
273 dir3d = None # _NAN3d?
274 else:
275 try:
276 sa, ca = _SinCos2(alp, unit)
277 except Exception as X:
278 raise TriaxialError(alp=alp, cause=X)
279 a, b, c = self._abc3
280 if k and kp and not (cb or so):
281 c2s2_b = (ca - sa) * (ca + sa) / b
282 dir3d = Vector3d(a * k * co * c2s2_b
283 -co * sb * ca * sa * _2_0,
284 c * kp * sb * c2s2_b)
285 else:
286 if not tx: # at oblate pole tx -> |cos(bet)|
287 c = _flipsign(co, cb)
288 n = Vector3d(-c * sb,
289 -so * sb, _0_0)
290 e = Vector3d(-so, c, _0_0)
291 elif not tz: # at prolate pole tz -> |sin(omg)|
292 s = _flipsign(sb, so)
293 n = Vector3d(_0_0, -s, cb)
294 e = Vector3d(_0_0, cb * co, co * s)
295 else:
296 k2, kp2 = self._k2_kp2
297 n = Vector3d(-a * k2 * sb * cb * co / tx,
298 -b * sb * so, c * cb * tz)
299 e = Vector3d(-a * tx * so, b * cb * co,
300 c * kp2 * sb * so * co / tz)
301 dir3d = n.unit().times(ca) # NAN
302 dir3d += e.unit().times(sa) # NAN
303 dir3d.name = ct.name
304 return ct, dir3d
306 def forwardCartesian(self, x_ct, y=None, z=None, normal=True, **eps_llk_name):
307 '''Project any cartesian I{onto} this triaxial's surface.
309 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
310 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
311 or L{Vector4Tuple}).
312 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
313 ignored otherwise.
314 @kwarg z: Z component (C{scalar}), like B{C{y}}.
315 @kwarg normal: If C{True}, the projection is C{perpendicular} to the surface,
316 otherwise C{radial} to the center of this triaxial (C{bool}).
317 @kwarg eps_llk_name: Root finder tolerance C{B{eps}=EPS}, kind C{B{llk}=None}
318 overriding C{B{x_ct}.llk} and optional C{B{name}="height4"} (C{str}).
320 @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)}.
322 @see: Method L{Triaxial3.reverseCartesian} to reverse the projection and
323 function L{height4<triaxials.triaxial5.height4>} for more details.
324 '''
325 llk, kwds = _xkwds_pop2(eps_llk_name, llk=_xattr(x_ct, llk=None))
326 h = self.sideOf(x_ct, y, z)
327 if h: # signed, square
328 v = self.height4(x_ct, y, z, normal=normal, **kwds)
329 h = v.h
330 else: # on the surface
331 v = _otherV3d_(x_ct, y, z)
332 n = _xkwds_get(kwds, name=NN)
333 return Cartesian5Tuple(v.x, v.y, v.z, h, llk, iteration=v.iteration, name=n)
335 def forwardLatLon(self, lat, lon, height=0, llk=LLK.ELLIPSOIDAL, **unit_name): # anytocart2
336 '''Convert any lat-/longitude kind to a cartesian on this triaxial's surface.
338 @arg lat: Latitude (C{Ang} or B{C{unit}}).
339 @arg lon: Longitude (C{Ang} or B{C{unit}}).
340 @kwarg height: Height above or below this triaxial's surface (C{meter}, same
341 units as this triaxial's semi-axes).
342 @kwarg llk: The kind (an L{LLK}).
343 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Degrees}
344 (or L{Radians}).
346 @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=B{height}} and
347 kind C{llk=B{llk}}.
349 @see: Method L{Triaxial3.reverseLatLon}.
350 '''
351 _fwd = self.forwardBetOmg if llk in LLK._NOIDAL else \
352 self.forwardPhiLam # PYCHOK OK
353 return _fwd(lat, lon, height=height, llk=llk, **_xkwds(unit_name, unit=Degrees))
355 def forwardPhiLam(self, phi, lam, height=0, llk=LLK.GEODETIC, **unit_name): # generictocart2
356 '''Convert any lat-/longitude kind to a cartesian on this triaxial's surface.
358 @arg phi: Latitude (C{Ang} or B{C{unit}}).
359 @arg lam: Longitude (C{Ang} or B{C{unit}}).
360 @kwarg height: Height above or below this triaxial's surface (C{meter}, same
361 units as this triaxial's semi-axes).
362 @kwarg llk: The kind (an L{LLK}).
363 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Radians}
364 (or L{Degrees}).
366 @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=B{height}} and
367 kind C{llk=B{llk}}.
369 @note: Longitude C{B{lam} -= Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
371 @see: Method L{Triaxial3.reverseLatLon}.
372 '''
373 ct, _ = self.forwardPhiLamZet2(phi, lam, None, height=height, llk=llk, **unit_name)
374 return ct
376 def forwardPhiLamZet2(self, phi, lam, zet, height=0, llk=LLK.GEODETIC, **unit_name): # generictocart2
377 '''Convert a lat-, longitude and heading to a cartesian and a direction
378 on this triaxial's surface.
380 @arg phi: Latitude (C{Ang} or B{C{unit}}).
381 @arg lam: Longitude (C{Ang} or B{C{unit}}).
382 @arg zet: Azimuth of the heading (C{Ang}, B{C{unit}} or C{None}).
383 @kwarg height: Height above or below this triaxial's surface (C{meter},
384 same units as this triaxial's semi-axes).
385 @kwarg llk: The kind (an L{LLK}).
386 @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar
387 C{B{unit}=}L{Radians} (or L{Degrees}).
389 @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
390 L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=0},
391 kind C{llk=B{llk}} and C{direction}, a C{Vector3d} on and
392 tangent to this triaxial's surface.
394 @note: Longitude C{B{lam} -= Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
396 @see: Method L{Triaxial3.reversePhiLamZet}.
397 '''
398 unit, name = _xkwds_pop2(unit_name, unit=Radians)
399 try:
400 sa, ca = _SinCos2(phi, unit)
401 if llk is LLK.GEODETIC_LON0 and self.Lon0:
402 lam = Ang.fromScalar(lam, unit=unit)
403 lam -= self.Lon0
404 sb, cb = _SinCos2(lam, unit)
405 except Exception as X:
406 raise TriaxialError(phi=phi, lam=lam, llk=llk, cause=X)
407 v, _, llk, name = _v_h_llk_name(ca * cb, ca * sb, sa, llk=llk, **name)
408 if llk and llk._X:
409 v = v._roty(False) # -1
410 d, t = _d_t(self, llk)
411 if t:
412 v = v.times_(*t)
413 if d:
414 d = v.dividedBy_(*self._abc3).length
415 v = v.dividedBy(d)
417 h = _HeightINT0(height)
418 if h: # cart2cart
419 v, h = self._toHeight2(v, h)
420 ct = Cartesian5Tuple(v.x, v.y, v.z, h, llk, **name)
422 if zet is None:
423 dir3d = None
424 else:
425 try:
426 s, c = _SinCos2(zet, unit)
427 except Exception as X:
428 raise TriaxialError(zet=zet, cause=X)
429 n, e = self._meridian2(v, lam, llk)
430 dir3d = n.times(c)
431 dir3d += e.times(s)
432 dir3d.name = ct.name
433 return ct, dir3d
435 def _meridian(self, lam, llk):
436 '''(INTERNAL) Get the meridian plane's at C{lam}.
437 '''
438 _, t = _d_t(self, llk)
439 if t:
440 a, b, c = t
441 lam = lam.mod((c if llk._X else a) / b)
442 return lam
444 def _meridian2(self, v, lam, llk):
445 '''(INTERNAL) Get 2-tuple C{(n, e)} at C{lam} meridian.
446 '''
447 mer = self._meridian(lam, llk)
448 return self._cardinal2(v, mer, llk)
450 def normed2(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # Ellipsoid3.Norm
451 '''Scale a cartesian and direction to this triaxial's surface.
453 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
454 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
455 or L{Vector4Tuple}).
456 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
457 ignored otherwise.
458 @kwarg z: Z component (C{scalar}), like B{C{y}}.
459 @kwarg dir3d: The direction (C{Vector3d} or C{None}).
460 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
461 overriding C{B{x_ct}.llk}.
463 @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
464 L{Cartesian5Tuple}C{(x, y, z, h, llk)} and C{direction}, a
465 C{Vector3d} tangent to this triaxial's surface or C{None}
466 iff C{B{dir3d} is None}.
467 '''
468 v, h, llk, name = _v_h_llk_name(x_ct, y, z, **llk_name)
470 u = v.dividedBy_(*self._abc3).length
471 r = v.dividedBy(u) if u else _NAN3d
472 ct = Cartesian5Tuple(r.x, r.y, r.z, h, llk, **name)
474 if isinstance(dir3d, Vector3d):
475 if u: # and r is not _NAN3d
476 u = r.dividedBy_(*self._a2b2c23)
477 d = dir3d.dot(u)
478 if _isfinite(d) and u.length2:
479 u = u.times(d / u.length2)
480 dir3d = dir3d.minus(u).unit() # NAN
481 else:
482 dir3d = _NAN3d
483 else:
484 dir3d = _NAN3d
485 dir3d.name = ct.name
486 return ct, dir3d
488 def reverse(self, x_ct, y=None, z=None, **name):
489 '''Convert a cartesian I{on this triaxial's surface} to I{geodetic} lat-
490 and longitude.
492 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
493 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
494 or L{Vector4Tuple}).
495 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
496 ignored otherwise.
497 @kwarg z: Z component (C{scalar}), like B{C{y}}.
498 @kwarg name: Optional C{B{name}=NN} (C{str}).
500 @return: A L{LatLonAzi5Tuple}C{(lat, lon, azimuth, h, llk)} with C{azimuth}
501 usually C{INT0}, height C{h} off this triaxial's surface in C{meter}
502 or C{INT0} and C{llk} either C{LLK.GEODETIC} or C{LLK.GEODETIC_LON0}.
503 '''
504 llk = LLK.GEODETIC_LON0 if self.Lon0 else LLK.GEODETIC
505 r = self.reversePhiLamZet(x_ct, y, z, llk=llk, **name)
506 return r.toLatLonAzi5(name=r.name)
508 def reverseBetOmg(self, x_ct, y=None, z=None, **llk_name): # Cartesian3.carttoellip
509 '''Convert a cartesian I{on this triaxial's surface} to an I{ellipsoidal}
510 lat-/longitude.
512 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
513 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
514 or L{Vector4Tuple}).
515 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
516 ignored otherwise.
517 @kwarg z: Z component (C{scalar}), like B{C{y}}.
518 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
519 overriding C{B{x_ct}.llk}.
521 @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None}
522 and C{llk=LLK.ELLIPSOIDAL}.
523 '''
524 v, _, llk, name = _v_h_llk_name_NOIDAL(x_ct, y, z, **llk_name)
526 _, y2, z2 = rs = v.x2y2z23
527 l0, l1, _ = ls = self._lcc23
528 qmax = fsumf_(*rs)
529 qmin = q = max(z2, y2 + z2 - l1, qmax - l0)
530 _fp2 = _Fp2(rs, ls, n=1)
531 f, _ = _fp2(q)
532 if f > _TOL2: # neg means convergence
533 q = max(qmin, min(qmax, _cubic(rs, qmax, l0, l1)))
534 f, fp = _fp2(q)
535 if fabs(f) > _TOL2:
536 q = max(qmin, q - _over(f, fp))
537 q = _solve(_fp2, q, self.b2)
539 a, b, c = map1(_sqrt0, l0 + q, l1 + q, q) # axes (a, b, c)
540 h = (c - self.c) or INT0
541 bet, omg, _ = self._reverseBetOmgAlp3(v, None, a, b, c, **name)
542 return BetOmgAlp5Tuple(bet, omg, None, h, llk, **name)
544 reverseBetaOmega = reverseBetOmg # for backward compatibility
546 def reverseBetOmgAlp(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # Ellipsoid3.cart2toellip[int]
547 '''Convert a cartesian and direction I{on this triaxial's surface} to an
548 I{ellipsoidal} lat-, longitude and heading.
550 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
551 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
552 or L{Vector4Tuple}).
553 @kwarg y: Y component (C{scalar}), required if B{C{x_ct}} is C{scalar},
554 ignored otherwise.
555 @kwarg z: Z component (C{scalar}), like B{C{y}}.
556 @kwarg dir3d: The direction (C{Vector3d} or C{None}).
557 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
558 overriding C{B{x_ct}.llk}.
560 @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None}
561 if C{B{dir3d} is None} and C{llk=LLK.ELLIPSOIDAL}.
562 '''
563 v, h, llk, name = _v_h_llk_name_NOIDAL(x_ct, y, z, **llk_name)
564 bet, omg, alp = self._reverseBetOmgAlp3(v, dir3d, **name)
565 return BetOmgAlp5Tuple(bet, omg, alp, h, llk, **name)
567 def _reverseBetOmgAlp3(self, v, dir3d, *a_b_c, **name): # cart2toellipint
568 '''(INTERNAL) Helper for methods C{reverseBetOmg/-Alp}.
569 '''
570 k, kp = self._k_kp
571 k2, kp2 = self._k2_kp2
572 a, b, c = a_b_c or self._abc3
573 V = v.dividedBy_(a, b, c)
574 X, E, Z = V.xyz3 # Xi, Eta, Zeta
575 h = fabs(E * k * kp * _2_0)
576 if v.y or fabs(v.x) != a * kp2 or \
577 fabs(v.z) != c * k2:
578 g = fdot(V.x2y2z23, k2, (k2 - kp2), -kp2)
579 h = hypot(g, h)
580 else:
581 g = _0_0
582 if h < EPS02:
583 so = cb = _0_0
584 elif g < 0:
585 h = _over(sqrt((h - g) * _0_5), kp)
586 so = _copysign(h, E)
587 cb = fabs(_over(E, so))
588 else:
589 cb = _over(sqrt((h + g) * _0_5), k)
590 so = _over(E, cb)
591 tx, tz = _txtz2(cb, so, k, kp)
592 sb = (Z / tz) if tz else _N_1_0
593 co = (X / tx) if tx else _1_0
594 bet = Ang_(sb, cb, **name)
595 omg = Ang_(so, co, **name)
597 if isinstance(dir3d, Vector3d): # cart2toellip(bet, omg, V) -> alp
598 if cb or so or not (tx and tz): # not umbilical
599 if not tx:
600 n = Vector3d(-co, -so, tx) * sb
601 e = Vector3d(-so, co, tx)
602 elif not tz:
603 n = Vector3d(tz, -sb, cb)
604 e = Vector3d(tz, cb, sb) * co
605 else:
606 n = Vector3d(-a * sb * k2 * cb * co / tx,
607 -b * sb * so, c * cb * tz)
608 e = Vector3d(-a * so * tx, b * cb * co,
609 c * so * kp2 * sb * co / tz)
610 sa = dir3d.dot(e.unit()) # NAN
611 ca = dir3d.dot(n.unit()) # NAN
612 else: # at umbilicial PYCHOK no cover
613 x, z = norm2(co * tx / a, sb * tz / c) # _MODS.karney._norm2
614 v = dir3d * (sb * co) # dir3d.times(sb * co)
615 s2a = -v.y
616 c2a = fdot(v, z, 0, -x) # v.x * z - v.z * x
617 sa = ca = -sb
618 sa *= _copysign(_1_0 - c2a, s2a) if c2a < 0 else s2a
619 ca *= fabs(s2a) if c2a < 0 else (c2a + _1_0)
620 alp = Ang_(sa, ca, **name)
621 elif dir3d is None:
622 alp = None # Ang.NAN(**name)
623 else:
624 raise TriaxialError(dir3d=dir3d)
625 return bet, omg, alp
627 def reverseCartesian(self, x_ct, y=None, z=None, height=0, normal=True, **llk_name): # cart2tocart
628 '''"Unproject" a cartesian I{off} this triaxial's surface.
630 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
631 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
632 or L{Vector4Tuple}).
633 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
634 ignored otherwise.
635 @kwarg z: Z component (C{scalar}), like B{C{y}}.
636 @kwarg height: Height above or below this triaxial's surface (C{meter},
637 same units as this triaxial's semi-axes).
638 @kwarg normal: If C{True}, B{C{height}} is C{perpendicular} to the surface,
639 otherwise C{radial} to the center of this triaxial (C{bool}).
640 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}}
641 overriding C{B{x_ct}.llk}.
643 @return: L{Cartesian5Tuple}C{(x, y, z, h, llk)}.
645 @raise TrialError: Cartesian B{C{x_ct}} or C{(x, y, z)} not on this
646 triaxial's surface.
648 @see: Methods L{Triaxial3.forwardCartesian}.
649 '''
650 kwds = _xkwds(llk_name, llk=_xattr(x_ct, llk=None))
651 v, _, llk, name = _v_h_llk_name(x_ct, y, z, **kwds)
652 _ = self._sideOn(v)
653 h = _HeightINT0(height)
654 if h:
655 v, h = self._toHeight2(v, h, normal)
656 return Cartesian5Tuple(v.x, v.y, v.z, h, llk, **name)
658 def reverseLatLon(self, x_ct, y=None, z=None, **llk_name): # cart2toany
659 '''Convert a cartesian I{on this triaxial's surface} to a lat-/longitude.
661 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
662 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
663 or L{Vector4Tuple}).
664 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
665 ignored otherwise.
666 @kwarg z: Z component (C{scalar}), like B{C{y}}.
667 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
668 overriding C{B{x_ct}.llk}.
670 @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None} or
671 a L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
673 @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
674 '''
675 llk, name = _xkwds_pop2(llk_name, llk=_xattr(x_ct,
676 llk=LLK.ELLIPSOIDAL))
677 _rev = self.reverseBetOmg if llk in LLK._NOIDAL else \
678 self.reversePhiLam # PYCHOK OK
679 return _rev(x_ct, y, z, llk=llk, **name)
681 def reversePhiLam(self, x_ct, y=None, z=None, **llk_name): # cart2togeneric
682 '''Convert a cartesian I{on this triaxial's surface} to lat-/longitude.
684 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
685 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
686 or L{Vector4Tuple}).
687 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
688 ignored otherwise.
689 @kwarg z: Z component (C{scalar}), like B{C{y}}.
690 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
691 overriding C{B{x_ct}.llk}.
693 @return: A L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
695 @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
696 '''
697 return self.reversePhiLamZet(x_ct, y, z, **llk_name)
699 def reversePhiLamZet(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # cart2togeneric(R, V, ...
700 '''Convert a cartesian and direction to lat-, longitude and azimuth.
702 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
703 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
704 or L{Vector4Tuple}).
705 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
706 ignored otherwise.
707 @kwarg z: Z component (C{scalar}), like B{C{y}}.
708 @kwarg dir3d: Optional direction (C{Vector3d} or C{None}).
709 @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
710 overriding C{B{x_ct}.llk}.
712 @return: A L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}
713 if C{B{dir3d} is None}.
715 @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
716 '''
717 ct = self.toTriaxial5(x_ct, y, z, h=NAN, **llk_name)
718 v, h, llk, name = _v_h_llk_name(ct)
719 _, t = _d_t(self, llk)
720 if t:
721 v = v.dividedBy_(*t)
722 if llk._X:
723 v = v._roty(True) # +1
724 phi = Ang_(v.z, hypot(v.x, v.y), **name)
725 lam = Ang_(v.y, v.x, **name) # Ang(0, 0) -> 0
727 if dir3d is None:
728 zet = None
729 elif isinstance(dir3d, Vector3d):
730 n, e = self._meridian2(v, lam, llk)
731 zet = Ang_(dir3d.dot(e),
732 dir3d.dot(n), **name)
733 else:
734 raise TriaxialError(dir3d=dir3d)
735 if llk is LLK.GEODETIC_LON0 and self.Lon0:
736 lam += self.Lon0
737 return PhiLamZet5Tuple(phi, lam, zet, h, llk, **name)
739 def random2(self, llk=LLK.ELLIPSOIDAL, both=False, _rand=random):
740 '''Return a random cartesian with/out direction on this triaxial's surface.
742 @kwarg llk: The kind (an L{LLK}).
743 @kwarg both: If C{True}, generate a random direction (C{bool}).
745 @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
746 L{Cartesian5Tuple}C{(x, y, z, h, llk)} and C{direction}, a
747 C{Vector3d} tangent to this triaxial's surface or C{None}
748 iff C{B{both} is False}.
749 '''
750 for _ in range(_MAXIT):
751 for _ in range(_MAXIT):
752 v = Vector3d(_rand(), _rand(), _rand())
753 u = v.length
754 if u and _isfinite(u):
755 break
756 else:
757 raise TriaxialError(Fmt.no_convergence(u))
758 v = v.dividedBy(u).times_(*self._abc3)
759 q = v.dividedBy_(*self._a2b2c23).length * self.c
760 if 0 < q <= _1_0: # _uni(q) < q:
761 break
762 else:
763 raise TriaxialError(Fmt.no_convergence(q))
764 ct = Cartesian5Tuple(v.x, v.y, v.z, INT0, llk, name__=self.random2)
765 v = None
766 if both:
767 for _ in range(_MAXIT):
768 v = Vector3d(_rand(), _rand(), _rand())
769 u = v.length
770 if u:
771 u = v.dividedBy(u).dividedBy_(*self._a2b2c23)
772 d = v.dot(u) / u.length2
773 v = v.minus(u.times(d))
774 u = v.length # normvec
775 if u and _isfinite(u):
776 v = v.dividedBy(u)
777 break
778 else:
779 raise TriaxialError(Fmt.no_convergence(u))
780 v.name = ct.name
781 return ct, v
783 def _toHeight2(self, v, h, normal=True):
784 '''(INTERNAL) Move cartesian C{Vector3d B{v}} to height C{h}.
785 '''
786 n = v.dividedBy_(*self._a2b2c23) if normal else v
787 if n.length > EPS02:
788 h = max(h, -self.c)
789 v = v.plus(n.times(h / n.length))
790 return v, h
792 def toOther(self, lat, lon, llk1=LLK.GEODETIC, llk2=LLK.GEODETIC, **unit_name): # anytoany
793 '''Convert one lat-/longitude kind to an other.
795 @arg lat: Latitude (C{Ang} or B{C{unit}}).
796 @arg lon: Longitude (C{Ang} or B{C{unit}}).
797 @kwarg llk1: The given kind (an L{LLK}).
798 @kwarg llk2: The result kind (an L{LLK}).
799 @kwarg name: Optional C{B{name}=NN} (C{str}).
801 @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None} or
802 a L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
804 @see: Methods L{Triaxial3.forwardLatLon} and -L{reverseLatLon}.
805 '''
806 ct = self.forwardLatLon(lat, lon, llk=llk1, **unit_name)
807 r = self.reverseLatLon(ct, llk=llk2, name=ct.name)
808# a, b = r[:2]
809# if not isAng(lat):
810# a = float(a)
811# if not isAng(lon):
812# b = float(b)
813# if (a, b) =! r[:2]:
814# r = r._dup(a, b)
815 return r
817 def toTriaxial5(self, x_ct, y=None, z=None, **triaxial_h_llk_name): # carttocart2
818 '''Find the closest cartesian on this or on another triaxial's surface.
820 @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
821 any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
822 or L{Vector4Tuple}).
823 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
824 ignored otherwise.
825 @kwarg z: Z component (C{scalar}), like B{C{y}}.
826 @kwarg triaxial_llk_name: Optional C{B{triaxial}=self} (C{Triaxial3}),
827 C{B{name}=NN} (C{str}), height C{B{h}} and kind C{B{llk}}
828 overriding C{B{x_ct}.h} respectively C{B{x_ct}.llk}.
830 @return: L{Cartesian5Tuple}C{(x, y, z, h, llk)}
832 @raise TriaxialError: If C{B{triaxial}} is not a L{Triaxial3}.
834 @see: Functions L{hartzell4<triaxials.triaxial5.hartzell4>} and
835 L{height4<triaxials.triaxial5.height4>} and methods.
836 '''
837 T, name = _xkwds_pop2(triaxial_h_llk_name, triaxial=self)
838 if not isinstance(T, Triaxial3):
839 raise TriaxialError(triaxial=T, x=x_ct, y=y, z=z)
841 v, h, llk, name = _v_h_llk_name(x_ct, y, z, **name)
842 if h or T is not self:
843 l0, l1, _ = ls = T._lcc23
844 r = Vector3d(*T._ztol(v))
845 s = r.times_(*T._abc3)
846 p = max(fabs(s.z), hypot(s.x, s.y) - l1, s.length - l0)
847 h = _solve(_Fp2(s.xyz3, ls, n=2), p, T.b2)
848 v = r.times_(*(_over(n, h + l_) for n, l_ in zip(T._a2b2c23, ls)))
849 if not h: # handle h == 0, v.y indeterminate
850 x = v.x if l0 else r.x # sphere
851 y = v.y if l1 else r.y # sphere or prolate
852 s = _1_0 - hypot2(x / T.a, y / T.b)
853 z = (sqrt(s) * _copysign(T.c, r.z)) if s > EPS02 else _0_0
854 v = Vector3d(x, y, z)
855 h -= T.c2
856 if h and v.length:
857 h *= v.dividedBy_(*T._a2b2c23).length
858 return Cartesian5Tuple(v.x, v.y, v.z, (h or INT0), llk, **name)
860 @Property_RO
861 def _ZTOL(self):
862 return self.b * (EPS / 8)
864 def _ztol(self, v):
865 for x in v.xyz3:
866 yield x if fabs(x) > self._ZTOL else _copysign_0_0(x)
869class Triaxial3B(Triaxial3):
870 '''Triaxial ellipsoid specified by its middle semi-axis and shape.
872 @see: L{Triaxial3} for more information.
873 '''
874 def __init__(self, b, e2=_0_0, k2=_1_0, kp2=_0_0, **name):
875 '''New, L{Triaxial3B} instance.
877 @see: L{Triaxial<triaxials.triaxial5.Triaxial>} for details.
878 '''
879 self._init_abc3_e2_k2_kp2(Radius_(b=b), e2, k2, kp2, **name)
882def _cubic(rs, rt, l0, l1): # Cartesian3.cubic
883 '''(INTERNaL) Solve sum(R2[i]/(z + lq2[i]), i=0,1,2) - 1 = 0
884 with lq2[2] = 0. This has three real roots with just one
885 satisifying q >= 0.
886 '''
887 a = l0 + l1
888 b = l0 * l1
889 c = -b * rs[2] # z2
890 # cubic equation z**3 + a*z**2 + b*z + c = 0
891 b -= fdot(rs, l1, l0, a)
892 a -= rt
893 _r = b > 0
894 if _r:
895 a, b = b, a
896 c = _1_over(c)
897 a *= c
898 b *= c
899 # see https://dlmf.nist.gov/1.11#iii
900 p = (b * _3_0 - a**2) / _3_0
901 t = -p / _3_0 # A / 4
902 if t > 0:
903 q = (a**3 * _2_0 - a * b * _9_0 + c * _27_0) / _27_0
904 # switch to https://dlmf.nist.gov/4.43
905 s = -q**2 - p**3 * _4_0 / _27_0
906 p = sqrt(s) if s > 0 else _0_0
907 s, c = sincos2(atan2(q, p) / _3_0) # alp
908 t = (c * _SQRT3 - s) * sqrt(t)
909 else:
910 t = _0_0
911 t -= a / _3_0
912 return _1_over(t) if _r else t
915def _d_t(triax, llk):
916 '''(INTERNAL) Helper.
917 '''
918 if llk in LLK._CENTRICS:
919 d_t = True, None
920 elif llk in LLK._DETICS:
921 d_t = True, triax._a2b2c23
922 elif llk in LLK._METRICS:
923 d_t = False, triax._abc3
924 else:
925 raise TriaxialError(llk=llk)
926 return d_t
929def _h_llk_unit_name(height, h=None, llk=LLK.ELLIPSOIDAL, unit=Radians, **name):
930 '''(INTERNAL) Helper, C{h} for backward compatibility.
931 '''
932 if llk is None:
933 llk = LLK.ELLIPSOIDAL
934 elif llk not in LLK._NOIDAL: # or llk._X
935 raise TriaxialError(llk=llk)
936 if h is None:
937 h = height
938 return h, llk, unit, name
941def _solve(_fp2, p, pscale, **n):
942 '''(INTERNAL) Solve _fp2(p) = 0
943 '''
944 dt = _N_1_0
945 pt = _EPSqrt * pscale
946 _P2 = Fsum(p).fsum2_
947 for i in range(_MAXIT):
948 fv, fp = _fp2(p, **n)
949 if not (fv > _TOL2):
950 break
951 p, d = _P2(-fv / fp) # d is positive
952 if i and d <= dt and (fv <= EPS8 or
953 d <= (max(pt, p) * _TOL)):
954 break
955 dt = d
956 else:
957 t = Fmt.no_convergence(d, min(dt, pt))
958 raise TriaxialError(_fp2.__name__, p, txt=t)
959 return p
962def _txtz2(cb, so, k, kp):
963 '''(INTERNAL) Helper.
964 '''
965 return hypot(cb * k, kp), hypot(k, so * kp)
968def _v_h_llk_name(x_ct, y=None, z=None, **h_llk_name):
969 '''(INTERNAL) Helper.
970 '''
971 if y is z is None and isinstance(x_ct, Cartesian5Tuple):
973 def _v_h_llk_name(h=x_ct.h, llk=x_ct.llk, **name):
974 v = Vector3d(*x_ct.xyz3, **name)
975 return v, h, llk, name
976 else:
977 def _v_h_llk_name(h=INT0, llk=None, **name): # PYCHOK redef
978 v = _otherV3d_(x_ct, y, z)
979 return v, h, llk, name
981 return _v_h_llk_name(**h_llk_name)
984def _v_h_llk_name_NOIDAL(x_ct, y, z, **h_llk_name):
985 '''(INTERNAL) Helper for methods C{reverseBetOmg} and C{-Alp}.
986 '''
987 v, h, llk, name = _v_h_llk_name(x_ct, y, z, **h_llk_name)
988 if h or llk not in LLK._NOIDAL: # or llk._X
989 kwds = dict(x_ct=x_ct) if y is z is None else \
990 dict(x=x_ct, y=y, z=z)
991 raise TriaxialError(h=h, llk=llk, **kwds)
992 return v, h, (LLK.ELLIPSOIDAL if llk is None else llk), name
995class Triaxial3s(_TriaxialsBase):
996 '''(INTERNAL) L{Triaxial3} registry, I{must} be a sub-class
997 to accommodate the L{_LazyNamedEnumItem} properties.
998 '''
999 _Triaxial = Triaxial3
1001Triaxial3s = Triaxial3s(Triaxial3, Triaxial3B) # PYCHOK singleton
1002'''Some pre-defined L{Triaxial3}s, like L{Triaxials<triaxials.triaxial5.Triaxials>}.'''
1003Triaxial3s._assert()
1005if __name__ == _DMAIN_:
1006 # __doc__ of this file, force all into registry
1007 from pygeodesy.internals import _pregistry
1008 _pregistry(Triaxial3s)
1011# **) MIT License
1012#
1013# Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved.
1014#
1015# Permission is hereby granted, free of charge, to any person obtaining a
1016# copy of this software and associated documentation files (the "Software"),
1017# to deal in the Software without restriction, including without limitation
1018# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1019# and/or sell copies of the Software, and to permit persons to whom the
1020# Software is furnished to do so, subject to the following conditions:
1021#
1022# The above copyright notice and this permission notice shall be included
1023# in all copies or substantial portions of the Software.
1024#
1025# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1026# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1027# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1028# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1029# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1030# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1031# OTHER DEALINGS IN THE SOFTWARE.