pygeodesy 25.10.10__py2.py3-none-any.whl → 25.12.12__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. pygeodesy/__init__.py +25 -12
  2. pygeodesy/__main__.py +1 -1
  3. pygeodesy/albers.py +1 -1
  4. pygeodesy/angles.py +960 -0
  5. pygeodesy/auxilats/_CX_4.py +1 -1
  6. pygeodesy/auxilats/_CX_6.py +1 -1
  7. pygeodesy/auxilats/_CX_8.py +1 -1
  8. pygeodesy/auxilats/_CX_Rs.py +1 -1
  9. pygeodesy/auxilats/__init__.py +2 -2
  10. pygeodesy/auxilats/__main__.py +1 -1
  11. pygeodesy/auxilats/auxAngle.py +7 -8
  12. pygeodesy/auxilats/auxDLat.py +1 -1
  13. pygeodesy/auxilats/auxDST.py +1 -1
  14. pygeodesy/auxilats/auxLat.py +1 -1
  15. pygeodesy/auxilats/auxily.py +1 -1
  16. pygeodesy/azimuthal.py +6 -5
  17. pygeodesy/basics.py +14 -10
  18. pygeodesy/booleans.py +8 -33
  19. pygeodesy/cartesianBase.py +7 -7
  20. pygeodesy/clipy.py +17 -23
  21. pygeodesy/constants.py +86 -63
  22. pygeodesy/css.py +1 -1
  23. pygeodesy/datums.py +1 -1
  24. pygeodesy/deprecated/__init__.py +2 -2
  25. pygeodesy/deprecated/bases.py +1 -1
  26. pygeodesy/deprecated/classes.py +32 -2
  27. pygeodesy/deprecated/consterns.py +1 -1
  28. pygeodesy/deprecated/datum.py +1 -1
  29. pygeodesy/deprecated/functions.py +1 -1
  30. pygeodesy/deprecated/nvector.py +1 -1
  31. pygeodesy/deprecated/rhumbBase.py +1 -1
  32. pygeodesy/deprecated/rhumbaux.py +1 -1
  33. pygeodesy/deprecated/rhumbsolve.py +1 -1
  34. pygeodesy/deprecated/rhumbx.py +1 -1
  35. pygeodesy/dms.py +1 -1
  36. pygeodesy/ecef.py +1 -1
  37. pygeodesy/ecefLocals.py +1 -1
  38. pygeodesy/elevations.py +1 -1
  39. pygeodesy/ellipsoidalBase.py +1 -1
  40. pygeodesy/ellipsoidalBaseDI.py +1 -1
  41. pygeodesy/ellipsoidalExact.py +1 -1
  42. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  43. pygeodesy/ellipsoidalKarney.py +1 -1
  44. pygeodesy/ellipsoidalNvector.py +1 -1
  45. pygeodesy/ellipsoidalVincenty.py +1 -1
  46. pygeodesy/ellipsoids.py +7 -6
  47. pygeodesy/elliptic.py +1 -1
  48. pygeodesy/epsg.py +1 -1
  49. pygeodesy/errors.py +8 -4
  50. pygeodesy/etm.py +1 -1
  51. pygeodesy/fmath.py +15 -8
  52. pygeodesy/formy.py +107 -5
  53. pygeodesy/frechet.py +1 -1
  54. pygeodesy/fstats.py +1 -1
  55. pygeodesy/fsums.py +1 -1
  56. pygeodesy/gars.py +1 -1
  57. pygeodesy/geod3solve.py +488 -0
  58. pygeodesy/geodesici.py +4 -4
  59. pygeodesy/geodesicw.py +1 -1
  60. pygeodesy/geodesicx/_C4_24.py +1 -1
  61. pygeodesy/geodesicx/_C4_27.py +1 -1
  62. pygeodesy/geodesicx/_C4_30.py +1 -1
  63. pygeodesy/geodesicx/__init__.py +1 -1
  64. pygeodesy/geodesicx/__main__.py +1 -1
  65. pygeodesy/geodesicx/gx.py +1 -1
  66. pygeodesy/geodesicx/gxarea.py +1 -1
  67. pygeodesy/geodesicx/gxbases.py +1 -1
  68. pygeodesy/geodesicx/gxline.py +1 -1
  69. pygeodesy/geodsolve.py +70 -102
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +1 -1
  72. pygeodesy/hausdorff.py +1 -1
  73. pygeodesy/heights.py +1 -1
  74. pygeodesy/internals.py +3 -3
  75. pygeodesy/interns.py +3 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +132 -116
  78. pygeodesy/ktm.py +1 -1
  79. pygeodesy/latlonBase.py +1 -1
  80. pygeodesy/lazily.py +25 -13
  81. pygeodesy/lcc.py +1 -1
  82. pygeodesy/ltp.py +1 -1
  83. pygeodesy/ltpTuples.py +1 -1
  84. pygeodesy/mgrs.py +3 -3
  85. pygeodesy/named.py +14 -9
  86. pygeodesy/namedTuples.py +1 -1
  87. pygeodesy/nvectorBase.py +1 -1
  88. pygeodesy/osgr.py +1 -1
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +1 -1
  91. pygeodesy/resections.py +1 -1
  92. pygeodesy/rhumb/__init__.py +8 -6
  93. pygeodesy/rhumb/aux_.py +1 -1
  94. pygeodesy/rhumb/bases.py +1 -1
  95. pygeodesy/rhumb/ekx.py +1 -1
  96. pygeodesy/rhumb/solve.py +91 -84
  97. pygeodesy/simplify.py +1 -1
  98. pygeodesy/solveBase.py +72 -49
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +1 -1
  101. pygeodesy/sphericalTrigonometry.py +1 -1
  102. pygeodesy/streprs.py +6 -4
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials/__init__.py +70 -0
  105. pygeodesy/triaxials/bases.py +935 -0
  106. pygeodesy/triaxials/conformal3.py +617 -0
  107. pygeodesy/triaxials/triaxial3.py +969 -0
  108. pygeodesy/triaxials/triaxial5.py +1220 -0
  109. pygeodesy/units.py +6 -1
  110. pygeodesy/unitsBase.py +1 -1
  111. pygeodesy/ups.py +2 -3
  112. pygeodesy/utily.py +19 -15
  113. pygeodesy/utm.py +1 -1
  114. pygeodesy/utmups.py +1 -1
  115. pygeodesy/utmupsBase.py +1 -1
  116. pygeodesy/vector2d.py +2 -2
  117. pygeodesy/vector3d.py +1 -1
  118. pygeodesy/vector3dBase.py +195 -51
  119. pygeodesy/webmercator.py +1 -1
  120. pygeodesy/wgrs.py +1 -1
  121. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/METADATA +13 -13
  122. pygeodesy-25.12.12.dist-info/RECORD +125 -0
  123. pygeodesy/triaxials.py +0 -1566
  124. pygeodesy-25.10.10.dist-info/RECORD +0 -119
  125. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/WHEEL +0 -0
  126. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,969 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''I{Ordered} triaxial ellipsoid classes L{Triaxial3} and L{Triaxial3B} for conversion between
