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