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,935 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''(INTERNAL) Base classes for I{ordered} triaxial ellipsoid classes L{Conformal}, L{Conformal3},
|
|
5
|
+
L{Triaxial}, L{Triaxial3} and I{unordered} L{Triaxial_}.
|
|
6
|
+
|
|
7
|
+
Transcoded to pure Python from I{Karney}'s GeographicLib 2.7 C++ classes U{Ellipsoid3<https://
|
|
8
|
+
GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Ellipsoid3.html>},
|
|
9
|
+
U{Cartesian3<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>} and
|
|
10
|
+
U{Conformal3<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Conformal3.html>}.
|
|
11
|
+
|
|
12
|
+
GeographicLib 2.5.2 C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/C++/doc/
|
|
13
|
+
classGeographicLib_1_1JacobiConformal.html#details>}.
|
|
14
|
+
|
|
15
|
+
Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024, 2025) and licensed under the MIT/X11 License.
|
|
16
|
+
For more information, see the U{GeographicLib 2.5.2 and 2.7<https://GeographicLib.SourceForge.io/>} documentation.
|
|
17
|
+
|
|
18
|
+
Enum-like C{Lat-/Longitude Kinds (LLK)}, see I{Karney}'s U{coord<https://GeographicLib.SourceForge.io/
|
|
19
|
+
C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>}:
|
|
20
|
+
|
|
21
|
+
@var LLK.CONFORMAL: Jacobi conformal X and Y projection
|
|
22
|
+
@var LLK.ELLIPSOIDAL: Ellipsoidal lat-, longitude and heading C{bet}, C{omg}, C{alp} (L{Ang})
|
|
23
|
+
@var LLK.GEOCENTRIC: Geocentric lat-, longitude and heading C{phi}", C{lam}" and C{zet} (L{Ang})
|
|
24
|
+
@var LLK.GEOCENTRIC_X: Geocentric with pole along major X axis
|
|
25
|
+
@var LLK.GEODETIC: Geodetic lat-, longitude and heading C{phi}, C{lam} and C{zet} (L{Ang})
|
|
26
|
+
@var LLK.GEODETIC_X: Geodetic with pole along major X axis
|
|
27
|
+
@var LLK.GEODETIC_LON0: Geodetic lat-, longitude I{- lon0} and heading C{phi}, C{lam} and C{zet} (L{Ang})
|
|
28
|
+
@var LLK.GEOGRAPHIC = LLK.GEODETIC
|
|
29
|
+
@var LLK.PARAMETRIC: Parametric lat-, longitude and heading C{phi}', C{lam}' and C{zet} (L{Ang})
|
|
30
|
+
@var LLK.PARAMETRIC_X: Parametric with pole along major X axis
|
|
31
|
+
@var LLK.PLANETODETIC = LLK.GEODETIC
|
|
32
|
+
@var LLK.PLANETOCENTRIC = LLK.GEOCENTRIC
|
|
33
|
+
'''
|
|
34
|
+
# make sure int/int division yields float quotient, see .basics
|
|
35
|
+
from __future__ import division as _; del _ # noqa: E702 ;
|
|
36
|
+
|
|
37
|
+
# from pygeodesy.angles import Ang, isAng # _MODS
|
|
38
|
+
from pygeodesy.basics import map1, isscalar
|
|
39
|
+
from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, _EPS2e4, INT0, NAN, PI2, PI_3, PI4, \
|
|
40
|
+
_isfinite, float0_, _0_0, _1_0, _N_1_0, _4_0 # PYCHOK used!
|
|
41
|
+
# from pygeodesy.ellipsoids import Ellipsoid # _MODS
|
|
42
|
+
# from pygeodesy.elliptic import Elliptic # _MODS
|
|
43
|
+
# from pygeodesy.errors import _ValueError, _xkwds # from .formy
|
|
44
|
+
from pygeodesy.fmath import fmean_, hypot, norm2, sqrt0, fabs, sqrt
|
|
45
|
+
from pygeodesy.formy import elliperim, _ValueError, _xkwds
|
|
46
|
+
from pygeodesy.fsums import _Fsumf_, fsumf_, fsum1f_
|
|
47
|
+
from pygeodesy.interns import _a_, _b_, _c_, _inside_, _not_, _NOTEQUAL_, _null_, \
|
|
48
|
+
_outside_, _scale_, _SPACE_, _spherical_, _x_, _y_, _z_
|
|
49
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
50
|
+
from pygeodesy.named import _NamedEnumItem, _NamedTuple, _Pass
|
|
51
|
+
from pygeodesy.namedTuples import Vector4Tuple
|
|
52
|
+
from pygeodesy.props import Property_RO, property_doc_, property_RO, property_ROver
|
|
53
|
+
from pygeodesy.units import Degrees, Easting, Float, Height, Height_, Meter2, Meter3, \
|
|
54
|
+
Northing, Radius_, Scalar
|
|
55
|
+
from pygeodesy.utily import asin1
|
|
56
|
+
from pygeodesy.vector3d import _otherV3d, Vector3d
|
|
57
|
+
|
|
58
|
+
# from math import fabs, sqrt # from .fmath
|
|
59
|
+
|
|
60
|
+
__all__ = _ALL_LAZY.triaxials_bases
|
|
61
|
+
__version__ = '25.12.12'
|
|
62
|
+
|
|
63
|
+
_bet_ = 'bet' # PYCHOK shared
|
|
64
|
+
_llk_ = 'llk' # PYCHOK shared
|
|
65
|
+
_MAXIT = 33 # 20 # PYCHOK shared
|
|
66
|
+
_not_ordered_ = _not_('ordered')
|
|
67
|
+
_omg_ = 'omg' # PYCHOK shared
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Conformal5Tuple(_NamedTuple): # see .Forward4Tuple
|
|
71
|
+
'''5-Tuple C{(x, y, z, scale, llk)} with the easting C{x} and
|
|
72
|
+
northing C{y} projection, C{scale} or C{NAN} I{but with}
|
|
73
|
+
C{z=INT0} I{and kind} C{llk=LLK.CONFORMAL} I{always}.
|
|
74
|
+
'''
|
|
75
|
+
_Names_ = (_x_, _y_, _z_, _scale_, _llk_)
|
|
76
|
+
_Units_ = ( Easting, Northing, _Pass, Scalar, _Pass)
|
|
77
|
+
|
|
78
|
+
def __new__(cls, x, y, z=INT0, scale=NAN, llk=None, **kwds): # **iteration_name
|
|
79
|
+
args = x, y, (z or INT0), scale, (llk or LLK.CONFORMAL)
|
|
80
|
+
return _NamedTuple.__new__(cls, args, **kwds)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class _LLK(str):
|
|
84
|
+
'''(INTERNAL) Lat-/Longitude Kind.
|
|
85
|
+
'''
|
|
86
|
+
def __init__(self, llk): # aka C++ alt
|
|
87
|
+
self._X = bool(llk.endswith('_X'))
|
|
88
|
+
str.__init__(llk)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class LLK(object):
|
|
92
|
+
'''Enum-like C{Lat-/Longitude Kinds (LLK)}, see U{coord<https://GeographicLib.
|
|
93
|
+
SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>}.
|
|
94
|
+
'''
|
|
95
|
+
CONFORMAL = _LLK('CONFORMAL')
|
|
96
|
+
|
|
97
|
+
ELLIPSOIDAL = _LLK('ELLIPSOIDAL') # bet, omg, alp
|
|
98
|
+
GEOCENTRIC = _LLK('GEOCENTRIC') # phi2p, lam2p, zet
|
|
99
|
+
GEOCENTRIC_X = _LLK('GEOCENTRIC_X')
|
|
100
|
+
GEODETIC = _LLK('GEODETIC') # phi, lam, zet
|
|
101
|
+
GEODETIC_LON0 = _LLK('GEODETIC_LON0')
|
|
102
|
+
GEODETIC_X = _LLK('GEODETIC_X')
|
|
103
|
+
GEOGRAPHIC = GEODETIC
|
|
104
|
+
PARAMETRIC = _LLK('PARAMETRIC') # phi1p, lam1p, zet
|
|
105
|
+
PARAMETRIC_X = _LLK('PARAMETRIC_X')
|
|
106
|
+
PLANETODETIC = GEODETIC
|
|
107
|
+
PLANETOCENTRIC = GEOCENTRIC
|
|
108
|
+
|
|
109
|
+
_CENTRICS = (GEOCENTRIC, GEOCENTRIC_X, PLANETOCENTRIC)
|
|
110
|
+
_DETICS = (GEODETIC, GEODETIC_X, GEODETIC_LON0, GEOGRAPHIC, PLANETODETIC)
|
|
111
|
+
_METRICS = (PARAMETRIC, PARAMETRIC_X)
|
|
112
|
+
_NOIDAL = (None, ELLIPSOIDAL)
|
|
113
|
+
# _XCLUDE = (CONFORMAL, GEOGRAPHIC, PLANETOCENTRIC, PLANETODETIC)
|
|
114
|
+
|
|
115
|
+
def items(self):
|
|
116
|
+
for n, llk in LLK.__class__.__dict__.items():
|
|
117
|
+
if isinstance(llk, _LLK):
|
|
118
|
+
yield n, llk
|
|
119
|
+
|
|
120
|
+
def keys(self):
|
|
121
|
+
for n, _ in self.items():
|
|
122
|
+
yield n
|
|
123
|
+
|
|
124
|
+
def values(self):
|
|
125
|
+
for _, llk in self.items():
|
|
126
|
+
yield llk
|
|
127
|
+
|
|
128
|
+
LLK = LLK() # PYCHOK singleton
|
|
129
|
+
# del _LLK
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _HeightINT0(h):
|
|
133
|
+
return h if h is INT0 else Height(h=h)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class _UnOrderedTriaxialBase(_NamedEnumItem):
|
|
137
|
+
'''(INTERNAL) Base class for all I{unordered} triaxial classes.
|
|
138
|
+
'''
|
|
139
|
+
_ijk = _kji = None
|
|
140
|
+
_unordered = True
|
|
141
|
+
|
|
142
|
+
def __init__(self, a_triaxial, b=None, c=None, **name):
|
|
143
|
+
'''New I{unordered} C{Triaxial_}.
|
|
144
|
+
|
|
145
|
+
@arg a_triaxial: Large, C{X} semi-axis (C{scalar}, conventionally in
|
|
146
|
+
C{meter}) or an other L{Triaxial}, L{Triaxial_} or
|
|
147
|
+
L{TriaxialB} instance.
|
|
148
|
+
@kwarg b: Middle, C{Y} semi-axis (C{meter}, same units as B{C{a}}),
|
|
149
|
+
required if C{B{a_triaxial} is scalar}, ignored otherwise.
|
|
150
|
+
@kwarg c: Small, C{Z} semi-axis (C{meter}, like B{C{b}}).
|
|
151
|
+
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
152
|
+
|
|
153
|
+
@raise TriaxialError: Invalid semi-axis or -axes.
|
|
154
|
+
'''
|
|
155
|
+
try:
|
|
156
|
+
try:
|
|
157
|
+
a = a_triaxial
|
|
158
|
+
t = a._abc3
|
|
159
|
+
name = _xkwds(name, name=a.name)
|
|
160
|
+
except AttributeError:
|
|
161
|
+
t = Radius_(a=a), Radius_(b=b), Radius_(c=c)
|
|
162
|
+
except (TypeError, ValueError) as x:
|
|
163
|
+
raise TriaxialError(a=a, b=b, c=c, cause=x)
|
|
164
|
+
if name:
|
|
165
|
+
self.name = name
|
|
166
|
+
|
|
167
|
+
a, b, c = self._abc3 = t
|
|
168
|
+
if self._unordered: # == not isinstance(self, Triaxial)
|
|
169
|
+
s, _, t = sorted(t)
|
|
170
|
+
if not (_isfinite(t) and _isfinite(s) and s > 0):
|
|
171
|
+
raise TriaxialError(a=a, b=b, c=c) # txt=_invalid_
|
|
172
|
+
elif not (_isfinite(a) and a >= b >= c > 0): # see TriaxialB
|
|
173
|
+
raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
|
|
174
|
+
elif not (a > c and self._a2c2 > 0 and self.e2ac > 0):
|
|
175
|
+
raise TriaxialError(a=a, c=c, e2ac=self.e2ac, txt=_spherical_)
|
|
176
|
+
|
|
177
|
+
def __str__(self):
|
|
178
|
+
return self.toStr()
|
|
179
|
+
|
|
180
|
+
@Property_RO
|
|
181
|
+
def a(self):
|
|
182
|
+
'''Get the C{largest, x} semi-axis (C{meter}, conventionally).
|
|
183
|
+
'''
|
|
184
|
+
a, _, _ = self._abc3
|
|
185
|
+
return a
|
|
186
|
+
|
|
187
|
+
@Property_RO
|
|
188
|
+
def a2(self):
|
|
189
|
+
'''Get C{a**2}.
|
|
190
|
+
'''
|
|
191
|
+
return self.a**2
|
|
192
|
+
|
|
193
|
+
@Property_RO
|
|
194
|
+
def _a2b2(self):
|
|
195
|
+
'''(INTERNAL) Get C{a**2 - b**2} == E_sub_e**2.
|
|
196
|
+
'''
|
|
197
|
+
a, b, _ = self._abc3
|
|
198
|
+
d = a - b
|
|
199
|
+
return (d * (a + b)) if d else _0_0
|
|
200
|
+
|
|
201
|
+
@Property_RO
|
|
202
|
+
def _a2_b2(self):
|
|
203
|
+
'''(INTERNAL) Get C{(a / b)**2}.
|
|
204
|
+
'''
|
|
205
|
+
a, b, _ = self._abc3
|
|
206
|
+
return (a / b)**2 if a != b else _1_0
|
|
207
|
+
|
|
208
|
+
@Property_RO
|
|
209
|
+
def _a2b2c23(self):
|
|
210
|
+
'''(INTERNAL) Get 3-tuple C{(a**2, b**2, c**2)}.
|
|
211
|
+
'''
|
|
212
|
+
a, b, c = self._abc3
|
|
213
|
+
return self.a2, self.b2, self.c2
|
|
214
|
+
|
|
215
|
+
@Property_RO
|
|
216
|
+
def _ab_elliperim(self):
|
|
217
|
+
'''(INTERNAL) Get C{ab} ellipse' perimeter.
|
|
218
|
+
'''
|
|
219
|
+
a, b, _ = self._abc3
|
|
220
|
+
return elliperim(a, b)
|
|
221
|
+
|
|
222
|
+
@Property_RO
|
|
223
|
+
def _a2c2(self):
|
|
224
|
+
'''(INTERNAL) Get C{a**2 - c**2} == E_sub_x**2.
|
|
225
|
+
'''
|
|
226
|
+
a, _, c = self._abc3
|
|
227
|
+
d = a - c
|
|
228
|
+
return (d * (a + c)) if d else _0_0
|
|
229
|
+
|
|
230
|
+
@Property_RO
|
|
231
|
+
def area(self):
|
|
232
|
+
'''Get the surface area (C{meter} I{squared}).
|
|
233
|
+
'''
|
|
234
|
+
c, b, a = sorted(self._abc3)
|
|
235
|
+
return _OrderedTriaxialBase(a, b, c).area if a > c else \
|
|
236
|
+
Meter2(area=self.a2 * PI4) # a == c == b
|
|
237
|
+
|
|
238
|
+
def area_p(self, p=1.6075):
|
|
239
|
+
'''I{Approximate} the surface area (C{meter} I{squared}).
|
|
240
|
+
|
|
241
|
+
@kwarg p: Exponent (C{scalar} > 0), 1.6 for near-spherical or 1.5849625007
|
|
242
|
+
for "near-flat" triaxials.
|
|
243
|
+
|
|
244
|
+
@see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Approximate_formula>}.
|
|
245
|
+
'''
|
|
246
|
+
a, b, c = self._abc3
|
|
247
|
+
if a == b == c:
|
|
248
|
+
a *= a
|
|
249
|
+
else:
|
|
250
|
+
_p = pow
|
|
251
|
+
a = _p(fmean_(_p(a * b, p), _p(a * c, p), _p(b * c, p)), _1_0 / p)
|
|
252
|
+
return Meter2(area_p=a * PI4)
|
|
253
|
+
|
|
254
|
+
@Property_RO
|
|
255
|
+
def b(self):
|
|
256
|
+
'''Get the C{middle, y} semi-axis (C{meter}, same units as B{C{a}}).
|
|
257
|
+
'''
|
|
258
|
+
_, b, _ = self._abc3
|
|
259
|
+
return b
|
|
260
|
+
|
|
261
|
+
@Property_RO
|
|
262
|
+
def b2(self):
|
|
263
|
+
'''Get C{b**2}.
|
|
264
|
+
'''
|
|
265
|
+
return self.b**2
|
|
266
|
+
|
|
267
|
+
@Property_RO
|
|
268
|
+
def _b2_a2(self):
|
|
269
|
+
'''(INTERNAL) Get C{(b / a)**2}.
|
|
270
|
+
'''
|
|
271
|
+
a, b, _ = self._abc3
|
|
272
|
+
return (b / a)**2 if a != b else _1_0
|
|
273
|
+
|
|
274
|
+
@Property_RO
|
|
275
|
+
def _b2c2(self):
|
|
276
|
+
'''(INTERNAL) Get C{b**2 - c**2} == E_sub_y**2.
|
|
277
|
+
'''
|
|
278
|
+
_, b, c = self._abc3
|
|
279
|
+
d = b - c
|
|
280
|
+
return (d * (b + c)) if d else _0_0
|
|
281
|
+
|
|
282
|
+
@Property_RO
|
|
283
|
+
def _bc_elliperim(self):
|
|
284
|
+
'''(INTERNAL) Get C{bc} ellipse' perimeter.
|
|
285
|
+
'''
|
|
286
|
+
_, b, c = self._abc3
|
|
287
|
+
return elliperim(b, c)
|
|
288
|
+
|
|
289
|
+
@Property_RO
|
|
290
|
+
def c(self):
|
|
291
|
+
'''Get the C{smallest, z} semi-axis (C{meter}, same units as B{C{a}}).
|
|
292
|
+
'''
|
|
293
|
+
_, _, c = self._abc3
|
|
294
|
+
return c
|
|
295
|
+
|
|
296
|
+
@Property_RO
|
|
297
|
+
def c2(self):
|
|
298
|
+
'''Get C{c**2}.
|
|
299
|
+
'''
|
|
300
|
+
return self.c**2
|
|
301
|
+
|
|
302
|
+
@Property_RO
|
|
303
|
+
def _c2_a2(self):
|
|
304
|
+
'''(INTERNAL) Get C{(c / a)**2}.
|
|
305
|
+
'''
|
|
306
|
+
a, _, c = self._abc3
|
|
307
|
+
return (c / a)**2 if a != c else _1_0
|
|
308
|
+
|
|
309
|
+
@Property_RO
|
|
310
|
+
def _c2_b2(self):
|
|
311
|
+
'''(INTERNAL) Get C{(c / b)**2}.
|
|
312
|
+
'''
|
|
313
|
+
_, b, c = self._abc3
|
|
314
|
+
return (c / b)**2 if b != c else _1_0
|
|
315
|
+
|
|
316
|
+
@Property_RO
|
|
317
|
+
def e2ab(self):
|
|
318
|
+
'''Get the C{ab} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (b/a)**2}.
|
|
319
|
+
'''
|
|
320
|
+
return Float(e2ab=(_1_0 - self._b2_a2) or _0_0)
|
|
321
|
+
|
|
322
|
+
# _1e2ab = _b2_a2 # == C{1 - e2ab} == C{(b/a)**2}
|
|
323
|
+
|
|
324
|
+
@Property_RO
|
|
325
|
+
def e2ac(self):
|
|
326
|
+
'''Get the C{ac} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/a)**2}.
|
|
327
|
+
'''
|
|
328
|
+
return Float(e2ac=(_1_0 - self._c2_a2) or _0_0)
|
|
329
|
+
|
|
330
|
+
# _1e2ac = _c2_a2 # == C{1 - e2ac} == C{(c/a)**2}
|
|
331
|
+
|
|
332
|
+
@Property_RO
|
|
333
|
+
def e2bc(self):
|
|
334
|
+
'''Get the C{bc} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/b)**2}.
|
|
335
|
+
'''
|
|
336
|
+
return Float(e2bc=(_1_0 - self._c2_b2) or _0_0)
|
|
337
|
+
|
|
338
|
+
# _1e2bc = _c2_b2 # == C{1 - e2bc} == C{(c/b)**2}
|
|
339
|
+
|
|
340
|
+
@property_ROver
|
|
341
|
+
def _Ellipsoid(self):
|
|
342
|
+
'''(INTERNAL) Get class L{Ellipsoid}, I{once}.
|
|
343
|
+
'''
|
|
344
|
+
return _MODS.ellipsoids.Ellipsoid # overwrite property_ROver
|
|
345
|
+
|
|
346
|
+
@property_ROver
|
|
347
|
+
def _Elliptic(self):
|
|
348
|
+
'''(INTERNAL) Get class L{Elliptic}, I{once}.
|
|
349
|
+
'''
|
|
350
|
+
return _MODS.elliptic.Elliptic # overwrite property_ROver
|
|
351
|
+
|
|
352
|
+
def hartzell4(self, pov, los=False, **name):
|
|
353
|
+
'''Compute the intersection of this triaxial's surface with a Line-Of-Sight
|
|
354
|
+
from a Point-Of-View in space.
|
|
355
|
+
|
|
356
|
+
@see: Function L{hartzell4<triaxials.triaxial5.hartzell4>} for further details.
|
|
357
|
+
'''
|
|
358
|
+
return self._triaxials_triaxial5.hartzell4(pov, los=los, tri_biax=self, **name)
|
|
359
|
+
|
|
360
|
+
def height4(self, x_xyz, y=None, z=None, normal=True, eps=EPS, **name):
|
|
361
|
+
'''Compute the projection on and the height above or below this triaxial's surface.
|
|
362
|
+
|
|
363
|
+
@see: Function L{height4<triaxials.triaxial5.height4>} for further details.
|
|
364
|
+
'''
|
|
365
|
+
m = self._triaxials_triaxial5
|
|
366
|
+
return m.height4(x_xyz, y=y, z=z, tri_biax=self, normal=normal, eps=eps, **name)
|
|
367
|
+
|
|
368
|
+
@Property_RO
|
|
369
|
+
def isOrdered(self):
|
|
370
|
+
'''Is this triaxial I{ordered} and I{not spherical} (C{bool})?
|
|
371
|
+
'''
|
|
372
|
+
a, b, c = self._abc3
|
|
373
|
+
return bool(a >= b > c) # b > c!
|
|
374
|
+
|
|
375
|
+
@Property_RO
|
|
376
|
+
def isSpherical(self):
|
|
377
|
+
'''Is this triaxial I{spherical} (C{Radius} or INT0)?
|
|
378
|
+
'''
|
|
379
|
+
a, b, c = self._abc3
|
|
380
|
+
return a if a == b == c else INT0
|
|
381
|
+
|
|
382
|
+
def _norm2(self, s, c, *a):
|
|
383
|
+
'''(INTERNAL) Normalize C{s} and C{c} iff not already.
|
|
384
|
+
'''
|
|
385
|
+
if fabs(_hypot2_1(s, c)) > EPS02:
|
|
386
|
+
s, c = norm2(s, c)
|
|
387
|
+
if a:
|
|
388
|
+
s, c = norm2(s * self.b, c * a[0])
|
|
389
|
+
return float0_(s, c)
|
|
390
|
+
|
|
391
|
+
def normal3d(self, x_xyz, y=None, z=None, length=_1_0):
|
|
392
|
+
'''Get a 3-D vector I{on and perpendicular to} this triaxial's surface.
|
|
393
|
+
|
|
394
|
+
@arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
|
|
395
|
+
L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
|
|
396
|
+
@kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
|
|
397
|
+
otherwise.
|
|
398
|
+
@kwarg z: Z component (C{scalar}), like B{C{y}}.
|
|
399
|
+
@kwarg length: Optional, signed length in out-/inward direction (C{scalar}).
|
|
400
|
+
|
|
401
|
+
@return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing out-
|
|
402
|
+
or inward for postive respectively negative B{C{length}}.
|
|
403
|
+
|
|
404
|
+
@raise TriaxialError: Zero length cartesian or vector.
|
|
405
|
+
|
|
406
|
+
@note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
|
|
407
|
+
use method L{Triaxial.sideOf} to validate.
|
|
408
|
+
|
|
409
|
+
@see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.
|
|
410
|
+
'''
|
|
411
|
+
# n = 2 * (x / a2, y / b2, z / c2)
|
|
412
|
+
# == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered
|
|
413
|
+
# == 2 * (x, y / _b2_a2, z / _c2_a2) / a2
|
|
414
|
+
# == unit(x, y / _b2_a2, z / _c2_a2).times(length)
|
|
415
|
+
x, y, z = _otherV3d_(x_xyz, y, z).xyz3
|
|
416
|
+
n = Vector3d(x, y / self._b2_a2,
|
|
417
|
+
z / self._c2_a2, name__=self.normal3d)
|
|
418
|
+
u = n.length
|
|
419
|
+
if u < EPS0:
|
|
420
|
+
raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
|
|
421
|
+
return n.times(length / u)
|
|
422
|
+
|
|
423
|
+
def normal4(self, x_xyz, y=None, z=None, height=0, normal=True):
|
|
424
|
+
'''Compute a cartesian at a height above or below this triaxial's surface.
|
|
425
|
+
|
|
426
|
+
@arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
|
|
427
|
+
L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
|
|
428
|
+
@kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
|
|
429
|
+
otherwise.
|
|
430
|
+
@kwarg z: Z component (C{scalar}), like B{C{y}}.
|
|
431
|
+
@kwarg height: The signed height (C{scalar}, same units as the triaxial axes).
|
|
432
|
+
@kwarg normal: If C{True}, the B{C{height}} is I{perpendicular, plumb} to the
|
|
433
|
+
triaxial's surface, otherwise C{radially} to the center of this
|
|
434
|
+
triaxial (C{bool}).
|
|
435
|
+
@kwarg name: Optional C{B{name}="normal4"} (C{str}).
|
|
436
|
+
|
|
437
|
+
@return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x},
|
|
438
|
+
C{y} and C{z} and C{h} the I{signed, normal distance} to the triaxial's
|
|
439
|
+
surface in C{meter}, conventionally. Positive C{h} indicates, the
|
|
440
|
+
cartesian is outside the triaxial, negative C{h} means inside.
|
|
441
|
+
|
|
442
|
+
@raise TriaxialError: Zero length cartesian or vector.
|
|
443
|
+
|
|
444
|
+
@note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
|
|
445
|
+
use method L{Triaxial.sideOf} to validate.
|
|
446
|
+
|
|
447
|
+
@see: Methods L{Triaxial.normal3d} and L{Triaxial.height4}.
|
|
448
|
+
'''
|
|
449
|
+
v, h = _otherV3d(x_xyz, y, z), Height_(height, low=None)
|
|
450
|
+
if h:
|
|
451
|
+
if v.length < EPS0:
|
|
452
|
+
raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
|
|
453
|
+
if normal:
|
|
454
|
+
n = self.normal3d(v, length=h)
|
|
455
|
+
h = n.length
|
|
456
|
+
n += v
|
|
457
|
+
else:
|
|
458
|
+
h = h / v.length
|
|
459
|
+
n = v.times(h + _1_0)
|
|
460
|
+
else:
|
|
461
|
+
n = v
|
|
462
|
+
return Vector4Tuple(n.x, n.y, n.z, h, name__=self.normal4)
|
|
463
|
+
|
|
464
|
+
def _order3(self, *abc, **reverse): # reverse=False
|
|
465
|
+
'''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.
|
|
466
|
+
|
|
467
|
+
@return: 3-Tuple C{(a, b, c)} ordered by or un-ordered
|
|
468
|
+
(reverse-ordered) C{ijk} if C{B{reverse}=True}.
|
|
469
|
+
'''
|
|
470
|
+
ijk = self._order_ijk(**reverse)
|
|
471
|
+
return _getitems(abc, *ijk) if ijk else abc
|
|
472
|
+
|
|
473
|
+
def _order3d(self, v, **reverse): # reverse=False
|
|
474
|
+
'''(INTERNAL) Un-/Order a C{Vector3d}.
|
|
475
|
+
|
|
476
|
+
@return: Vector3d(x, y, z) un-/ordered.
|
|
477
|
+
'''
|
|
478
|
+
ijk = self._order_ijk(**reverse)
|
|
479
|
+
return v.classof(*_getitems(v.xyz3, *ijk)) if ijk else v
|
|
480
|
+
|
|
481
|
+
@Property_RO
|
|
482
|
+
def _ordered4(self):
|
|
483
|
+
'''(INTERNAL) Helper for C{_hartzell3} and C{_plumbTo5}.
|
|
484
|
+
'''
|
|
485
|
+
def _order2(reverse, a, b, c):
|
|
486
|
+
'''(INTERNAL) Un-Order C{a}, C{b} and C{c}.
|
|
487
|
+
|
|
488
|
+
@return: 2-Tuple C{((a, b, c), ijk)} with C{a} >= C{b} >= C{c}
|
|
489
|
+
and C{ijk} a 3-tuple with the initial indices.
|
|
490
|
+
'''
|
|
491
|
+
i, j, k = range(3)
|
|
492
|
+
if a < b:
|
|
493
|
+
a, b, i, j = b, a, j, i
|
|
494
|
+
if a < c:
|
|
495
|
+
a, c, i, k = c, a, k, i
|
|
496
|
+
if b < c:
|
|
497
|
+
b, c, j, k = c, b, k, j
|
|
498
|
+
# reverse (k, j, i) since (a, b, c) is reversed-sorted
|
|
499
|
+
ijk = (k, j, i) if reverse else (None if i < j < k else (i, j, k))
|
|
500
|
+
return (a, b, c), ijk
|
|
501
|
+
|
|
502
|
+
abc, T = self._abc3, self
|
|
503
|
+
if not self.isOrdered:
|
|
504
|
+
abc, ijk = _order2(False, *abc)
|
|
505
|
+
if ijk:
|
|
506
|
+
_, kji = _order2(True, *ijk)
|
|
507
|
+
T = _UnOrderedTriaxialBase(*abc)
|
|
508
|
+
T._ijk, T._kji = ijk, kji
|
|
509
|
+
return abc + (T,)
|
|
510
|
+
|
|
511
|
+
def _order_ijk(self, reverse=False):
|
|
512
|
+
'''(INTERNAL) Get the un-/order indices.
|
|
513
|
+
'''
|
|
514
|
+
return self._kji if reverse else self._ijk
|
|
515
|
+
|
|
516
|
+
def _radialTo3(self, sbeta, cbeta, somega, comega):
|
|
517
|
+
'''(INTERNAL) I{Unordered} helper for C{.height4}.
|
|
518
|
+
'''
|
|
519
|
+
def _rphi(a, b, sphi, cphi):
|
|
520
|
+
# <https://WikiPedia.org/wiki/Ellipse#Polar_form_relative_to_focus>
|
|
521
|
+
# polar form: radius(phi) = a * b / hypot(a * sphi, b * cphi)
|
|
522
|
+
return (b / hypot(sphi, b / a * cphi)) if a > b else (
|
|
523
|
+
(a / hypot(cphi, a / b * sphi)) if a < b else a)
|
|
524
|
+
|
|
525
|
+
sa, ca = self._norm2(sbeta, cbeta)
|
|
526
|
+
sb, cb = self._norm2(somega, comega)
|
|
527
|
+
|
|
528
|
+
a, b, c = self._abc3
|
|
529
|
+
if a != b:
|
|
530
|
+
a = _rphi(a, b, sb, cb)
|
|
531
|
+
if a != c:
|
|
532
|
+
c = _rphi(a, c, sa, ca)
|
|
533
|
+
t = c * ca
|
|
534
|
+
return (t * cb), (t * sb), (c * sa)
|
|
535
|
+
|
|
536
|
+
def sideOf(self, x_xyz, y=None, z=None, eps=EPS4):
|
|
537
|
+
'''Is a cartesian on, above or below the surface of this triaxial?
|
|
538
|
+
|
|
539
|
+
@arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
|
|
540
|
+
L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
|
|
541
|
+
@kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
|
|
542
|
+
ignored otherwise.
|
|
543
|
+
@kwarg z: Z component (C{scalar}), like B{C{y}}.
|
|
544
|
+
@kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).
|
|
545
|
+
|
|
546
|
+
@return: C{INT0} if C{(B{x}, B{y}, B{z})} is near this triaxial's surface
|
|
547
|
+
within tolerance B{C{eps}}, otherwise the signed, radial distance
|
|
548
|
+
I{squared} (C{float}), nega-/positive for in- respectively outside
|
|
549
|
+
this triaxial.
|
|
550
|
+
|
|
551
|
+
@see: Methods L{Triaxial.height4} and L{Triaxial.normal3d}.
|
|
552
|
+
'''
|
|
553
|
+
v = _otherV3d_(x_xyz, y, z)
|
|
554
|
+
s = fsumf_(_N_1_0, *map(_over02, v.xyz3, self._abc3))
|
|
555
|
+
return INT0 if fabs(s) < eps else s
|
|
556
|
+
|
|
557
|
+
def _sideOn(self, v, eps=_EPS2e4):
|
|
558
|
+
s = self.sideOf(v.xyz, eps=eps)
|
|
559
|
+
if s: # PYCHOK no cover
|
|
560
|
+
t = _SPACE_((_inside_ if s < 0 else _outside_), self.toRepr())
|
|
561
|
+
raise TriaxialError(eps=eps, sideOf=s, x=v.x, y=v.y, z=v.z, txt=t)
|
|
562
|
+
return s
|
|
563
|
+
|
|
564
|
+
def toEllipsoid(self, **name):
|
|
565
|
+
'''Convert this triaxial to a I{biaxial} L{Ellipsoid}, provided 2 axes match.
|
|
566
|
+
|
|
567
|
+
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
568
|
+
|
|
569
|
+
@return: An L{Ellipsoid} with north along this C{Z} axis if C{a == b},
|
|
570
|
+
this C{Y} axis if C{a == c} or this C{X} axis if C{b == c}.
|
|
571
|
+
|
|
572
|
+
@raise TriaxialError: This C{a != b}, C{b != c} and C{c != a}.
|
|
573
|
+
|
|
574
|
+
@see: Method L{Ellipsoid.toTriaxial}.
|
|
575
|
+
'''
|
|
576
|
+
a, b, c = self._abc3
|
|
577
|
+
if a == b:
|
|
578
|
+
b = c # N = c-Z
|
|
579
|
+
elif b == c: # N = a-X
|
|
580
|
+
a, b = b, a
|
|
581
|
+
elif a != c: # N = b-Y
|
|
582
|
+
t = _SPACE_(_a_, _NOTEQUAL_, _b_, _NOTEQUAL_, _c_)
|
|
583
|
+
raise TriaxialError(a=a, b=b, c=c, txt=t)
|
|
584
|
+
return self._Ellipsoid(a, b=b, name=self._name__(name))
|
|
585
|
+
|
|
586
|
+
toBiaxial = toEllipsoid
|
|
587
|
+
|
|
588
|
+
def toStr(self, prec=9, **name): # PYCHOK signature
|
|
589
|
+
'''Return this C{Triaxial} as a string.
|
|
590
|
+
|
|
591
|
+
@kwarg prec: Precision, number of decimal digits (0..9).
|
|
592
|
+
@kwarg name: Optional name (C{str}), to override or C{None}
|
|
593
|
+
to exclude this triaxial's name.
|
|
594
|
+
|
|
595
|
+
@return: This C{Triaxial}'s attributes (C{str}).
|
|
596
|
+
'''
|
|
597
|
+
T = _UnOrderedTriaxialBase
|
|
598
|
+
C = self._triaxials_triaxial3.Triaxial3B
|
|
599
|
+
if isinstance(self, C):
|
|
600
|
+
t = T.b, C.e2, C.k2, C.kp2
|
|
601
|
+
else:
|
|
602
|
+
t = T.a, # props
|
|
603
|
+
C = self._triaxials_triaxial5.ConformalSphere
|
|
604
|
+
t += (C.ab, C.bc) if isinstance(self, C) else (T.b, T.c)
|
|
605
|
+
C = _Triaxial3Base
|
|
606
|
+
t += (C.k2, C.kp2) if isinstance(self, C) else \
|
|
607
|
+
(T.e2ab, T.e2bc, T.e2ac)
|
|
608
|
+
for C in (self._triaxials_triaxial5.Conformal,
|
|
609
|
+
self._triaxials_conformal3.Conformal3):
|
|
610
|
+
if isinstance(self, C):
|
|
611
|
+
t += C.xyQ2,
|
|
612
|
+
break
|
|
613
|
+
t += T.volume, T.area
|
|
614
|
+
return self._instr(area_p=self.area_p(), prec=prec, props=t, **name)
|
|
615
|
+
|
|
616
|
+
@property_ROver
|
|
617
|
+
def _triaxials_conformal3(self):
|
|
618
|
+
'''(INTERNAL) Get module L{pygeodesy.triaxials.conformal3}, I{once}.
|
|
619
|
+
'''
|
|
620
|
+
return _MODS.triaxials.conformal3 # overwrite property_ROver
|
|
621
|
+
|
|
622
|
+
@property_ROver
|
|
623
|
+
def _triaxials_triaxial3(self):
|
|
624
|
+
'''(INTERNAL) Get module L{pygeodesy.triaxials.triaxial3}, I{once}.
|
|
625
|
+
'''
|
|
626
|
+
return _MODS.triaxials.triaxial3 # overwrite property_ROver
|
|
627
|
+
|
|
628
|
+
@property_ROver
|
|
629
|
+
def _triaxials_triaxial5(self):
|
|
630
|
+
'''(INTERNAL) Get module L{pygeodesy.triaxials.triaxial5}, I{once}.
|
|
631
|
+
'''
|
|
632
|
+
return _MODS.triaxials.triaxial5 # overwrite property_ROver
|
|
633
|
+
|
|
634
|
+
@Property_RO
|
|
635
|
+
def unOrdered(self):
|
|
636
|
+
'''Is this triaxial I{un-ordered} and I{not spherical} (C{bool})?
|
|
637
|
+
'''
|
|
638
|
+
return not (self.isOrdered or bool(self.isSpherical))
|
|
639
|
+
|
|
640
|
+
@Property_RO
|
|
641
|
+
def volume(self):
|
|
642
|
+
'''Get the volume (C{meter**3}), M{4 / 3 * PI * a * b * c}.
|
|
643
|
+
'''
|
|
644
|
+
a, b, c = self._abc3
|
|
645
|
+
return Meter3(volume=a * b * c * PI_3 * _4_0)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class _OrderedTriaxialBase(_UnOrderedTriaxialBase):
|
|
649
|
+
'''(INTERNAL) Base class for all I{ordered} triaxial classes.
|
|
650
|
+
'''
|
|
651
|
+
_unordered = False
|
|
652
|
+
|
|
653
|
+
def __init__(self, a_triaxial, b=None, c=None, **name):
|
|
654
|
+
'''New I{ordered} L{Triaxial}, L{Triaxial3}, L{Conformal} or L{Conformal3}.
|
|
655
|
+
|
|
656
|
+
@arg a_triaxial: Largest semi-axis (C{scalar}, conventionally in C{meter})
|
|
657
|
+
or an other L{Triaxial} or L{Triaxial_} instance.
|
|
658
|
+
@kwarg b: Middle semi-axis (C{meter}, same units as B{C{a}}), required
|
|
659
|
+
if C{B{a_triaxial} is scalar}, ignored otherwise.
|
|
660
|
+
@kwarg c: Smallest semi-axis (C{meter}, like B{C{b}}).
|
|
661
|
+
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
662
|
+
|
|
663
|
+
@note: The semi-axes must be ordered as C{B{a} >= B{b} >= B{c} > 0} and
|
|
664
|
+
must be ellipsoidal, C{B{a} > B{c}}.
|
|
665
|
+
|
|
666
|
+
@raise TriaxialError: Semi-axes unordered, spherical or invalid.
|
|
667
|
+
'''
|
|
668
|
+
_UnOrderedTriaxialBase.__init__(self, a_triaxial, b=b, c=c, **name)
|
|
669
|
+
|
|
670
|
+
@Property_RO
|
|
671
|
+
def _a2b2_a2c2(self):
|
|
672
|
+
'''@see: Methods C{.forwardBetaOmega} and property C{._k2E_kp2E}.
|
|
673
|
+
'''
|
|
674
|
+
s = self._a2c2
|
|
675
|
+
if s:
|
|
676
|
+
s = self._a2b2 / s
|
|
677
|
+
return s or _0_0
|
|
678
|
+
|
|
679
|
+
@Property_RO
|
|
680
|
+
def area(self):
|
|
681
|
+
'''Get the surface area (C{meter} I{squared}).
|
|
682
|
+
|
|
683
|
+
@see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Surface_area>}.
|
|
684
|
+
'''
|
|
685
|
+
a, b, c = self._abc3
|
|
686
|
+
if a != b:
|
|
687
|
+
kp2, k2 = self._k2E_kp2E # swapped!
|
|
688
|
+
aE = self._Elliptic(k2, _0_0, kp2, _1_0)
|
|
689
|
+
c2 = self._c2_a2 # cos(phi)**2 = (c/a)**2
|
|
690
|
+
s = sqrt(self.e2ac) # sin(phi)**2 = 1 - c2
|
|
691
|
+
r = asin1(s) # phi = atan2(sqrt(c2), s)
|
|
692
|
+
b *= fsum1f_(aE.fE(r) * s, (c / a) * (c / b),
|
|
693
|
+
aE.fF(r) * c2 / s)
|
|
694
|
+
a = Meter2(area=a * b * PI2)
|
|
695
|
+
else: # a == b > c
|
|
696
|
+
a = self._Ellipsoid(a, b=c).areax
|
|
697
|
+
return a
|
|
698
|
+
|
|
699
|
+
@Property_RO
|
|
700
|
+
def _k2E_kp2E(self):
|
|
701
|
+
'''(INTERNAL) Get elliptic C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.area}.
|
|
702
|
+
'''
|
|
703
|
+
# k2 = a2b2 / a2c2 * c2_b2
|
|
704
|
+
# kp2 = b2c2 / a2c2 * a2_b2
|
|
705
|
+
# b2 = b**2
|
|
706
|
+
# xE = Elliptic(k2, -a2b2 / b2, kp2, a2_b2)
|
|
707
|
+
# yE = Elliptic(kp2, +b2c2 / b2, k2, c2_b2)
|
|
708
|
+
# aE = Elliptic(kp2, 0, k2, 1)
|
|
709
|
+
k2 = (self._c2_b2 * self._a2b2_a2c2) or _0_0
|
|
710
|
+
kp2 = (self._a2_b2 * self._b2c2 / self._a2c2) if k2 else _1_0
|
|
711
|
+
return k2, kp2
|
|
712
|
+
|
|
713
|
+
def _radialTo3(self, sbeta, cbeta, somega, comega):
|
|
714
|
+
'''(INTERNAL) Convert I{ellipsoidal} lat- C{beta} and longitude
|
|
715
|
+
C{omega} to a cartesian I{on this triaxial's surface}, also
|
|
716
|
+
I{ordered} helper for C{.height4 with normal=False}.
|
|
717
|
+
'''
|
|
718
|
+
sa, ca = self._norm2(sbeta, cbeta)
|
|
719
|
+
sb, cb = self._norm2(somega, comega)
|
|
720
|
+
|
|
721
|
+
b2_a2 = self._b2_a2 # == (b/a)**2
|
|
722
|
+
c2_a2 = -self._c2_a2 # == -(c/a)**2
|
|
723
|
+
a2c2_a2 = self. e2ac # (a**2 - c**2) / a**2 == 1 - (c/a)**2
|
|
724
|
+
|
|
725
|
+
x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)
|
|
726
|
+
z2 = _Fsumf_(c2_a2, sb**2, b2_a2 * cb**2).fover(a2c2_a2)
|
|
727
|
+
|
|
728
|
+
x, y, z = self._abc3
|
|
729
|
+
x *= cb * _sqrt0(x2)
|
|
730
|
+
y *= ca * sb
|
|
731
|
+
z *= sa * _sqrt0(z2)
|
|
732
|
+
return x, y, z
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class _Triaxial3Base(_OrderedTriaxialBase):
|
|
736
|
+
'''(INTERNAL) Base class for I{unordered} triaxialC{3} classes.
|
|
737
|
+
'''
|
|
738
|
+
_e2_k2_kp2 = None
|
|
739
|
+
_Lon0 = None
|
|
740
|
+
|
|
741
|
+
@Property_RO
|
|
742
|
+
def e2(self):
|
|
743
|
+
'''Get the I{squared eccentricity} (C{scalar}), M{(a**2 - c**2) / b**2}.
|
|
744
|
+
'''
|
|
745
|
+
if self._e2_k2_kp2:
|
|
746
|
+
e2, _, _ = self._e2_k2_kp2
|
|
747
|
+
else:
|
|
748
|
+
e2 = self._a2c2 / self.b2
|
|
749
|
+
return Float(e2=e2)
|
|
750
|
+
|
|
751
|
+
def _init_abc3_e2_k2_kp2(self, b, e2, k2, kp2, **name):
|
|
752
|
+
'''(INTERNAL) C{Triaxial3B.__init__}.
|
|
753
|
+
'''
|
|
754
|
+
if name:
|
|
755
|
+
self.name = name
|
|
756
|
+
s = k2 + kp2
|
|
757
|
+
if s > 0 and s != _1_0:
|
|
758
|
+
k2 = k2 / s # /= chokes PyChecker
|
|
759
|
+
kp2 = kp2 / s
|
|
760
|
+
if min(e2, k2, kp2) < 0 or not s > 0:
|
|
761
|
+
raise TriaxialError(e2=e2, k2=k2, kp2=kp2)
|
|
762
|
+
if e2:
|
|
763
|
+
a = Radius_(a=_sqrt0(_1_0 + e2 * kp2) * b) if kp2 else b
|
|
764
|
+
c = Radius_(c=_sqrt0(_1_0 - e2 * k2) * b) if k2 else b
|
|
765
|
+
else: # spherical
|
|
766
|
+
a = c = b
|
|
767
|
+
if not (_isfinite(b) and a >= b >= c > 0):
|
|
768
|
+
raise TriaxialError(b=b, a=a, c=c, e2=e2,
|
|
769
|
+
k2=k2, kp2=kp2, txt=_not_ordered_)
|
|
770
|
+
self._abc3 = a, b, c
|
|
771
|
+
self._e2_k2_kp2 = e2, k2, kp2
|
|
772
|
+
|
|
773
|
+
@property_RO
|
|
774
|
+
def isBiaxial(self):
|
|
775
|
+
'''Is this triaxial I{biaxial} (C{bool}), C{a} == C{b} or C{b} == C{c}?
|
|
776
|
+
'''
|
|
777
|
+
return self.isOblate or self.isProlate
|
|
778
|
+
|
|
779
|
+
@property_RO
|
|
780
|
+
def isOblate(self):
|
|
781
|
+
'''Is this triaxial I{oblate} (C{bool}), C{a} == C{b}?
|
|
782
|
+
'''
|
|
783
|
+
return bool(self.kp2 == 0)
|
|
784
|
+
|
|
785
|
+
@property_RO
|
|
786
|
+
def isProlate(self):
|
|
787
|
+
'''Is this triaxial I{prolate} (C{bool}), C{b} == C{c}?
|
|
788
|
+
'''
|
|
789
|
+
return bool(self.k2 == 0)
|
|
790
|
+
|
|
791
|
+
@Property_RO
|
|
792
|
+
def _k_kp(self):
|
|
793
|
+
'''(INTERNAL) Get the oblate C{k} and prolate C{kp} parameters.
|
|
794
|
+
'''
|
|
795
|
+
return map1(_sqrt0, *self._k2_kp2)
|
|
796
|
+
|
|
797
|
+
@Property_RO
|
|
798
|
+
def k2(self):
|
|
799
|
+
'''(INTERNAL) Get the oblate C{k2} parameter I{squared}.
|
|
800
|
+
'''
|
|
801
|
+
k2, _ = self._k2_kp2
|
|
802
|
+
return k2
|
|
803
|
+
|
|
804
|
+
@Property_RO
|
|
805
|
+
def _k2_kp2(self):
|
|
806
|
+
'''(INTERNAL) Get the oblate C{k2} and prolate C{kp2} parameters I{squared}.
|
|
807
|
+
'''
|
|
808
|
+
if self._e2_k2_kp2:
|
|
809
|
+
_, k2, kp2 = self._e2_k2_kp2
|
|
810
|
+
else:
|
|
811
|
+
s = self._a2c2
|
|
812
|
+
k2 = (self._b2c2 / s) if s else _1_0
|
|
813
|
+
kp2 = (self._a2b2 / s) if s else _0_0
|
|
814
|
+
return k2, kp2
|
|
815
|
+
|
|
816
|
+
@Property_RO
|
|
817
|
+
def kp2(self):
|
|
818
|
+
'''(INTERNAL) Get the prolate C{kp2} parameter I{squared}.
|
|
819
|
+
'''
|
|
820
|
+
_, kp2 = self._k2_kp2
|
|
821
|
+
return kp2
|
|
822
|
+
|
|
823
|
+
@Property_RO
|
|
824
|
+
def _lcc23(self):
|
|
825
|
+
return self._a2c2, self._b2c2, _0_0
|
|
826
|
+
|
|
827
|
+
@property_doc_(" longitude of the I{earth}'s major semi-axis C{a}, (L{Ang}), Karney's C{Triaxial_Earth_lon0}.")
|
|
828
|
+
def Lon0(self):
|
|
829
|
+
if self._Lon0 is None:
|
|
830
|
+
self.Lon0 = -(1493 / 100) if self.name.startswith('WGS84_3') else 0
|
|
831
|
+
return self._Lon0
|
|
832
|
+
|
|
833
|
+
@Lon0.setter # PYCHOK setter!
|
|
834
|
+
def Lon0(self, lon0):
|
|
835
|
+
m = _MODS.angles
|
|
836
|
+
d = lon0.degrees if m.isAng(lon0) else Degrees(lon0)
|
|
837
|
+
n = _Triaxial3Base.Lon0.name
|
|
838
|
+
self._Lon0 = m.Ang.fromDegrees(d, name=n)
|
|
839
|
+
|
|
840
|
+
@Property_RO
|
|
841
|
+
def _xE(self):
|
|
842
|
+
'''(INTERNAL) Get the x-elliptic function.
|
|
843
|
+
'''
|
|
844
|
+
return self._xyE(self.e2, self.k2, self.kp2)
|
|
845
|
+
|
|
846
|
+
def _xyE(self, e2, k2, kp2):
|
|
847
|
+
'''(INTERNAL) Helper for C{._xE} and C{._yE}.
|
|
848
|
+
'''
|
|
849
|
+
if e2:
|
|
850
|
+
a2 = -kp2 * e2
|
|
851
|
+
ap2 = _1_0 - a2
|
|
852
|
+
kp2 *= _1_0 - k2 * e2
|
|
853
|
+
k2 *= ap2
|
|
854
|
+
else:
|
|
855
|
+
a2, ap2 = _0_0, _1_0
|
|
856
|
+
return self._Elliptic(kp2, a2, k2, ap2)
|
|
857
|
+
|
|
858
|
+
@Property_RO
|
|
859
|
+
def _yE(self):
|
|
860
|
+
'''(INTERNAL) Get the y-elliptic function.
|
|
861
|
+
'''
|
|
862
|
+
return self._xyE(-self.e2, self.kp2, self.k2)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
class TriaxialError(_ValueError):
|
|
866
|
+
'''Raised for any cartesian or conformal triaxial issues.
|
|
867
|
+
'''
|
|
868
|
+
pass # ...
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _getitems(items, *indices):
|
|
872
|
+
'''(INTERNAL) Get the C{items} at the given I{indices}.
|
|
873
|
+
|
|
874
|
+
@return: C{Type(items[i] for i in indices)} with
|
|
875
|
+
C{Type = type(items)}, any C{type} having
|
|
876
|
+
the special method C{__getitem__}.
|
|
877
|
+
'''
|
|
878
|
+
return type(items)(map(items.__getitem__, indices))
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def _hypot2_1(x, y, z=0):
|
|
882
|
+
'''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x), fabs(y),
|
|
883
|
+
fabs(z))} rarely greater than 1.0.
|
|
884
|
+
'''
|
|
885
|
+
return fsumf_(_N_1_0, x*x, y*y, z*z)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _otherV3d_(x_xyz, y, z, **name):
|
|
889
|
+
'''(INTERNAL) Get a Vector3d from C{x_xyz}, C{y} and C{z}.
|
|
890
|
+
'''
|
|
891
|
+
return Vector3d(x_xyz, y, z, **name) if isscalar(x_xyz) else \
|
|
892
|
+
_otherV3d(x_xyz=x_xyz, **name)
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
def _over0(p, q):
|
|
896
|
+
'''(INTERNAL) Return C{p / q} or C{0}.
|
|
897
|
+
'''
|
|
898
|
+
return (p / q) if q > fabs(p) else _0_0
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def _over02(p, q):
|
|
902
|
+
'''(INTERNAL) Return C{(p / q)**2} or C{0}.
|
|
903
|
+
'''
|
|
904
|
+
return (p / q)**2 if p and q else _0_0
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _sqrt0(x):
|
|
908
|
+
'''(INTERNAL) C{sqrt0} with C{TriaxialError}.
|
|
909
|
+
'''
|
|
910
|
+
return sqrt0(x, Error=TriaxialError)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
__all__ += _ALL_DOCS(_OrderedTriaxialBase, _Triaxial3Base, _UnOrderedTriaxialBase)
|
|
914
|
+
|
|
915
|
+
# **) MIT License
|
|
916
|
+
#
|
|
917
|
+
# Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
918
|
+
#
|
|
919
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
920
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
921
|
+
# to deal in the Software without restriction, including without limitation
|
|
922
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
923
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
924
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
925
|
+
#
|
|
926
|
+
# The above copyright notice and this permission notice shall be included
|
|
927
|
+
# in all copies or substantial portions of the Software.
|
|
928
|
+
#
|
|
929
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
930
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
931
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
932
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
933
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
934
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
935
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|