5
+ variuos lat-/longitudal and cartesian coordinates on a triaxial ellipsoid using L{Ang}, L{Deg},
6
+ L{Rad} lat-, longitude and heading angles.
7
+
8
+ Transcoded to pure Python from I{Karney}'s GeographicLib 2.7 C++ classes U{Ellipsoidal3<https://
9
+ GeographicLib.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>}.
11
+
12
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2024-2025) and licensed under the MIT/X11
13
+ License. For more information, see the U{GeographicLib 2.7 <https://GeographicLib.SourceForge.io/>}
14
+ documentation.
15
+ '''
16
+ # make sure int/int division yields float quotient, see .basics
17
+ from __future__ import division as _; del _ # noqa: E702 ;
18
+
19
+ from pygeodesy.angles import Ang, Ang_, _Ang3Tuple, atan2, sincos2, _SinCos2
20
+ from pygeodesy.basics import _copysign, map1
21
+ from pygeodesy.constants import EPS, EPS02, EPS8, _EPSqrt, INT0, NAN, \
22
+ _copysign_0_0, _copysign_1_0, _flipsign, \
23
+ _isfinite, _over, _1_over, _0_0, _0_5, \
24
+ _1_0, _N_1_0, _2_0, _3_0, _4_0, _9_0
25
+ from pygeodesy.errors import _xattr, _xkwds, _xkwds_get, _xkwds_pop2
26
+ from pygeodesy.fmath import cbrt2, fdot, hypot, hypot2, norm2, fabs, sqrt
27
+ from pygeodesy.fsums import Fsum, fsumf_, Fmt
28
+ from pygeodesy.interns import NN, _h_, _lam_, _name_, _phi_
29
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
30
+ from pygeodesy.named import _NamedDict, _Pass # _Named
31
+ from pygeodesy.namedTuples import Vector4Tuple
32
+ from pygeodesy.props import Property_RO, property_ROver
33
+ # from pygeodesy.streprs import Fmt # from .fsums
34
+ from pygeodesy.triaxials.bases import _bet_, _HeightINT0, LLK, _llk_, \
35
+ _MAXIT, _omg_, _otherV3d_, _sqrt0, \
36
+ _Triaxial3Base, TriaxialError
37
+ from pygeodesy.units import Degrees, Radians, Radius_
38
+ # from pygeodesy.utily import atan2, sincos2 # from .triaxials.angles
39
+ from pygeodesy.vector3d import Vector3d
40
+
41
+ # from math import fabs, sqrt # from .fmath
42
+ from random import random
43
+
44
+ __all__ = _ALL_LAZY.triaxials_triaxial3
45
+ __version__ = '25.12.12'
46
+
47
+ _alp_ = 'alp'
48
+ _NAN3d = Vector3d(NAN, NAN, NAN)
49
+ _SQRT3 = sqrt(_3_0)
50
+ _TOL = cbrt2(EPS)
51
+ _TOL2 = _TOL**2 # cbrt(EPS)**4
52
+ _zet_ = 'zet'
53
+ _27_0 = 27.0
54
+
55
+
56
+ class BetOmgAlp5Tuple(_Ang3Tuple):
57
+ '''5-Tuple C{(bet, omg, alp, h, llk)} with I{ellipsoidal}
58
+ lat- C{bet}, longitude C{omg} and azimuth C{alp}, all
59
+ in L{Ang}les on and height C{h} off the triaxial's
60
+ surface and kind C{llk} set to C{LLK.ELLIPSOIDAL}.
61
+ '''
62
+ _Names_ = (_bet_, _omg_, _alp_, _h_, _llk_)
63
+ _Units_ = ( Ang, Ang, _Pass, _HeightINT0, _Pass)
64
+
65
+
66
+ class Cartesian5Tuple(Vector4Tuple):
67
+ '''5-Tuple C{(x, y, z, h, llk)} with I{cartesian} C{x},
68
+ C{y} and C{z} coordinates on and height C{h} above
69
+ or below the triaxial's surface and kind C{llk} set
70
+ to the original C{LLK} or C{None}.
71
+ '''
72
+ _Names_ = Vector4Tuple._Names_ + (_llk_,)
73
+ _Units_ = Vector4Tuple._Units_ + (_Pass,)
74
+
75
+ def __new__(cls, x, y, z, h=0, llk=None, **kwds): # **iteration_name
76
+ args = x, y, z, (h or INT0), llk
77
+ return Vector4Tuple.__new__(cls, args, **kwds)
78
+
79
+
80
+ class _Fp2(object):
81
+ '''(INTERNAL) Function and derivate evaluation.
82
+ '''
83
+ _D = EPS * _0_5
84
+
85
+ def __init__(self, rs, ls, n=1):
86
+ # assert 0 < n <= 2
87
+ self._2 = n == 2
88
+ self._rls = tuple((p, q) for p, q in zip(rs, ls) if p)
89
+
90
+ def __call__(self, p):
91
+ # Evaluate C{f(p) = sum((rs[k] / (p + ls[k]))**n,
92
+ # k=0..2) - 1} and its derivative C{fp}.
93
+ f = _N_1_0
94
+ fc = fp = _0_0
95
+ _D = self._D
96
+ _2 = self._2
97
+ for g, q in self._rls:
98
+ q = _1_over(p + q)
99
+ g *= q
100
+ if _2:
101
+ g *= g
102
+ q += q
103
+ r = round(g / _D) * _D
104
+ f += r
105
+ fc += g - r
106
+ fp -= g * q
107
+ return (f + fc), fp
108
+
109
+
110
+ class PhiLamZet5Tuple(_Ang3Tuple):
111
+ '''5-Tuple C{(phi, lam, zet, h, llk)} with trixial lat-
112
+ lat- C{phi}, longitude C{lam} and azimuth C{zet}, all
113
+ in L{Ang}les on and height C{h} off the triaxial's
114
+ surface and kind C{llk} set to an C{LLK}.
115
+ '''
116
+ _Names_ = (_phi_, _lam_, _zet_, _h_, _llk_)
117
+ _Units_ = ( Ang, Ang, _Pass, _HeightINT0, _Pass)
118
+
119
+
120
+ class Triaxial3(_Triaxial3Base):
121
+ '''I{Ordered} triaxial ellipsoid convering between cartesian and
122
+ lat-/longitudes using using class L{Ang}.
123
+
124
+ @see: L{Triaxial<triaxials.triaxial5.Triaxial>} for details.
125
+ '''
126
+ def _cardinal2(self, v, mer, llk): # cardinaldir
127
+ '''(INTERNAL) Get 2-tuple C{(n, e)} at C{mer}idian.
128
+ '''
129
+ # assert isinstance(v, Vector3d) and isinstance(mer, Ang) \
130
+ # and isinstance(llk, LLK.__class__)
131
+ a2, b2, c2 = self._a2b2c23
132
+ if llk._X:
133
+ a2, c2 = c2, a2
134
+ v = v._roty(True) # +1
135
+ x, y, z = v.xyz3
136
+ if x or y:
137
+ s = (-z) / c2
138
+ z = x**2 / a2 + y**2 / b2
139
+ else:
140
+ y, x, _ = mer.scn3
141
+ s = _copysign_1_0(-z)
142
+ z = _0_0
143
+ n = Vector3d(x * s, y * s, z).unit()
144
+ e = v.dividedBy_(a2, b2, c2).unit() # normvec
145
+ e = n.cross(e).unit()
146
+ if llk._X:
147
+ e = e._roty(False) # -1
148
+ n = n._roty(False) # -1
149
+ return n, e
150
+
151
+ def forwardBetOmg(self, bet, omg, height=0, **unit_name): # elliptocart2
152
+ '''Convert an I{ellipsoidal} lat- and longitude to a cartesian
153
+ on this triaxial's surface.
154
+
155
+ @arg bet: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
156
+ @arg omg: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
157
+ @kwarg height: Height above or below this triaxial's surface (C{meter},
158
+ same units as this triaxial's semi-axes).
159
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar
160
+ C{B{unit}=}L{Radians} (or L{Degrees}).
161
+
162
+ @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with C{h=B{height}}
163
+ and kind C{llk=LLK.ELLIPSOIDAL}.
164
+
165
+ @see: Method L{Triaxial3.reverseBetOmg}.
166
+ '''
167
+ ct, _ = self.forwardBetOmgAlp2(bet, omg, None, height, **unit_name)
168
+ return ct
169
+
170
+ forwardBetaOmega = forwardBetOmg # for backward compatibility
171
+
172
+ def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
173
+ '''DEPRECATED on 2025.11.15, like C{Triaxial.forwardBetaOmega_}.'''
174
+ return self.forwardBetaOmega(Ang_(sbeta, cbeta),
175
+ Ang_(somega, comega), **name)
176
+
177
+ def forwardBetOmgAlp2(self, bet, omg, alp, height=0, **unit_name): # elliptocart2
178
+ '''Convert an I{ellipsoidal} lat-, longitude and heading to a
179
+ cartesian and a direction on this triaxial's surface.
180
+
181
+ @arg bet: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
182
+ @arg omg: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
183
+ @arg alp: Azimuth of the heading (C{Ang}, B{C{unit}} or C{None}).
184
+ @kwarg height: Height above or below this triaxial's surface (C{meter},
185
+ same units as this triaxial's semi-axes).
186
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}), scalar
187
+ C{B{unit}=}L{Radians} (or L{Degrees}).
188
+
189
+ @return: 2-Tuple C{(cartesian, direction)} with C{cartesian} a
190
+ L{Cartesian5Tuple}C{(x, y, z, h, llk)} with C{h=B{height}},
191
+ kind C{llk=LLK.ELLIPSOIDAL} and C{direction} a C{Vector3d}
192
+ tangent to this triaxial's surface or C{None}.
193
+
194
+ @see: Method L{Triaxial3.reverseBetOmgAlp}.
195
+ '''
196
+ h, llk, unit, name = _h_llk_unit_name(height, **unit_name)
197
+ a, b, c = self._abc3
198
+ if h: # Cartesian.elliptocart
199
+ a, b, _ = self._a2b2c23
200
+ h = _HeightINT0(h)
201
+ s = (c * _2_0 + h) * h
202
+ if s < 0:
203
+ s = -min(a, b, -s)
204
+ a = sqrt(a + s)
205
+ b = sqrt(b + s)
206
+ c += h
207
+ sb, cb = _SinCos2(bet, unit)
208
+ so, co = _SinCos2(omg, unit)
209
+ k, kp = self._k_kp
210
+ tx, tz = _txtz2(cb, so, k, kp)
211
+ ct = Cartesian5Tuple(a * co * tx,
212
+ b * cb * so,
213
+ c * sb * tz,
214
+ h, llk, **name)
215
+
216
+ if alp is None: # or h?
217
+ dir3d = None # _NAN3d?
218
+ else:
219
+ try:
220
+ sa, ca = _SinCos2(alp, unit)
221
+ except Exception as X:
222
+ raise TriaxialError(alp=alp, cause=X)
223
+ a, b, c = self._abc3
224
+ if k and kp and not (cb or so):
225
+ c2s2_b = (ca - sa) * (ca + sa) / b
226
+ dir3d = Vector3d(a * k * co * c2s2_b
227
+ -co * sb * ca * sa * _2_0,
228
+ c * kp * sb * c2s2_b)
229
+ else:
230
+ if not tx: # at oblate pole tx -> |cos(bet)|
231
+ c = _flipsign(co, cb)
232
+ n = Vector3d(-c * sb,
233
+ -so * sb, _0_0)
234
+ e = Vector3d(-so, c, _0_0)
235
+ elif not tz: # at prolate pole tz -> |sin(omg)|
236
+ s = _flipsign(sb, so)
237
+ n = Vector3d(_0_0, -s, cb)
238
+ e = Vector3d(_0_0, cb * co, co * s)
239
+ else:
240
+ k2, kp2 = self._k2_kp2
241
+ n = Vector3d(-a * k2 * sb * cb * co / tx,
242
+ -b * sb * so, c * cb * tz)
243
+ e = Vector3d(-a * tx * so, b * cb * co,
244
+ c * kp2 * sb * so * co / tz)
245
+ dir3d = n.unit().times(ca) # NAN
246
+ dir3d += e.unit().times(sa) # NAN
247
+ dir3d.name = ct.name
248
+ return ct, dir3d
249
+
250
+ def forwardCartesian(self, x_ct, y=None, z=None, normal=True, **eps_llk_name):
251
+ '''Project any cartesian I{onto} this triaxial's surface.
252
+
253
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
254
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
255
+ or L{Vector4Tuple}).
256
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
257
+ ignored otherwise.
258
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
259
+ @kwarg normal: If C{True}, the projection is C{perpendicular} to the surface,
260
+ otherwise C{radial} to the center of this triaxial (C{bool}).
261
+ @kwarg eps_llk_name: Root finder tolerance C{B{eps}=EPS}, kind C{B{llk}=None}
262
+ overriding C{B{x_ct}.llk} and optional C{B{name}="height4"} (C{str}).
263
+
264
+ @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)}.
265
+
266
+ @see: Method L{Triaxial3.reverseCartesian} to reverse the projection and
267
+ function L{height4<triaxials.triaxial5.height4>} for more details.
268
+ '''
269
+ llk, kwds = _xkwds_pop2(eps_llk_name, llk=_xattr(x_ct, llk=None))
270
+ h = self.sideOf(x_ct, y, z)
271
+ if h: # signed, square
272
+ v = self.height4(x_ct, y, z, normal=normal, **kwds)
273
+ h = v.h
274
+ else: # on the surface
275
+ v = _otherV3d_(x_ct, y, z)
276
+ n = _xkwds_get(kwds, name=NN)
277
+ return Cartesian5Tuple(v.x, v.y, v.z, h, llk, iteration=v.iteration, name=n)
278
+
279
+ def forwardLatLon(self, lat, lon, height=0, llk=LLK.ELLIPSOIDAL, **unit_name): # anytocart2
280
+ '''Convert any lat-/longitude kind to a cartesian on this triaxial's surface.
281
+
282
+ @arg lat: Latitude (C{Ang} or B{C{unit}}).
283
+ @arg lon: Longitude (C{Ang} or B{C{unit}}).
284
+ @kwarg height: Height above or below this triaxial's surface (C{meter}, same
285
+ units as this triaxial's semi-axes).
286
+ @kwarg llk: The kind (an L{LLK}).
287
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Degrees}
288
+ (or L{Radians}).
289
+
290
+ @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=B{height}} and
291
+ kind C{llk=B{llk}}.
292
+
293
+ @see: Method L{Triaxial3.reverseLatLon}.
294
+ '''
295
+ _fwd = self.forwardBetOmg if llk in LLK._NOIDAL else \
296
+ self.forwardPhiLam # PYCHOK OK
297
+ return _fwd(lat, lon, height=height, llk=llk, **_xkwds(unit_name, unit=Degrees))
298
+
299
+ def forwardPhiLam(self, phi, lam, height=0, llk=LLK.GEODETIC, **unit_name): # generictocart2
300
+ '''Convert any lat-/longitude kind to a cartesian on this triaxial's surface.
301
+
302
+ @arg phi: Latitude (C{Ang} or B{C{unit}}).
303
+ @arg lam: Longitude (C{Ang} or B{C{unit}}).
304
+ @kwarg height: Height above or below this triaxial's surface (C{meter}, same
305
+ units as this triaxial's semi-axes).
306
+ @kwarg llk: The kind (an L{LLK}).
307
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar C{B{unit}=}L{Radians}
308
+ (or L{Degrees}).
309
+
310
+ @return: A L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=0} and kind
311
+ C{llk=B{llk}}.
312
+
313
+ @note: Longitude C{B{lam} -= Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
314
+
315
+ @see: Method L{Triaxial3.reverseLatLon}.
316
+ '''
317
+ ct, _ = self.forwardPhiLamZet2(phi, lam, None, height=height, llk=llk, **unit_name)
318
+ return ct
319
+
320
+ def forwardPhiLamZet2(self, phi, lam, zet, height=0, llk=LLK.GEODETIC, **unit_name): # generictocart2
321
+ '''Convert a lat-, longitude and heading to a cartesian and a direction
322
+ on this trixial's surface.
323
+
324
+ @arg phi: Latitude (C{Ang} or B{C{unit}}).
325
+ @arg lam: Longitude (C{Ang} or B{C{unit}}).
326
+ @arg zet: Azimuth of the heading (C{Ang}, B{C{unit}} or C{None}).
327
+ @kwarg height: Height above or below this triaxial's surface (C{meter},
328
+ same units as this triaxial's semi-axes).
329
+ @kwarg llk: The kind (an L{LLK}).
330
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar
331
+ C{B{unit}=}L{Radians} (or L{Degrees}).
332
+
333
+ @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
334
+ L{Cartesian5Tuple}C{(x, y, z, h, llk)} with height C{h=0},
335
+ kind C{llk=B{llk}} and C{direction}, a C{Vector3d} on and
336
+ tangent to this triaxial's surface.
337
+
338
+ @note: Longitude C{B{lam} -= Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
339
+
340
+ @see: Method L{Triaxial3.reversePhiLamZet}.
341
+ '''
342
+ unit, name = _xkwds_pop2(unit_name, unit=Radians)
343
+ try:
344
+ sa, ca = _SinCos2(phi, unit)
345
+ if llk is LLK.GEODETIC_LON0:
346
+ lam = Ang.fromScalar(lam, unit=unit)
347
+ lam -= self.Lon0
348
+ sb, cb = _SinCos2(lam, unit)
349
+ except Exception as X:
350
+ raise TriaxialError(phi=phi, lam=lam, llk=llk, cause=X)
351
+ v, _, llk, name = _v_h_llk_name(ca * cb, ca * sb, sa, llk=llk, **name)
352
+ if llk and llk._X:
353
+ v = v._roty(False) # -1
354
+ d, t = _d_t(self, llk)
355
+ if t:
356
+ v = v.times_(*t)
357
+ if d:
358
+ d = v.dividedBy_(*self._abc3).length
359
+ v = v.dividedBy(d)
360
+
361
+ h = _HeightINT0(height)
362
+ if h: # cart2cart
363
+ v, h = self._toHeight2(v, h)
364
+ ct = Cartesian5Tuple(v.x, v.y, v.z, h, llk, **name)
365
+
366
+ if zet is None:
367
+ dir3d = None
368
+ else:
369
+ try:
370
+ s, c = _SinCos2(zet, unit)
371
+ except Exception as X:
372
+ raise TriaxialError(zet=zet, cause=X)
373
+ n, e = self._meridian2(v, lam, llk)
374
+ dir3d = n.times(c)
375
+ dir3d += e.times(s)
376
+ dir3d.name = ct.name
377
+ return ct, dir3d
378
+
379
+ def _meridian(self, lam, llk):
380
+ '''(INTERNAL) Get the meridian plane's at C{lam}.
381
+ '''
382
+ _, t = _d_t(self, llk)
383
+ if t:
384
+ a, b, c = t
385
+ lam = lam.mod((c if llk._X else a) / b)
386
+ return lam
387
+
388
+ def _meridian2(self, v, lam, llk):
389
+ '''(INTERNAL) Get 2-tuple C{(n, e)} at C{lam} meridian.
390
+ '''
391
+ mer = self._meridian(lam, llk)
392
+ return self._cardinal2(v, mer, llk)
393
+
394
+ def normed2(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # Ellipsoid3.Norm
395
+ '''Scale a cartesian and direction to this triaxial's surface.
396
+
397
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
398
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
399
+ or L{Vector4Tuple}).
400
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
401
+ ignored otherwise.
402
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
403
+ @kwarg dir3d: The direction (C{Vector3d} or C{None}).
404
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
405
+ overriding C{B{x_ct}.llk}.
406
+
407
+ @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
408
+ L{Cartesian5Tuple}C{(x, y, z, h, llk)} and C{direction}, a
409
+ C{Vector3d} tangent to this triaxial's surface or C{None}
410
+ iff C{B{dir3d} is None}.
411
+ '''
412
+ v, h, llk, name = _v_h_llk_name(x_ct, y, z, **llk_name)
413
+
414
+ u = v.dividedBy_(*self._abc3).length
415
+ r = v.dividedBy(u) if u else _NAN3d
416
+ ct = Cartesian5Tuple(r.x, r.y, r.z, h, llk, **name)
417
+
418
+ if isinstance(dir3d, Vector3d):
419
+ if u: # and r is not _NAN3d
420
+ u = r.dividedBy_(*self._a2b2c23)
421
+ d = dir3d.dot(u)
422
+ if _isfinite(d) and u.length2:
423
+ u = u.times(d / u.length2)
424
+ dir3d = dir3d.minus(u).unit() # NAN
425
+ else:
426
+ dir3d = _NAN3d
427
+ else:
428
+ dir3d = _NAN3d
429
+ dir3d.name = ct.name
430
+ return ct, dir3d
431
+
432
+ def reverseBetOmg(self, x_ct, y=None, z=None, **llk_name): # Cartesian3.carttoellip
433
+ '''Convert a cartesian I{on this triaxial's surface} to an I{ellipsoidal}
434
+ lat-/longitude.
435
+
436
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
437
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
438
+ or L{Vector4Tuple}).
439
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
440
+ ignored otherwise.
441
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
442
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
443
+ overriding C{B{x_ct}.llk}.
444
+
445
+ @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None}
446
+ and C{llk=LLK.ELLIPSOIDAL}.
447
+ '''
448
+ v, _, llk, name = _v_h_llk_name_NOIDAL(x_ct, y, z, **llk_name)
449
+
450
+ _, y2, z2 = rs = v.x2y2z23
451
+ l0, l1, _ = ls = self._lcc23
452
+ qmax = fsumf_(*rs)
453
+ qmin = q = max(z2, y2 + z2 - l1, qmax - l0)
454
+ _fp2 = _Fp2(rs, ls, n=1)
455
+ f, _ = _fp2(q)
456
+ if f > _TOL2: # neg means convergence
457
+ q = max(qmin, min(qmax, _cubic(rs, qmax, l0, l1)))
458
+ f, fp = _fp2(q)
459
+ if fabs(f) > _TOL2:
460
+ q = max(qmin, q - _over(f, fp))
461
+ q = _solve(_fp2, q, self.b2)
462
+
463
+ a, b, c = map1(_sqrt0, l0 + q, l1 + q, q) # axes (a, b, c)
464
+ h = (c - self.c) or INT0
465
+ bet, omg, _ = self._reverseBetOmgAlp3(v, None, a, b, c, **name)
466
+ return BetOmgAlp5Tuple(bet, omg, None, h, llk, **name)
467
+
468
+ reverseBetaOmega = reverseBetOmg # for backward compatibility
469
+
470
+ def reverseBetOmgAlp(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # Ellipsoid3.cart2toellip[int]
471
+ '''Convert a cartesian and direction I{on this triaxial's surface} to an
472
+ I{ellipsoidal} lat-, longitude and heading.
473
+
474
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
475
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
476
+ or L{Vector4Tuple}).
477
+ @kwarg y: Y component (C{scalar}), required if B{C{x_ct}} is C{scalar},
478
+ ignored otherwise.
479
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
480
+ @kwarg dir3d: The direction (C{Vector3d} or C{None}).
481
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
482
+ overriding C{B{x_ct}.llk}.
483
+
484
+ @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None}
485
+ if C{B{dir3d} is None} and C{llk=LLK.ELLIPSOIDAL}.
486
+ '''
487
+ v, h, llk, name = _v_h_llk_name_NOIDAL(x_ct, y, z, **llk_name)
488
+ bet, omg, alp = self._reverseBetOmgAlp3(v, dir3d, **name)
489
+ return BetOmgAlp5Tuple(bet, omg, alp, h, llk, **name)
490
+
491
+ def _reverseBetOmgAlp3(self, v, dir3d, *a_b_c, **name): # cart2toellipint
492
+ '''(INTERNAL) Helper for methods C{reverseBetOmg/-Alp}.
493
+ '''
494
+ k, kp = self._k_kp
495
+ k2, kp2 = self._k2_kp2
496
+ a, b, c = a_b_c or self._abc3
497
+ V = v.dividedBy_(a, b, c)
498
+ X, E, Z = V.xyz3 # Xi, Eta, Zeta
499
+ h = fabs(E * k * kp * _2_0)
500
+ if v.y or fabs(v.x) != a * kp2 or \
501
+ fabs(v.z) != c * k2:
502
+ g = fdot(V.x2y2z23, k2, (k2 - kp2), -kp2)
503
+ h = hypot(g, h)
504
+ else:
505
+ g = _0_0
506
+ if h < EPS02:
507
+ so = cb = _0_0
508
+ elif g < 0:
509
+ h = _over(sqrt((h - g) * _0_5), kp)
510
+ so = _copysign(h, E)
511
+ cb = fabs(_over(E, so))
512
+ else:
513
+ cb = _over(sqrt((h + g) * _0_5), k)
514
+ so = _over(E, cb)
515
+ tx, tz = _txtz2(cb, so, k, kp)
516
+ sb = (Z / tz) if tz else _N_1_0
517
+ co = (X / tx) if tx else _1_0
518
+ bet = Ang_(sb, cb, **name)
519
+ omg = Ang_(so, co, **name)
520
+
521
+ if isinstance(dir3d, Vector3d): # cart2toellip(bet, omg, V) -> alp
522
+ if cb or so or not (tx and tz): # not umbilical
523
+ if not tx:
524
+ n = Vector3d(-co, -so, tx) * sb
525
+ e = Vector3d(-so, co, tx)
526
+ elif not tz:
527
+ n = Vector3d(tz, -sb, cb)
528
+ e = Vector3d(tz, cb, sb) * co
529
+ else:
530
+ n = Vector3d(-a * sb * k2 * cb * co / tx,
531
+ -b * sb * so, c * cb * tz)
532
+ e = Vector3d(-a * so * tx, b * cb * co,
533
+ c * so * kp2 * sb * co / tz)
534
+ sa = dir3d.dot(e.unit()) # NAN
535
+ ca = dir3d.dot(n.unit()) # NAN
536
+ else: # at umbilicial PYCHOK no cover
537
+ x, z = norm2(co * tx / a, sb * tz / c) # _MODS.karney._norm2
538
+ v = dir3d * (sb * co) # dir3d.times(sb * co)
539
+ s2a = -v.y
540
+ c2a = fdot(v, z, 0, -x) # v.x * z - v.z * x
541
+ sa = ca = -sb
542
+ sa *= _copysign(_1_0 - c2a, s2a) if c2a < 0 else s2a
543
+ ca *= fabs(s2a) if c2a < 0 else (c2a + _1_0)
544
+ alp = Ang_(sa, ca, **name)
545
+ elif dir3d is None:
546
+ alp = None # Ang.NAN(**name)
547
+ else:
548
+ raise TriaxialError(dir3d=dir3d)
549
+ return bet, omg, alp
550
+
551
+ def reverseCartesian(self, x_ct, y=None, z=None, height=0, normal=True, **llk_name): # cart2tocart
552
+ '''"Unproject" a cartesian I{off} this triaxial's surface.
553
+
554
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
555
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
556
+ or L{Vector4Tuple}).
557
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
558
+ ignored otherwise.
559
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
560
+ @kwarg height: Height above or below this triaxial's surface (C{meter},
561
+ same units as this triaxial's semi-axes).
562
+ @kwarg normal: If C{True}, B{C{height}} is C{perpendicular} to the surface,
563
+ otherwise C{radial} to the center of this triaxial (C{bool}).
564
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}}
565
+ overriding C{B{x_ct}.llk}.
566
+
567
+ @return: L{Cartesian5Tuple}C{(x, y, z, h, llk)}.
568
+
569
+ @raise TrialError: Cartesian B{C{x_ct}} or C{(x, y, z)} not on this
570
+ triaxial's surface.
571
+
572
+ @see: Methods L{Triaxial3.forwardCartesian}.
573
+ '''
574
+ kwds = _xkwds(llk_name, llk=_xattr(x_ct, llk=None))
575
+ v, _, llk, name = _v_h_llk_name(x_ct, y, z, **kwds)
576
+ _ = self._sideOn(v)
577
+ h = _HeightINT0(height)
578
+ if h:
579
+ v, h = self._toHeight2(v, h, normal)
580
+ return Cartesian5Tuple(v.x, v.y, v.z, h, llk, **name)
581
+
582
+ def reverseLatLon(self, x_ct, y=None, z=None, **llk_name): # cart2toany
583
+ '''Convert a cartesian I{on this triaxial's surface} to a lat-/longitude.
584
+
585
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
586
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
587
+ or L{Vector4Tuple}).
588
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
589
+ ignored otherwise.
590
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
591
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
592
+ overriding C{B{x_ct}.llk}.
593
+
594
+ @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None} or
595
+ a L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
596
+
597
+ @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
598
+ '''
599
+ llk, name = _xkwds_pop2(llk_name, llk=_xattr(x_ct,
600
+ llk=LLK.ELLIPSOIDAL))
601
+ _rev = self.reverseBetOmg if llk in LLK._NOIDAL else \
602
+ self.reversePhiLam # PYCHOK OK
603
+ return _rev(x_ct, y, z, llk=llk, **name)
604
+
605
+ def reversePhiLam(self, x_ct, y=None, z=None, **llk_name): # cart2togeneric
606
+ '''Convert a cartesian I{on this triaxial's surface} to lat-/longitude.
607
+
608
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
609
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
610
+ or L{Vector4Tuple}).
611
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
612
+ ignored otherwise.
613
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
614
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
615
+ overriding C{B{x_ct}.llk}.
616
+
617
+ @return: A L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
618
+
619
+ @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
620
+ '''
621
+ return self.reversePhiLamZet(x_ct, y, z, **llk_name)
622
+
623
+ def reversePhiLamZet(self, x_ct, y=None, z=None, dir3d=None, **llk_name): # cart2togeneric(R, V, ...
624
+ '''Convert a cartesian and direction to lat-, longitude and azimuth.
625
+
626
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
627
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
628
+ or L{Vector4Tuple}).
629
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
630
+ ignored otherwise.
631
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
632
+ @kwarg dir3d: Optional direction (C{Vector3d} or C{None}).
633
+ @kwarg llk_name: Optional C{B{name}=NN} (C{str}) and kind C{B{llk}=None}
634
+ overriding C{B{x_ct}.llk}.
635
+
636
+ @return: A L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}
637
+ if C{B{dir3d} is None}.
638
+
639
+ @note: Longitude C{B{lam} += Lon0} if C{B{llk} is LLK.GEODETIC_LON0}.
640
+ '''
641
+ ct = self.toTriaxial5(x_ct, y, z, h=NAN, **llk_name)
642
+ v, h, llk, name = _v_h_llk_name(ct)
643
+ _, t = _d_t(self, llk)
644
+ if t:
645
+ v = v.dividedBy_(*t)
646
+ if llk._X:
647
+ v = v._roty(True) # +1
648
+ phi = Ang_(v.z, hypot(v.x, v.y), **name)
649
+ lam = Ang_(v.y, v.x, **name) # Ang(0, 0) -> 0
650
+ if llk is LLK.GEODETIC_LON0:
651
+ lam += self.Lon0
652
+
653
+ if dir3d is None:
654
+ zet = None
655
+ elif isinstance(dir3d, Vector3d):
656
+ n, e = self._meridian2(v, lam, llk)
657
+ zet = Ang_(dir3d.dot(e),
658
+ dir3d.dot(n), **name)
659
+ else:
660
+ raise TriaxialError(dir3d=dir3d)
661
+ return PhiLamZet5Tuple(phi, lam, zet, h, llk, **name)
662
+
663
+ def random2(self, llk=LLK.ELLIPSOIDAL, both=False, _rand=random):
664
+ '''Return a random cartesian with/out direction on this triaxial's surface.
665
+
666
+ @kwarg llk: The kind (an L{LLK}).
667
+ @kwarg both: If C{True}, generate a random direction (C{bool}).
668
+
669
+ @return: 2-Tuple C{(cartesian, direction)} with the C{cartesian} a
670
+ L{Cartesian5Tuple}C{(x, y, z, h, llk)} and C{direction}, a
671
+ C{Vector3d} tangent to this triaxial's surface or C{None}
672
+ iff C{B{both} is False}.
673
+ '''
674
+ for _ in range(_MAXIT):
675
+ for _ in range(_MAXIT):
676
+ v = Vector3d(_rand(), _rand(), _rand())
677
+ u = v.length
678
+ if u and _isfinite(u):
679
+ break
680
+ else:
681
+ raise TriaxialError(Fmt.no_convergence(u))
682
+ v = v.dividedBy(u).times_(*self._abc3)
683
+ q = v.dividedBy_(*self._a2b2c23).length * self.c
684
+ if 0 < q <= _1_0: # _uni(q) < q:
685
+ break
686
+ else:
687
+ raise TriaxialError(Fmt.no_convergence(q))
688
+ ct = Cartesian5Tuple(v.x, v.y, v.z, INT0, llk, name__=self.random2)
689
+ v = None
690
+ if both:
691
+ for _ in range(_MAXIT):
692
+ v = Vector3d(_rand(), _rand(), _rand())
693
+ u = v.length
694
+ if u:
695
+ u = v.dividedBy(u).dividedBy_(*self._a2b2c23)
696
+ d = v.dot(u) / u.length2
697
+ v = v.minus(u.times(d))
698
+ u = v.length # normvec
699
+ if u and _isfinite(u):
700
+ v = v.dividedBy(u)
701
+ break
702
+ else:
703
+ raise TriaxialError(Fmt.no_convergence(u))
704
+ v.name = ct.name
705
+ return ct, v
706
+
707
+ def _toHeight2(self, v, h, normal=True):
708
+ '''(INTERNAL) Move cartesian C{Vector3d B{v}} to height C{h}.
709
+ '''
710
+ n = v.dividedBy_(*self._a2b2c23) if normal else v
711
+ if n.length > EPS02:
712
+ h = max(h, -self.c)
713
+ v = v.plus(n.times(h / n.length))
714
+ return v, h
715
+
716
+ def toOther(self, lat, lon, llk1=LLK.GEODETIC, llk2=LLK.GEODETIC, **unit_name): # anytoany
717
+ '''Convert one lat-/longitude kind to an other.
718
+
719
+ @arg lat: Latitude (C{Ang} or B{C{unit}}).
720
+ @arg lon: Longitude (C{Ang} or B{C{unit}}).
721
+ @kwarg llk1: The given kind (an L{LLK}).
722
+ @kwarg llk2: The result kind (an L{LLK}).
723
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
724
+
725
+ @return: A L{BetOmgAlp5Tuple}C{(bet, omg, alp, h, llk)} with C{alp=None} or
726
+ a L{PhiLamZet5Tuple}C{(phi, lam, zet, h, llk)} with C{zet=None}.
727
+
728
+ @see: Methods L{Triaxial3.forwardLatLon} and -L{reverseLatLon}.
729
+ '''
730
+ ct = self.forwardLatLon(lat, lon, llk=llk1, **unit_name)
731
+ r = self.reverseLatLon(ct, llk=llk2, name=ct.name)
732
+ # a, b = r[:2]
733
+ # if not isAng(lat):
734
+ # a = float(a)
735
+ # if not isAng(lon):
736
+ # b = float(b)
737
+ # if (a, b) =! r[:2]:
738
+ # r = r._dup(a, b)
739
+ return r
740
+
741
+ def toTriaxial5(self, x_ct, y=None, z=None, **triaxial_h_llk_name): # carttocart2
742
+ '''Find the closest cartesian on this or on another triaxial's surface.
743
+
744
+ @arg x_ct: X component (C{scalar}) or a cartesian (L{Cartesian5Tuple} or
745
+ any C{Cartesian}, L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple}
746
+ or L{Vector4Tuple}).
747
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
748
+ ignored otherwise.
749
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
750
+ @kwarg triaxial_llk_name: Optional C{B{triaxial}=self} (C{Triaxial3}),
751
+ C{B{name}=NN} (C{str}), height C{B{h}} and kind C{B{llk}}
752
+ overriding C{B{x_ct}.h} respectively C{B{x_ct}.llk}.
753
+
754
+ @return: L{Cartesian5Tuple}C{(x, y, z, h, llk)}
755
+
756
+ @raise TriaxialError: If C{B{triaxial}} is not a L{Triaxial3}.
757
+
758
+ @see: Functions L{hartzell4<triaxials.triaxial5.hartzell4>} and
759
+ L{height4<triaxials.triaxial5.height4>} and methods.
760
+ '''
761
+ T, name = _xkwds_pop2(triaxial_h_llk_name, triaxial=self)
762
+ if not isinstance(T, Triaxial3):
763
+ raise TriaxialError(triaxial=T, x=x_ct, y=y, z=z)
764
+
765
+ v, h, llk, name = _v_h_llk_name(x_ct, y, z, **name)
766
+ if h or T is not self:
767
+ l0, l1, _ = ls = T._lcc23
768
+ r = Vector3d(*T._ztol(v))
769
+ s = r.times_(*T._abc3)
770
+ p = max(fabs(s.z), hypot(s.x, s.y) - l1, s.length - l0)
771
+ h = _solve(_Fp2(s.xyz3, ls, n=2), p, T.b2)
772
+ v = r.times_(*(_over(_, h + l_) for _, l_ in zip(T._a2b2c23, ls)))
773
+ if not h: # handle h == 0, v.y indeterminate
774
+ x = v.x if l0 else r.x # sphere
775
+ y = v.y if l1 else r.y # sphere or prolate
776
+ s = _1_0 - hypot2(x / T.a, y / T.b)
777
+ z = (sqrt(s) * _copysign(T.c, r.z)) if s > EPS02 else _0_0
778
+ v = Vector3d(x, y, z)
779
+ h -= T.c2
780
+ if h and v.length:
781
+ h *= v.dividedBy_(*T._a2b2c23).length
782
+ return Cartesian5Tuple(v.x, v.y, v.z, (h or INT0), llk, **name)
783
+
784
+ @Property_RO
785
+ def _ZTOL(self):
786
+ return self.b * (EPS / 8)
787
+
788
+ def _ztol(self, v):
789
+ for x in v.xyz3:
790
+ yield x if fabs(x) > self._ZTOL else _copysign_0_0(x)
791
+
792
+
793
+ class Triaxial3B(Triaxial3):
794
+ '''Triaxial ellipsoid specified by its middle semi-axis and shape.
795
+
796
+ @see: L{Triaxial3} for more information.
797
+ '''
798
+ def __init__(self, b, e2=_0_0, k2=_1_0, kp2=_0_0, **name):
799
+ '''New, L{Triaxial3B} instance.
800
+
801
+ @see: L{Triaxial<triaxials.triaxial5.Triaxial>} for details.
802
+ '''
803
+ self._init_abc3_e2_k2_kp2(Radius_(b=b), e2, k2, kp2, **name)
804
+
805
+
806
+ class Triaxial3s(_NamedDict):
807
+ '''(INTERNAL) L{Triaxial3} registry, I{must} be a sub-class
808
+ to accommodate the L{_LazyNamedEnumItem} properties.
809
+ '''
810
+ def __getattr__(self, name):
811
+ '''Get the value of an item by B{C{name}}.
812
+ '''
813
+ try:
814
+ return self[name]
815
+ except KeyError:
816
+ if name == _name_:
817
+ return _MODS.named._Named.name.fget(self)
818
+ raise _NamedDict._AttributeError(self, self._DOT_(name))
819
+
820
+ def __getitem__(self, key):
821
+ '''Get the value of an item by B{C{key}}.
822
+ '''
823
+ T = self._Triaxials(key, None)
824
+ if T is None or key == _name_:
825
+ raise KeyError(key)
826
+ return Triaxial3(T, name=key)
827
+
828
+ @property_ROver
829
+ def _Triaxials(self):
830
+ '''(INTERNAL) Get the C{Triaxials.get}, I{once}.
831
+ '''
832
+ return _MODS.triaxials.triaxial5.Triaxials.get
833
+
834
+ Triaxial3s = Triaxial3s() # PYCHOK singleton
835
+ '''Some pre-defined L{Triaxial3}s, like L{Triaxials<triaxials.Triaxials>}.'''
836
+
837
+
838
+ def _cubic(rs, rt, l0, l1): # Cartesian3.cubic
839
+ '''(INTERNaL) Solve sum(R2[i]/(z + lq2[i]), i=0,1,2) - 1 = 0
840
+ with lq2[2] = 0. This has three real roots with just one
841
+ satisifying q >= 0.
842
+ '''
843
+ a = l0 + l1
844
+ b = l0 * l1
845
+ c = -b * rs[2] # z2
846
+ # cubic equation z^3 + a*z^2 + b*z + c = 0
847
+ b -= fdot(rs, l1, l0, a)
848
+ a -= rt
849
+ _r = b > 0
850
+ if _r:
851
+ a, b = b, a
852
+ c = _1_over(c)
853
+ a *= c
854
+ b *= c
855
+ # see https://dlmf.nist.gov/1.11#iii
856
+ p = (b * _3_0 - a**2) / _3_0
857
+ t = -p / _3_0 # A / 4
858
+ if t > 0:
859
+ q = (a**3 * _2_0 - a * b * _9_0 + c * _27_0) / _27_0
860
+ # switch to https://dlmf.nist.gov/4.43
861
+ s = -q**2 - p**3 * _4_0 / _27_0
862
+ p = sqrt(s) if s > 0 else _0_0
863
+ s, c = sincos2(atan2(q, p) / _3_0) # alp
864
+ t = (c * _SQRT3 - s) * sqrt(t)
865
+ else:
866
+ t = _0_0
867
+ t -= a / _3_0
868
+ return _1_over(t) if _r else t
869
+
870
+
871
+ def _d_t(triax, llk):
872
+ '''(INTERNAL) Helper.
873
+ '''
874
+ if llk in LLK._CENTRICS:
875
+ d_t = True, None
876
+ elif llk in LLK._DETICS:
877
+ d_t = True, triax._a2b2c23
878
+ elif llk in LLK._METRICS:
879
+ d_t = False, triax._abc3
880
+ else:
881
+ raise TriaxialError(llk=llk)
882
+ return d_t
883
+
884
+
885
+ def _h_llk_unit_name(height, h=None, llk=LLK.ELLIPSOIDAL, unit=Radians, **name):
886
+ '''(INTERNAL) Helper, C{h} for backward compatibility.
887
+ '''
888
+ if llk is None:
889
+ llk = LLK.ELLIPSOIDAL
890
+ elif llk not in LLK._NOIDAL: # or llk._X
891
+ raise TriaxialError(llk=llk)
892
+ if h is None:
893
+ h = height
894
+ return h, llk, unit, name
895
+
896
+
897
+ def _solve(_fp2, p, pscale, **n):
898
+ '''(INTERNAL) Solve _fp2(p) = 0
899
+ '''
900
+ dt = _N_1_0
901
+ pt = _EPSqrt * pscale
902
+ _P2 = Fsum(p).fsum2_
903
+ for i in range(_MAXIT):
904
+ fv, fp = _fp2(p, **n)
905
+ if not (fv > _TOL2):
906
+ break
907
+ p, d = _P2(-fv / fp) # d is positive
908
+ if i and d <= dt and (fv <= EPS8 or d <= (max(pt, p) * _TOL)):
909
+ break
910
+ dt = d
911
+ else:
912
+ t = Fmt.no_convergence(d, min(dt, pt))
913
+ raise TriaxialError(_fp2.__name__, p, txt=t)
914
+ return p
915
+
916
+
917
+ def _txtz2(cb, so, k, kp):
918
+ '''(INTERNAL) Helper.
919
+ '''
920
+ return hypot(cb * k, kp), hypot(k, so * kp)
921
+
922
+
923
+ def _v_h_llk_name(x_ct, y=None, z=None, **h_llk_name):
924
+ '''(INTERNAL) Helper.
925
+ '''
926
+ if y is z is None and isinstance(x_ct, Cartesian5Tuple):
927
+
928
+ def _v_h_llk_name(h=x_ct.h, llk=x_ct.llk, **name):
929
+ v = Vector3d(*x_ct.xyz3, **name)
930
+ return v, h, llk, name
931
+ else:
932
+ def _v_h_llk_name(h=INT0, llk=None, **name): # PYCHOK redef
933
+ v = _otherV3d_(x_ct, y, z)
934
+ return v, h, llk, name
935
+
936
+ return _v_h_llk_name(**h_llk_name)
937
+
938
+
939
+ def _v_h_llk_name_NOIDAL(x_ct, y, z, **h_llk_name):
940
+ '''(INTERNAL) Helper for methods C{reverseBetOmg} and C{-Alp}.
941
+ '''
942
+ v, h, llk, name = _v_h_llk_name(x_ct, y, z, **h_llk_name)
943
+ if h or llk not in LLK._NOIDAL: # or llk._X
944
+ kwds = dict(x_ct=x_ct) if y is z is None else \
945
+ dict(x=x_ct, y=y, z=z)
946
+ raise TriaxialError(h=h, llk=llk, **kwds)
947
+ return v, h, (LLK.ELLIPSOIDAL if llk is None else llk), name
948
+
949
+ # **) MIT License
950
+ #
951
+ # Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved.
952
+ #
953
+ # Permission is hereby granted, free of charge, to any person obtaining a
954
+ # copy of this software and associated documentation files (the "Software"),
955
+ # to deal in the Software without restriction, including without limitation
956
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
957
+ # and/or sell copies of the Software, and to permit persons to whom the
958
+ # Software is furnished to do so, subject to the following conditions:
959
+ #
960
+ # The above copyright notice and this permission notice shall be included
961
+ # in all copies or substantial portions of the Software.
962
+ #
963
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
964
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
965
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
966
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
967
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
968
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
969
+ # OTHER DEALINGS IN THE SOFTWARE.