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.
- pygeodesy/__init__.py +25 -12
- pygeodesy/__main__.py +1 -1
- pygeodesy/albers.py +1 -1
- pygeodesy/angles.py +960 -0
- pygeodesy/auxilats/_CX_4.py +1 -1
- pygeodesy/auxilats/_CX_6.py +1 -1
- pygeodesy/auxilats/_CX_8.py +1 -1
- pygeodesy/auxilats/_CX_Rs.py +1 -1
- pygeodesy/auxilats/__init__.py +2 -2
- pygeodesy/auxilats/__main__.py +1 -1
- pygeodesy/auxilats/auxAngle.py +7 -8
- pygeodesy/auxilats/auxDLat.py +1 -1
- pygeodesy/auxilats/auxDST.py +1 -1
- pygeodesy/auxilats/auxLat.py +1 -1
- pygeodesy/auxilats/auxily.py +1 -1
- pygeodesy/azimuthal.py +6 -5
- pygeodesy/basics.py +14 -10
- pygeodesy/booleans.py +8 -33
- pygeodesy/cartesianBase.py +7 -7
- pygeodesy/clipy.py +17 -23
- pygeodesy/constants.py +86 -63
- pygeodesy/css.py +1 -1
- pygeodesy/datums.py +1 -1
- pygeodesy/deprecated/__init__.py +2 -2
- pygeodesy/deprecated/bases.py +1 -1
- pygeodesy/deprecated/classes.py +32 -2
- pygeodesy/deprecated/consterns.py +1 -1
- pygeodesy/deprecated/datum.py +1 -1
- pygeodesy/deprecated/functions.py +1 -1
- pygeodesy/deprecated/nvector.py +1 -1
- pygeodesy/deprecated/rhumbBase.py +1 -1
- pygeodesy/deprecated/rhumbaux.py +1 -1
- pygeodesy/deprecated/rhumbsolve.py +1 -1
- pygeodesy/deprecated/rhumbx.py +1 -1
- pygeodesy/dms.py +1 -1
- pygeodesy/ecef.py +1 -1
- pygeodesy/ecefLocals.py +1 -1
- pygeodesy/elevations.py +1 -1
- pygeodesy/ellipsoidalBase.py +1 -1
- pygeodesy/ellipsoidalBaseDI.py +1 -1
- pygeodesy/ellipsoidalExact.py +1 -1
- pygeodesy/ellipsoidalGeodSolve.py +1 -1
- pygeodesy/ellipsoidalKarney.py +1 -1
- pygeodesy/ellipsoidalNvector.py +1 -1
- pygeodesy/ellipsoidalVincenty.py +1 -1
- pygeodesy/ellipsoids.py +7 -6
- pygeodesy/elliptic.py +1 -1
- pygeodesy/epsg.py +1 -1
- pygeodesy/errors.py +8 -4
- pygeodesy/etm.py +1 -1
- pygeodesy/fmath.py +15 -8
- pygeodesy/formy.py +107 -5
- pygeodesy/frechet.py +1 -1
- pygeodesy/fstats.py +1 -1
- pygeodesy/fsums.py +1 -1
- pygeodesy/gars.py +1 -1
- pygeodesy/geod3solve.py +488 -0
- pygeodesy/geodesici.py +4 -4
- pygeodesy/geodesicw.py +1 -1
- pygeodesy/geodesicx/_C4_24.py +1 -1
- pygeodesy/geodesicx/_C4_27.py +1 -1
- pygeodesy/geodesicx/_C4_30.py +1 -1
- pygeodesy/geodesicx/__init__.py +1 -1
- pygeodesy/geodesicx/__main__.py +1 -1
- pygeodesy/geodesicx/gx.py +1 -1
- pygeodesy/geodesicx/gxarea.py +1 -1
- pygeodesy/geodesicx/gxbases.py +1 -1
- pygeodesy/geodesicx/gxline.py +1 -1
- pygeodesy/geodsolve.py +70 -102
- pygeodesy/geohash.py +1 -1
- pygeodesy/geoids.py +1 -1
- pygeodesy/hausdorff.py +1 -1
- pygeodesy/heights.py +1 -1
- pygeodesy/internals.py +3 -3
- pygeodesy/interns.py +3 -3
- pygeodesy/iters.py +1 -1
- pygeodesy/karney.py +132 -116
- pygeodesy/ktm.py +1 -1
- pygeodesy/latlonBase.py +1 -1
- pygeodesy/lazily.py +25 -13
- pygeodesy/lcc.py +1 -1
- pygeodesy/ltp.py +1 -1
- pygeodesy/ltpTuples.py +1 -1
- pygeodesy/mgrs.py +3 -3
- pygeodesy/named.py +14 -9
- pygeodesy/namedTuples.py +1 -1
- pygeodesy/nvectorBase.py +1 -1
- pygeodesy/osgr.py +1 -1
- pygeodesy/points.py +1 -1
- pygeodesy/props.py +1 -1
- pygeodesy/resections.py +1 -1
- pygeodesy/rhumb/__init__.py +8 -6
- pygeodesy/rhumb/aux_.py +1 -1
- pygeodesy/rhumb/bases.py +1 -1
- pygeodesy/rhumb/ekx.py +1 -1
- pygeodesy/rhumb/solve.py +91 -84
- pygeodesy/simplify.py +1 -1
- pygeodesy/solveBase.py +72 -49
- pygeodesy/sphericalBase.py +1 -1
- pygeodesy/sphericalNvector.py +1 -1
- pygeodesy/sphericalTrigonometry.py +1 -1
- pygeodesy/streprs.py +6 -4
- pygeodesy/trf.py +1 -1
- pygeodesy/triaxials/__init__.py +70 -0
- pygeodesy/triaxials/bases.py +935 -0
- pygeodesy/triaxials/conformal3.py +617 -0
- pygeodesy/triaxials/triaxial3.py +969 -0
- pygeodesy/triaxials/triaxial5.py +1220 -0
- pygeodesy/units.py +6 -1
- pygeodesy/unitsBase.py +1 -1
- pygeodesy/ups.py +2 -3
- pygeodesy/utily.py +19 -15
- pygeodesy/utm.py +1 -1
- pygeodesy/utmups.py +1 -1
- pygeodesy/utmupsBase.py +1 -1
- pygeodesy/vector2d.py +2 -2
- pygeodesy/vector3d.py +1 -1
- pygeodesy/vector3dBase.py +195 -51
- pygeodesy/webmercator.py +1 -1
- pygeodesy/wgrs.py +1 -1
- {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/METADATA +13 -13
- pygeodesy-25.12.12.dist-info/RECORD +125 -0
- pygeodesy/triaxials.py +0 -1566
- pygeodesy-25.10.10.dist-info/RECORD +0 -119
- {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/WHEEL +0 -0
- {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.
|