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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. pygeodesy/__init__.py +25 -12
  2. pygeodesy/__main__.py +1 -1
  3. pygeodesy/albers.py +1 -1
  4. pygeodesy/angles.py +960 -0
  5. pygeodesy/auxilats/_CX_4.py +1 -1
  6. pygeodesy/auxilats/_CX_6.py +1 -1
  7. pygeodesy/auxilats/_CX_8.py +1 -1
  8. pygeodesy/auxilats/_CX_Rs.py +1 -1
  9. pygeodesy/auxilats/__init__.py +2 -2
  10. pygeodesy/auxilats/__main__.py +1 -1
  11. pygeodesy/auxilats/auxAngle.py +7 -8
  12. pygeodesy/auxilats/auxDLat.py +1 -1
  13. pygeodesy/auxilats/auxDST.py +1 -1
  14. pygeodesy/auxilats/auxLat.py +1 -1
  15. pygeodesy/auxilats/auxily.py +1 -1
  16. pygeodesy/azimuthal.py +6 -5
  17. pygeodesy/basics.py +14 -10
  18. pygeodesy/booleans.py +8 -33
  19. pygeodesy/cartesianBase.py +7 -7
  20. pygeodesy/clipy.py +17 -23
  21. pygeodesy/constants.py +86 -63
  22. pygeodesy/css.py +1 -1
  23. pygeodesy/datums.py +1 -1
  24. pygeodesy/deprecated/__init__.py +2 -2
  25. pygeodesy/deprecated/bases.py +1 -1
  26. pygeodesy/deprecated/classes.py +32 -2
  27. pygeodesy/deprecated/consterns.py +1 -1
  28. pygeodesy/deprecated/datum.py +1 -1
  29. pygeodesy/deprecated/functions.py +1 -1
  30. pygeodesy/deprecated/nvector.py +1 -1
  31. pygeodesy/deprecated/rhumbBase.py +1 -1
  32. pygeodesy/deprecated/rhumbaux.py +1 -1
  33. pygeodesy/deprecated/rhumbsolve.py +1 -1
  34. pygeodesy/deprecated/rhumbx.py +1 -1
  35. pygeodesy/dms.py +1 -1
  36. pygeodesy/ecef.py +1 -1
  37. pygeodesy/ecefLocals.py +1 -1
  38. pygeodesy/elevations.py +1 -1
  39. pygeodesy/ellipsoidalBase.py +1 -1
  40. pygeodesy/ellipsoidalBaseDI.py +1 -1
  41. pygeodesy/ellipsoidalExact.py +1 -1
  42. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  43. pygeodesy/ellipsoidalKarney.py +1 -1
  44. pygeodesy/ellipsoidalNvector.py +1 -1
  45. pygeodesy/ellipsoidalVincenty.py +1 -1
  46. pygeodesy/ellipsoids.py +7 -6
  47. pygeodesy/elliptic.py +1 -1
  48. pygeodesy/epsg.py +1 -1
  49. pygeodesy/errors.py +8 -4
  50. pygeodesy/etm.py +1 -1
  51. pygeodesy/fmath.py +15 -8
  52. pygeodesy/formy.py +107 -5
  53. pygeodesy/frechet.py +1 -1
  54. pygeodesy/fstats.py +1 -1
  55. pygeodesy/fsums.py +1 -1
  56. pygeodesy/gars.py +1 -1
  57. pygeodesy/geod3solve.py +488 -0
  58. pygeodesy/geodesici.py +4 -4
  59. pygeodesy/geodesicw.py +1 -1
  60. pygeodesy/geodesicx/_C4_24.py +1 -1
  61. pygeodesy/geodesicx/_C4_27.py +1 -1
  62. pygeodesy/geodesicx/_C4_30.py +1 -1
  63. pygeodesy/geodesicx/__init__.py +1 -1
  64. pygeodesy/geodesicx/__main__.py +1 -1
  65. pygeodesy/geodesicx/gx.py +1 -1
  66. pygeodesy/geodesicx/gxarea.py +1 -1
  67. pygeodesy/geodesicx/gxbases.py +1 -1
  68. pygeodesy/geodesicx/gxline.py +1 -1
  69. pygeodesy/geodsolve.py +70 -102
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +1 -1
  72. pygeodesy/hausdorff.py +1 -1
  73. pygeodesy/heights.py +1 -1
  74. pygeodesy/internals.py +3 -3
  75. pygeodesy/interns.py +3 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +132 -116
  78. pygeodesy/ktm.py +1 -1
  79. pygeodesy/latlonBase.py +1 -1
  80. pygeodesy/lazily.py +25 -13
  81. pygeodesy/lcc.py +1 -1
  82. pygeodesy/ltp.py +1 -1
  83. pygeodesy/ltpTuples.py +1 -1
  84. pygeodesy/mgrs.py +3 -3
  85. pygeodesy/named.py +14 -9
  86. pygeodesy/namedTuples.py +1 -1
  87. pygeodesy/nvectorBase.py +1 -1
  88. pygeodesy/osgr.py +1 -1
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +1 -1
  91. pygeodesy/resections.py +1 -1
  92. pygeodesy/rhumb/__init__.py +8 -6
  93. pygeodesy/rhumb/aux_.py +1 -1
  94. pygeodesy/rhumb/bases.py +1 -1
  95. pygeodesy/rhumb/ekx.py +1 -1
  96. pygeodesy/rhumb/solve.py +91 -84
  97. pygeodesy/simplify.py +1 -1
  98. pygeodesy/solveBase.py +72 -49
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +1 -1
  101. pygeodesy/sphericalTrigonometry.py +1 -1
  102. pygeodesy/streprs.py +6 -4
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials/__init__.py +70 -0
  105. pygeodesy/triaxials/bases.py +935 -0
  106. pygeodesy/triaxials/conformal3.py +617 -0
  107. pygeodesy/triaxials/triaxial3.py +969 -0
  108. pygeodesy/triaxials/triaxial5.py +1220 -0
  109. pygeodesy/units.py +6 -1
  110. pygeodesy/unitsBase.py +1 -1
  111. pygeodesy/ups.py +2 -3
  112. pygeodesy/utily.py +19 -15
  113. pygeodesy/utm.py +1 -1
  114. pygeodesy/utmups.py +1 -1
  115. pygeodesy/utmupsBase.py +1 -1
  116. pygeodesy/vector2d.py +2 -2
  117. pygeodesy/vector3d.py +1 -1
  118. pygeodesy/vector3dBase.py +195 -51
  119. pygeodesy/webmercator.py +1 -1
  120. pygeodesy/wgrs.py +1 -1
  121. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/METADATA +13 -13
  122. pygeodesy-25.12.12.dist-info/RECORD +125 -0
  123. pygeodesy/triaxials.py +0 -1566
  124. pygeodesy-25.10.10.dist-info/RECORD +0 -119
  125. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/WHEEL +0 -0
  126. {pygeodesy-25.10.10.dist-info → pygeodesy-25.12.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1220 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Triaxal ellipsoid classes L{Triaxial} and I{unordered} L{Triaxial_} and Jacobi conformal projections
5
+ L{Conformal} and L{ConformalSphere}, transcoded from I{Karney}'s GeographicLib 2.5.2 C++ class U{JacobiConformal
6
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1JacobiConformal.html#details>} to pure
7
+ Python and miscellaneous classes L{BetaOmega2Tuple}, L{BetaOmega3Tuple} and L{Conformal2Tuple}, I{all kept
8
+ for backward copability}.
9
+
10
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024) and licensed under the MIT/X11
11
+ License. For more information, see the U{GeographicLib 2.5.2<https://GeographicLib.SourceForge.io>}
12
+ I{experimental} documentation.
13
+
14
+ @see: U{Geodesics on a triaxial ellipsoid<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
15
+ Geodesics_on_a_triaxial_ellipsoid>} and U{Triaxial coordinate systems and their geometrical
16
+ interpretation<https://OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
17
+
18
+ @var Triaxials.Amalthea: Triaxial(name='Amalthea', a=125000, b=73000, c=64000, e2ab=0.658944, e2bc=0.231375493, e2ac=0.737856, volume=2446253479595252, area=93239507787.490371704, area_p=93212299402.670425415)
19
+ @var Triaxials.Ariel: Triaxial(name='Ariel', a=581100, b=577900, c=577700, e2ab=0.01098327, e2bc=0.000692042, e2ac=0.011667711, volume=812633172614203904, area=4211301462766.580078125, area_p=4211301574065.829589844)
20
+ @var Triaxials.Earth: Triaxial(name='Earth', a=6378173.435, b=6378103.9, c=6356754.399999999, e2ab=0.000021804, e2bc=0.006683418, e2ac=0.006705077, volume=1083208241574987694080, area=510065911057441.0625, area_p=510065915922713.6875)
21
+ @var Triaxials.Enceladus: Triaxial(name='Enceladus', a=256600, b=251400, c=248300, e2ab=0.040119337, e2bc=0.024509841, e2ac=0.06364586, volume=67094551514082248, area=798618496278.596679688, area_p=798619018175.109985352)
22
+ @var Triaxials.Europa: Triaxial(name='Europa', a=1564130, b=1561230, c=1560930, e2ab=0.003704694, e2bc=0.000384275, e2ac=0.004087546, volume=15966575194402123776, area=30663773697323.51953125, area_p=30663773794562.45703125)
23
+ @var Triaxials.Io: Triaxial(name='Io', a=1829400, b=1819300, c=1815700, e2ab=0.011011391, e2bc=0.003953651, e2ac=0.014921506, volume=25313121117889765376, area=41691875849096.7421875, area_p=41691877397441.2109375)
24
+ @var Triaxials.Mars: Triaxial(name='Mars', a=3394600, b=3393300, c=3376300, e2ab=0.000765776, e2bc=0.009994646, e2ac=0.010752768, volume=162907283585817247744, area=144249140795107.4375, area_p=144249144150662.15625)
25
+ @var Triaxials.Mimas: Triaxial(name='Mimas', a=207400, b=196800, c=190600, e2ab=0.09960581, e2bc=0.062015624, e2ac=0.155444317, volume=32587072869017956, area=493855762247.691833496, area_p=493857714107.9375)
26
+ @var Triaxials.Miranda: Triaxial(name='Miranda', a=240400, b=234200, c=232900, e2ab=0.050915557, e2bc=0.011070811, e2ac=0.061422691, volume=54926187094835456, area=698880863325.757080078, area_p=698881306767.950317383)
27
+ @var Triaxials.Moon: Triaxial(name='Moon', a=1735550, b=1735324, c=1734898, e2ab=0.000260419, e2bc=0.000490914, e2ac=0.000751206, volume=21886698675223740416, area=37838824729886.09375, area_p=37838824733332.21875)
28
+ @var Triaxials.Tethys: Triaxial(name='Tethys', a=535600, b=528200, c=525800, e2ab=0.027441672, e2bc=0.009066821, e2ac=0.036259685, volume=623086233855821440, area=3528073490771.394042969, area_p=3528074261832.738769531)
29
+ @var Triaxials.WGS84_3: Triaxial(name='WGS84_3', a=6378171.36, b=6378101.609999999, c=6356751.84, e2ab=0.000021871, e2bc=0.006683505, e2ac=0.00670523, volume=1083207064030173855744, area=510065541435967.4375, area_p=510065546301413.5625)
30
+ @var Triaxials.WGS84_3r: Triaxial(name='WGS84_3r', a=6378172, b=6378102, c=6356752, e2ab=0.00002195, e2bc=0.006683577, e2ac=0.00670538, volume=1083207266220584468480, area=510065604942135.8125, area_p=510065609807745.0)
31
+ @var Triaxials.WGS84_35: Triaxial(name='WGS84_35', a=6378172, b=6378102, c=6356752.314245179, e2ab=0.00002195, e2bc=0.006683478, e2ac=0.006705281, volume=1083207319768789942272, area=510065621722018.125, area_p=510065626587483.3125)
32
+ '''
33
+ # make sure int/int division yields float quotient, see .basics
34
+ from __future__ import division as _; del _ # noqa: E702 ;
35
+
36
+ from pygeodesy.angles import _SinCos2, Property_RO
37
+ from pygeodesy.basics import _isin, isLatLon
38
+ from pygeodesy.constants import EPS, EPS0, EPS02, _EPS2e4, INT0, \
39
+ _isfinite, isnear1, _over, _SQRT2_2, \
40
+ _0_0, _0_5, _1_0, _N_1_0, _64_0
41
+ from pygeodesy.datums import Datum, _spherical_datum, _WGS84, _EWGS84, Fmt
42
+ # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
43
+ # from pygeodesy.elliptic import Elliptic # _MODS
44
+ from pygeodesy.errors import _AssertionError, _ValueError, _xkwds_pop2
45
+ from pygeodesy.fmath import Fdot, fdot, hypot, hypot_, fabs, sqrt
46
+ from pygeodesy.fsums import fsumf_, fsum1f_
47
+ from pygeodesy.interns import NN, _beta_, _distant_, _DMAIN_, _finite_, _height_, \
48
+ _inside_, _near_, _negative_, _not_, _null_, _opposite_, \
49
+ _outside_, _too_, _x_, _y_
50
+ from pygeodesy.lazily import _ALL_LAZY, _FOR_DOCS
51
+ from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name__, _NamedEnum, _Pass
52
+ from pygeodesy.namedTuples import LatLon3Tuple, _NamedTupleTo, Vector2Tuple, \
53
+ Vector3Tuple, Vector4Tuple
54
+ # from pygeodesy.props import Property_RO # from .triaxials.angles
55
+ # from pygeodesy.streprs import Fmt # from .datums
56
+ from pygeodesy.triaxials.bases import Conformal5Tuple, _HeightINT0, _hypot2_1, \
57
+ _not_ordered_, _OrderedTriaxialBase, _over0, \
58
+ _otherV3d_, _over02, _sqrt0, TriaxialError, \
59
+ _Triaxial3Base, _UnOrderedTriaxialBase
60
+ from pygeodesy.units import Degrees, Height_, Lat, Lon, Meter, Radians, Radius_, Scalar_
61
+ from pygeodesy.utily import atan2, atan2d, km2m, m2km
62
+ from pygeodesy.vector3d import _otherV3d, Vector3d
63
+
64
+ # from math import fabs, sqrt # from .fmath
65
+
66
+ __all__ = _ALL_LAZY.triaxials_triaxial5
67
+ __version__ = '25.11.29'
68
+
69
+ _omega_ = 'omega'
70
+ _TRIPS = 359 # Eberly 1074?
71
+
72
+
73
+ class _NamedTupleToX(_NamedTupleTo): # in .testNamedTuples
74
+ '''(INTERNAL) Base class for L{BetaOmega2Tuple},
75
+ L{BetaOmega3Tuple} and L{Conformal2Tuple}.
76
+ '''
77
+ def _toDegrees(self, name, **toDMS_kwds):
78
+ '''(INTERNAL) Convert C{self[0:2]} to L{Degrees} or C{toDMS}.
79
+ '''
80
+ return self._toX3U(_NamedTupleTo._Degrees3, Degrees, name, *self, **toDMS_kwds)
81
+
82
+ def _toRadians(self, name):
83
+ '''(INTERNAL) Convert C{self[0:2]} to L{Radians}.
84
+ '''
85
+ return self._toX3U(_NamedTupleTo._Radians3, Radians, name, *self)
86
+
87
+ def _toX3U(self, _X3, U, name, a, b, *c, **kwds):
88
+ a, b, s = _X3(self, a, b, **kwds)
89
+ if s is None or name:
90
+ n = self._name__(name)
91
+ s = self.classof(a, b, *c, name=n).reUnit(U, U).toUnits()
92
+ return s
93
+
94
+
95
+ class BetaOmega2Tuple(_NamedTupleToX):
96
+ '''2-Tuple C{(beta, omega)} with I{ellipsoidal} lat- and
97
+ longitude C{beta} and C{omega} both in L{Radians} (or
98
+ L{Degrees}).
99
+ '''
100
+ _Names_ = (_beta_, _omega_)
101
+ _Units_ = (_Pass, _Pass)
102
+
103
+ def toDegrees(self, name=NN, **toDMS_kwds):
104
+ '''Convert this L{BetaOmega2Tuple} to L{Degrees} or C{toDMS}.
105
+
106
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
107
+
108
+ @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and
109
+ C{omega} both in L{Degrees} or as L{toDMS} strings
110
+ provided some B{C{toDMS_kwds}} keyword arguments are
111
+ specified.
112
+ '''
113
+ return self._toDegrees(name, **toDMS_kwds)
114
+
115
+ def toRadians(self, **name):
116
+ '''Convert this L{BetaOmega2Tuple} to L{Radians}.
117
+
118
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
119
+
120
+ @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and C{omega}
121
+ both in L{Radians}.
122
+ '''
123
+ return self._toRadians(name)
124
+
125
+
126
+ class BetaOmega3Tuple(_NamedTupleToX):
127
+ '''3-Tuple C{(beta, omega, height)} with I{ellipsoidal} lat- and
128
+ longitude C{beta} and C{omega} both in L{Radians} (or L{Degrees})
129
+ and the C{height}, rather the (signed) I{distance} to the triaxial's
130
+ surface (measured along the radial line to the triaxial's center)
131
+ in C{meter}, conventionally.
132
+ '''
133
+ _Names_ = BetaOmega2Tuple._Names_ + (_height_,)
134
+ _Units_ = BetaOmega2Tuple._Units_ + ( Meter,)
135
+
136
+ def toDegrees(self, name=NN, **toDMS_kwds):
137
+ '''Convert this L{BetaOmega3Tuple} to L{Degrees} or C{toDMS}.
138
+
139
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
140
+
141
+ @return: L{BetaOmega3Tuple}C{(beta, omega, height)} with
142
+ C{beta} and C{omega} both in L{Degrees} or as
143
+ L{toDMS} strings provided some B{C{toDMS_kwds}}
144
+ keyword arguments are specified.
145
+ '''
146
+ return self._toDegrees(name, **toDMS_kwds)
147
+
148
+ def toRadians(self, **name):
149
+ '''Convert this L{BetaOmega3Tuple} to L{Radians}.
150
+
151
+ @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
152
+
153
+ @return: L{BetaOmega3Tuple}C{(beta, omega, height)} with C{beta}
154
+ and C{omega} both in L{Radians}.
155
+ '''
156
+ return self._toRadians(name)
157
+
158
+ def to2Tuple(self, **name):
159
+ '''Reduce this L{BetaOmega3Tuple} to a L{BetaOmega2Tuple}.
160
+
161
+ @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
162
+ '''
163
+ return BetaOmega2Tuple(*self[:2], name=self._name__(name))
164
+
165
+
166
+ class Conformal2Tuple(_NamedTupleToX):
167
+ '''2-Tuple C{(x, y)} with a I{Jacobi Conformal} C{x} and C{y}
168
+ projection, both in L{Radians} (or L{Degrees}).
169
+ '''
170
+ _Names_ = (_x_, _y_)
171
+ _Units_ = (_Pass, _Pass)
172
+
173
+ def toDegrees(self, name=NN, **toDMS_kwds):
174
+ '''Convert this L{Conformal2Tuple} to L{Degrees} or C{toDMS}.
175
+
176
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
177
+
178
+ @return: L{Conformal2Tuple}C{(x, y)} with C{x} and C{y} both
179
+ in L{Degrees} or as L{toDMS} strings provided some
180
+ B{C{toDMS_kwds}} keyword arguments are specified.
181
+ '''
182
+ return self._toDegrees(name, **toDMS_kwds)
183
+
184
+ def toRadians(self, **name):
185
+ '''Convert this L{Conformal2Tuple} to L{Radians}.
186
+
187
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
188
+
189
+ @return: L{Conformal2Tuple}C{(x, y)} with C{x} and C{y} both in L{Radians}.
190
+ '''
191
+ return self._toRadians(name)
192
+
193
+ def to5Tuple(self, b_conformal, **z_scale_name):
194
+ '''Return this L{Conformal2Tuple} as a L{Conformal5Tuple}.
195
+
196
+ @arg b_conformal: Middle semi-axis (C{meter}, conventionally)
197
+ or the original L{Conformal} of this 2-tuple.
198
+ @kwarg z_scale_name: Optional C{B{z}=0} meter, C{B{scale}=NAN}
199
+ and C{B{name}=NN} (C{str}).
200
+
201
+ @return: A L{Conformal5Tuple}.
202
+ '''
203
+ if isinstance(b_conformal, Conformal):
204
+ b = b_conformal.b
205
+ else:
206
+ b = Radius_(b=b_conformal)
207
+ x, y = self.toRadians()
208
+ x, y = _over(x, b), _over(y, b)
209
+ return Conformal5Tuple(x, y, **z_scale_name)
210
+
211
+
212
+ class Triaxial_(_UnOrderedTriaxialBase):
213
+ '''I{Unordered} triaxial ellipsoid.
214
+
215
+ Triaxial ellipsoids with right-handed semi-axes C{a}, C{b} and C{c}, oriented
216
+ such that the large principal ellipse C{ab} is the equator I{Z}=0, I{beta}=0,
217
+ while the small principal ellipse C{ac} is the prime meridian, plane I{Y}=0,
218
+ I{omega}=0.
219
+
220
+ The four umbilic points, C{abs}(I{omega}) = C{abs}(I{beta}) = C{PI/2}, lie on
221
+ the middle principal ellipse C{bc} in plane I{X}=0, I{omega}=C{PI/2}.
222
+
223
+ @note: I{Geodetic} C{lat}- and C{lon}gitudes are in C{degrees}, I{geodetic}
224
+ C{phi} and C{lam}bda are in C{radians}, but I{ellipsoidal} lat- and
225
+ longitude C{beta} and C{omega} are in L{Radians} by default (or in
226
+ L{Degrees} if converted).
227
+ '''
228
+ if _FOR_DOCS:
229
+ __init__ = _UnOrderedTriaxialBase.__init__
230
+
231
+
232
+ class Triaxial(_OrderedTriaxialBase):
233
+ '''I{Ordered} triaxial ellipsoid.
234
+
235
+ @see: L{Triaxial_} for more information.
236
+ '''
237
+ if _FOR_DOCS:
238
+ __init__ = _OrderedTriaxialBase.__init__
239
+
240
+ def forwardBetaOmega(self, beta, omega, height=0, **unit_name):
241
+ '''Convert I{ellipsoidal} lat- C{beta}, longitude C{omega} and C{height}
242
+ to cartesian.
243
+
244
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
245
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
246
+ @kwarg height: Height above or below the triaxial's surface (C{meter},
247
+ same units as this triaxial's semi-axes.
248
+ @kwarg unit_name: Optional C{B{name}=NN} (C{str}) and scalar
249
+ C{B{unit}=}L{Radians} (or L{Degrees}).
250
+
251
+ @return: A L{Vector3Tuple}C{(x, y, z)}.
252
+
253
+ @see: Method L{Triaxial.reverseBetaOmega} and U{equations (23-25)<https://
254
+ OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
255
+ '''
256
+ unit, name = _xkwds_pop2(unit_name, unit=Radians)
257
+ if height:
258
+ z = self._Height(height) + self.c
259
+ if z > 0:
260
+ z2 = z**2
261
+ x = z * _sqrt0(_1_0 + self._a2c2 / z2)
262
+ y = z * _sqrt0(_1_0 + self._b2c2 / z2)
263
+ else:
264
+ x = y = z = _0_0
265
+ else:
266
+ x, y, z = self._abc3
267
+ if z: # and x and y:
268
+ sa, ca = _SinCos2(beta, unit)
269
+ sb, cb = _SinCos2(omega, unit)
270
+
271
+ r = self._a2b2_a2c2
272
+ x *= cb * (_sqrt0(ca**2 + sa**2 * r) if r else fabs(ca))
273
+ y *= ca * sb
274
+ z *= sa * (_sqrt0(_1_0 - cb**2 * r) if r else _1_0)
275
+ return Vector3Tuple(x, y, z, **name)
276
+
277
+ def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
278
+ '''Convert I{ellipsoidal} lat- and longitude C{beta} and C{omega}
279
+ to cartesian coordinates I{on this triaxial's surface}.
280
+
281
+ @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
282
+ @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
283
+ @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
284
+ @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
285
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
286
+
287
+ @return: A L{Vector3Tuple}C{(x, y, z)} on the surface.
288
+
289
+ @raise TriaxialError: This triaxial is near-spherical.
290
+
291
+ @see: Method L{Triaxial.reverseBetaOmega}, U{Triaxial ellipsoid coordinate
292
+ system<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
293
+ Triaxial_ellipsoid_coordinate_system>} and U{equations (23-25)<https://
294
+ OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
295
+ '''
296
+ t = self._radialTo3(sbeta, cbeta, somega, comega)
297
+ return Vector3Tuple(*t, **name)
298
+
299
+ def forwardCartesian(self, x_xyz, y=None, z=None, normal=True, **eps_name):
300
+ '''Project any cartesian to a cartesian I{on this triaxial's surface}.
301
+
302
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
303
+ L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
304
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
305
+ ignored otherwise.
306
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
307
+ @kwarg normal: If C{True}, the projection is C{perpendicular} to the surface,
308
+ otherwise C{radial} to the center of this triaxial (C{bool}).
309
+ @kwarg eps_name: Root finder tolerance C{B{eps}=EPS} and optional
310
+ C{B{name}="height4"} (C{str}).
311
+
312
+ @return: A L{Vector4Tuple}C{(x, y, z, h)}.
313
+
314
+ @see: Method L{Triaxial.reverseCartesian} to reverse the projection and
315
+ function L{height4<triaxials.triaxial5.height4>} for more details.
316
+ '''
317
+ return self.height4(x_xyz, y, z, normal=normal, **eps_name)
318
+
319
+ def forwardLatLon(self, lat, lon, height=0, **unit_name):
320
+ '''Convert I{geodetic} lat-, longitude and height to cartesian.
321
+
322
+ @arg lat: Geodetic latitude (C{Ang} or B{C{unit}}).
323
+ @arg lon: Geodetic longitude (C{Ang} or B{C{unit}}).
324
+ @arg height: Height above the ellipsoid (C{meter}, same units
325
+ as this triaxial's semi-axes).
326
+ @kwarg unit_name: Optional scalar C{B{unit}=}L{Degrees} and
327
+ C{B{name}=NN} (C{str}).
328
+
329
+ @return: A L{Vector3Tuple}C{(x, y, z)}.
330
+
331
+ @see: Method L{Triaxial.reverseLatLon} and U{equations (9-11)<https://
332
+ OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
333
+ '''
334
+ unit, name = _xkwds_pop2(unit_name, unit=Degrees)
335
+ return self._forwardLatLon3(height, name, *(_SinCos2(lat, unit) +
336
+ _SinCos2(lon, unit)))
337
+
338
+ def forwardLatLon_(self, slat, clat, slon, clon, height=0, **name):
339
+ '''Convert I{geodetic} lat-, longitude and height to cartesian.
340
+
341
+ @arg slat: Geodetic latitude C{sin(lat)} (C{scalar}).
342
+ @arg clat: Geodetic latitude C{cos(lat)} (C{scalar}).
343
+ @arg slon: Geodetic longitude C{sin(lon)} (C{scalar}).
344
+ @arg clon: Geodetic longitude C{cos(lon)} (C{scalar}).
345
+ @arg height: Height above the ellipsoid (C{meter}, same units
346
+ as this triaxial's semi-axes).
347
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
348
+
349
+ @return: A L{Vector3Tuple}C{(x, y, z)}.
350
+
351
+ @see: Method L{Triaxial.reverseLatLon} and U{equations (9-11)<https://
352
+ OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
353
+ '''
354
+ sa, ca = self._norm2(slat, clat)
355
+ sb, cb = self._norm2(slon, clon)
356
+ return self._forwardLatLon3(height, name, sa, ca, sb, cb)
357
+
358
+ def _forwardLatLon3(self, height, name, sa, ca, sb, cb): # name always **name
359
+ '''(INTERNAL) Helper for C{.forwardLatLon} and C{.forwardLatLon_}.
360
+ '''
361
+ h = self._Height(height)
362
+ x = ca * cb
363
+ y = ca * sb
364
+ z = sa
365
+ # 1 - (1 - (c/a)**2) * sa**2 - (1 - (b/a)**2) * ca**2 * sb**2
366
+ t = fsumf_(_1_0, -self.e2ac * z**2, -self.e2ab * y**2)
367
+ n = self.a / _sqrt0(t) # prime vertical
368
+ x *= h + n
369
+ y *= h + n * self._b2_a2
370
+ z *= h + n * self._c2_a2
371
+ return Vector3Tuple(x, y, z, **name)
372
+
373
+ def _Height(self, height):
374
+ '''(INTERNAL) Validate a C{height}.
375
+ '''
376
+ return Height_(height=height, low=-self.c, Error=TriaxialError)
377
+
378
+ def reverseBetaOmega(self, x_xyz, y=None, z=None, **name):
379
+ '''Convert cartesian to I{ellipsoidal} lat- and longitude, C{beta}, C{omega}
380
+ and height.
381
+
382
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
383
+ L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
384
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
385
+ ignored otherwise.
386
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
387
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
388
+
389
+ @return: A L{BetaOmega3Tuple}C{(beta, omega, height)} with C{beta} and
390
+ C{omega} in L{Radians} and (radial) C{height} in C{meter}, same
391
+ units as this triaxial's semi-axes.
392
+
393
+ @see: Methods L{Triaxial.forwardBetaOmega} and L{Triaxial.forwardBetaOmega_}
394
+ and U{equations (21-22)<https://OLD.Topo.Auth.GR/wp-content/uploads/
395
+ sites/111/2021/12/09_Panou.pdf>}.
396
+ '''
397
+ v = _otherV3d_(x_xyz, y, z)
398
+ a, b, h = _reverseLatLon3(v, atan2, v, self.forwardBetaOmega_)
399
+ return BetaOmega3Tuple(Radians(beta=a), Radians(omega=b), h, **name)
400
+
401
+ def reverseCartesian(self, x_xyz, y=None, z=None, height=0, normal=True, eps=_EPS2e4, **name):
402
+ '''"Unproject" a cartesian I{off} this triaxial's surface.
403
+
404
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
405
+ L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
406
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
407
+ ignored otherwise.
408
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
409
+ @kwarg height: Height above or below this triaxial's surface (C{meter}, same
410
+ units as this triaxial's semi-axes).
411
+ @kwarg normal: If C{True}, B{C{height}} is C{perpendicular} to the surface,
412
+ otherwise C{radial} to the center of this triaxial (C{bool}).
413
+ @kwarg eps: Tolerance for on-surface test (C{scalar}), see method
414
+ L{Triaxial.sideOf}.
415
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
416
+
417
+ @return: A L{Vector3Tuple}C{(x, y, z)}.
418
+
419
+ @raise TrialError: Cartesian B{C{x_xyz}} or C{(x, y, z)} not on this triaxial's
420
+ surface.
421
+
422
+ @see: Methods L{Triaxial.forwardCartesian} and L{Triaxial.height4}.
423
+ '''
424
+ h, name = _xkwds_pop2(name, h=height) # h=height for backward compatibility
425
+ v = _otherV3d_(x_xyz, y, z, **name)
426
+ _ = self._sideOn(v, eps=eps)
427
+ h = _HeightINT0(h)
428
+ if h:
429
+ if normal:
430
+ v = v.plus(self.normal3d(*v.xyz, length=h))
431
+ elif v.length > EPS0:
432
+ v = v.times(_1_0 + (h / v.length))
433
+ return v.xyz # Vector3Tuple
434
+
435
+ def reverseLatLon(self, x_xyz, y=None, z=None, **name):
436
+ '''Convert cartesian to I{geodetic} lat-, longitude and height.
437
+
438
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
439
+ L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
440
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
441
+ ignored otherwise.
442
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
443
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
444
+
445
+ @return: A L{LatLon3Tuple}C{(lat, lon, height)} with C{lat} and C{lon}
446
+ in C{degrees} and (radial) C{height} in C{meter}, same units
447
+ as this triaxial's semi-axes.
448
+
449
+ @see: Methods L{Triaxial.forwardLatLon} and L{Triaxial.forwardLatLon_}
450
+ and U{equations (4-5)<https://OLD.Topo.Auth.GR/wp-content/uploads/
451
+ sites/111/2021/12/09_Panou.pdf>}.
452
+ '''
453
+ v = _otherV3d_(x_xyz, y, z)
454
+ s = v.times_(self._c2_a2, # == 1 - e_sub_x**2
455
+ self._c2_b2, # == 1 - e_sub_y**2
456
+ _1_0)
457
+ a, b, h = _reverseLatLon3(s, atan2d, v, self.forwardLatLon_)
458
+ return LatLon3Tuple(Lat(a), Lon(b), h, **name)
459
+
460
+
461
+ class TriaxialB(_Triaxial3Base):
462
+ '''I{Ordered} triaxial ellipsoid specified by its middle semi-axis and shape.
463
+
464
+ @see: L{Triaxial} for details and more information.
465
+ '''
466
+ def __init__(self, b, e2=_0_0, k2=_1_0, kp2=_0_0, **name):
467
+ '''New L{TriaxialB} triaxial.
468
+
469
+ @arg b: Middle semi-axis (C{meter}, conventionally).
470
+ @kwarg e2: Excentricty I{squared} (C{scalar, e2 = (a**2 - c**2) / B{b}**2}).
471
+ @kwarg k2: Oblateness I{squared} (C{scalar, k2 = (C{b}**2 - c**2) / (a**2 - c**2)}).
472
+ @kwarg kp2: Prolateness I{squared} (C{scalar, kp2 = (a**2 - C{b}**2) / (a**2 - c**2)}).
473
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
474
+
475
+ @note: The semi-axes must be ordered as C{B{a} >= B{b} >= B{c} > 0} but
476
+ may be spherical, C{B{e2} == 0}, i.e. C{B{a} == B{c}}. In that case
477
+ C{B{k2} == 0} represents a I{prolate sphere}, C{B{k2} == 1} an
478
+ I{oblate sphere}, otherwise a I{triaxial sphere} with C{0 < B{k2} < 1}.
479
+
480
+ @note: The B{C{k2}} and B{C{kp2}} arguments are normalized, C{B{k2} + B{kp2} == 1}.
481
+
482
+ @raise TriaxialError: Semi-axes unordered or invalid.
483
+ '''
484
+ self._init_abc3_e2_k2_kp2(Radius_(b=b), e2, k2, kp2, **name)
485
+
486
+
487
+ class Conformal(Triaxial):
488
+ '''This is a I{Jacobi Conformal} projection of a triaxial ellipsoid to a plane where
489
+ the C{X} and C{Y} grid lines are straight.
490
+
491
+ I{Ellipsoidal} coordinates I{beta} and I{omega} are converted to Jacobi Conformal
492
+ I{y} respectively I{x} separately. Jacobi's coordinates have been multiplied
493
+ by C{sqrt(B{a}**2 - B{c}**2) / (2 * B{b})} so that the customary results are
494
+ returned in the case of an ellipsoid of revolution.
495
+
496
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2014-2024) and
497
+ licensed under the MIT/X11 License.
498
+
499
+ @note: This constructor can I{not be used to specify a sphere}, see alternate
500
+ L{ConformalSphere}.
501
+
502
+ @see: L{Triaxial}, C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/
503
+ C++/doc/classGeographicLib_1_1JacobiConformal.html#details>}, U{Jacobi's conformal
504
+ projection<https://GeographicLib.SourceForge.io/C++/doc/jacobi.html>} and Jacobi,
505
+ C. G. J. I{U{Vorlesungen über Dynamik<https://Books.Google.com/books?
506
+ id=ryEOAAAAQAAJ&pg=PA212>}}, page 212ff.
507
+ '''
508
+ if _FOR_DOCS:
509
+ __init__ = Triaxial.__init__
510
+
511
+ @Property_RO
512
+ def _a2_b(self):
513
+ return self._a2_b2 * self.b
514
+
515
+ @Property_RO
516
+ def _c2_b(self):
517
+ return self._c2_b2 * self.b
518
+
519
+ def x(self, omega, unit=Radians):
520
+ '''Compute a I{Jacobi Conformal} C{x} projection.
521
+
522
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
523
+ @kwarg unit: Unit of scalar B{C{omega}} (or C{Degrees}).
524
+
525
+ @return: The C{x} projection (L{Meter}), same units as
526
+ this triaxial's semi-axes.
527
+ '''
528
+ s, c = _SinCos2(omega, unit)
529
+ return Meter(x=self._x(s, c, self._a2_b))
530
+
531
+ def _x(self, s, c, a2_b_):
532
+ '''(INTERNAL) Helper for C{.x}, C{.xR_} and C{.xy}.
533
+ '''
534
+ s, c = self._norm2(s, c, self.a)
535
+ return self._xE.fPi(s, c) * a2_b_
536
+
537
+ @Property_RO
538
+ def _xE(self):
539
+ '''(INTERNAL) Get the x-elliptic function.
540
+ '''
541
+ k2, kp2 = self._k2E_kp2E
542
+ # -a2b2 / b2 == (b2 - a2) / b2 == 1 - a2 / b2 == 1 - a2_b2
543
+ return self._Elliptic(k2, _1_0 - self._a2_b2, kp2, self._a2_b2)
544
+
545
+ def xR(self, omega, unit=Radians):
546
+ '''Compute a I{Jacobi Conformal} C{x} projection.
547
+
548
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
549
+ @kwarg unit: Unit of scalar B{C{omega}} (or C{Degrees}).
550
+
551
+ @return: The C{x} projection (L{Radians}).
552
+ '''
553
+ return self.xR_(*_SinCos2(omega, unit))
554
+
555
+ def xR_(self, somega, comega):
556
+ '''Compute a I{Jacobi Conformal} C{x} projection.
557
+
558
+ @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
559
+ @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
560
+
561
+ @return: The C{x} projection (L{Radians}).
562
+ '''
563
+ return Radians(x=self._x(somega, comega, self._a2_b2))
564
+
565
+ def xy(self, beta, omega, **unit_name):
566
+ '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
567
+
568
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
569
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
570
+ @kwarg unit_name: Optional scalar C{B{unit}=}L{Radians} and
571
+ name (C{str}), overriding C{B{name}="xyR2"}.
572
+
573
+ @return: A (L{Vector2Tuple}C{(x, y)}), both in C{Meter},
574
+ same units as this triaxial's semi-axes..
575
+ '''
576
+ unit, name = _xkwds_pop2(unit_name, unit=Radians)
577
+ return Vector2Tuple(self.x(omega, unit=unit),
578
+ self.y(beta, unit=unit),
579
+ name=_name__(name, name__=self.xy))
580
+
581
+ @Property_RO
582
+ def xyQ2(self):
583
+ '''Get the I{Jacobi Conformal} quadrant size in C{meter}
584
+ (L{Vector2Tuple}C{(x, y)}).
585
+ '''
586
+ return Vector2Tuple(Meter(x=self._a2_b * self._xE.cPi),
587
+ Meter(y=self._c2_b * self._yE.cPi),
588
+ name=Conformal.xyQ2.name)
589
+
590
+ @Property_RO
591
+ def xyQR2(self):
592
+ '''Get the I{Jacobi Conformal} quadrant size in C{Radians}
593
+ (L{Conformal2Tuple}C{(x, y)}).
594
+ '''
595
+ return Conformal2Tuple(Radians(x=self._a2_b2 * self._xE.cPi),
596
+ Radians(y=self._c2_b2 * self._yE.cPi),
597
+ name=Conformal.xyQR2.name)
598
+
599
+ def xyR2(self, beta, omega, **unit_name):
600
+ '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
601
+
602
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
603
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
604
+ @kwarg unit_name: Optional scalar C{B{unit}=}L{Radians} and
605
+ name (C{str}), overriding C{B{name}="xyR2"}.
606
+
607
+ @return: A L{Conformal2Tuple}C{(x, y)}, both in C{Radians}.
608
+ '''
609
+ unit, name = _xkwds_pop2(unit_name, unit=Radians)
610
+ sb_cb_so_co = _SinCos2(beta, unit) + _SinCos2(omega, unit)
611
+ return self.xyR2_(*sb_cb_so_co, name=_name__(name, name__=self.xyR2))
612
+
613
+ def xyR2_(self, sbeta, cbeta, somega, comega, **name):
614
+ '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
615
+
616
+ @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
617
+ @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
618
+ @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
619
+ @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
620
+ @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2_"}.
621
+
622
+ @return: A L{Conformal2Tuple}C{(x, y)}.
623
+ '''
624
+ return Conformal2Tuple(self.xR_(somega, comega),
625
+ self.yR_(sbeta, cbeta),
626
+ name=_name__(name, name__=self.xyR2_))
627
+
628
+ def y(self, beta, unit=Radians):
629
+ '''Compute a I{Jacobi Conformal} C{y} projection.
630
+
631
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
632
+ @kwarg unit: Unit of scalar B{C{beta}} (or C{Degrees}).
633
+
634
+ @return: The C{y} projection (L{Meter}), same units as
635
+ this triaxial's semi-axes.
636
+ '''
637
+ s, c = _SinCos2(beta, unit)
638
+ return Meter(y=self._y(s, c, self._c2_b))
639
+
640
+ def _y(self, s, c, c2_b_):
641
+ '''(INTERNAL) Helper for C{.y}, C{.yR_} and C{.xy}.
642
+ '''
643
+ s, c = self._norm2(s, c, self.c)
644
+ return self._yE.fPi(s, c) * c2_b_
645
+
646
+ @Property_RO
647
+ def _yE(self):
648
+ '''(INTERNAL) Get the y-elliptic function.
649
+ '''
650
+ k2, kp2 = self._k2E_kp2E
651
+ # b2c2 / b2 == (b2 - c2) / b2 == 1 - c2 / b2 == e2bc
652
+ return self._Elliptic(kp2, self.e2bc, k2, self._c2_b2)
653
+
654
+ def yR(self, beta, unit=Radians):
655
+ '''Compute a I{Jacobi Conformal} C{y} projection.
656
+
657
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
658
+ @kwarg unit: Unit of scalar B{C{beta}} (or C{Degrees}).
659
+
660
+ @return: The C{y} projection (L{Radians}).
661
+ '''
662
+ return self.yR_(*_SinCos2(beta, unit))
663
+
664
+ def yR_(self, sbeta, cbeta):
665
+ '''Compute a I{Jacobi Conformal} C{y} projection.
666
+
667
+ @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
668
+ @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
669
+
670
+ @return: The C{y} projection (L{Radians}).
671
+ '''
672
+ return Radians(y=self._y(sbeta, cbeta, self._c2_b2))
673
+
674
+
675
+ class ConformalSphere(Conformal):
676
+ '''Alternate, I{Jacobi Conformal projection} on a I{spherical} triaxial.
677
+
678
+ @see: L{Conformal<triaxials.triaxial5.Conformal>} for more information.
679
+ '''
680
+ _ab = _bc = 0
681
+
682
+ def __init__(self, radius_conformal, ab=0, bc=0, **name):
683
+ '''New L{ConformalSphere}.
684
+
685
+ @arg radius_conformal: Radius (C{scalar}, conventionally in C{meter})
686
+ or an other L{ConformalSphere} or L{Conformal}.
687
+ @kwarg ab: Relative magnitude of C{B{a} - B{b}} (C{meter}, same units
688
+ as C{scalar B{radius}}.
689
+ @kwarg bc: Relative magnitude of C{B{b} - B{c}} (C{meter}, same units
690
+ as C{scalar B{radius}}.
691
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
692
+
693
+ @raise TriaxialError: Invalid B{C{radius_conformal}}, negative B{C{ab}},
694
+ negative B{C{bc}} or C{(B{ab} + B{bc})} not positive.
695
+
696
+ @note: If B{C{radius_conformal}} is a L{ConformalSphere} and if B{C{ab}}
697
+ and B{C{bc}} are both zero or C{None}, the B{C{radius_conformal}}'s
698
+ C{ab}, C{bc}, C{a}, C{b} and C{c} are copied.
699
+ '''
700
+ try:
701
+ r = radius_conformal
702
+ if isinstance(r, Triaxial): # ordered only
703
+ t = r._abc3
704
+ j = isinstance(r, ConformalSphere) and not bool(ab or bc)
705
+ else:
706
+ t = (Radius_(radius=r),) * 3
707
+ j = False
708
+ self._ab = r.ab if j else Scalar_(ab=ab) # low=0
709
+ self._bc = r.bc if j else Scalar_(bc=bc) # low=0
710
+ if (self.ab + self.bc) <= 0:
711
+ raise ValueError(_negative_)
712
+ a, _, c = self._abc3 = t
713
+ if not (a >= c and _isfinite(self._a2b2)
714
+ and _isfinite(self._a2c2)):
715
+ raise ValueError(_not_(_finite_))
716
+ except (TypeError, ValueError) as x:
717
+ raise TriaxialError(radius=r, ab=ab, bc=bc, cause=x)
718
+ if name:
719
+ self.name = name
720
+
721
+ @Property_RO
722
+ def ab(self):
723
+ '''Get relative magnitude C{a - b} (C{meter}, same units as B{C{a}}).
724
+ '''
725
+ return self._ab
726
+
727
+ @Property_RO
728
+ def _a2b2(self):
729
+ '''(INTERNAL) Get C{a**2 - b**2} == ab * (a + b).
730
+ '''
731
+ a, b, _ = self._abc3
732
+ return self.ab * (a + b)
733
+
734
+ @Property_RO
735
+ def _a2c2(self):
736
+ '''(INTERNAL) Get C{a**2 - c**2} == a2b2 + b2c2.
737
+ '''
738
+ return self._a2b2 + self._b2c2
739
+
740
+ @Property_RO
741
+ def bc(self):
742
+ '''Get relative magnitude C{b - c} (C{meter}, same units as B{C{a}}).
743
+ '''
744
+ return self._bc
745
+
746
+ @Property_RO
747
+ def _b2c2(self):
748
+ '''(INTERNAL) Get C{b**2 - c**2} == bc * (b + c).
749
+ '''
750
+ _, b, c = self._abc3
751
+ return self.bc * (b + c)
752
+
753
+ @Property_RO
754
+ def radius(self):
755
+ '''Get radius (C{meter}, conventionally).
756
+ '''
757
+ return self.a
758
+
759
+
760
+ class Triaxials(_NamedEnum):
761
+ '''(INTERNAL) L{Triaxial} registry, I{must} be a sub-class
762
+ to accommodate the L{_LazyNamedEnumItem} properties.
763
+ '''
764
+ def _Lazy(self, *abc, **name):
765
+ '''(INTERNAL) Instantiate the C{Triaxial}.
766
+ '''
767
+ a, b, c = map(km2m, abc)
768
+ return Triaxial(a, b, c, **name)
769
+
770
+ Triaxials = Triaxials(Triaxial, Triaxial_) # PYCHOK singleton
771
+ '''Some pre-defined L{Triaxial}s, all I{lazily} instantiated.'''
772
+ # <https://ArxIV.org/pdf/1909.06452.pdf> Table 1 Semi-axes in Km
773
+ # <https://www.JPS.NASA.gov/education/images/pdf/ss-moons.pdf>
774
+ # <https://link.Springer.com/article/10.1007/s00190-022-01650-9>
775
+ # <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Constants.html>
776
+ _abc84_35 = (_EWGS84.a + 35), (_EWGS84.a - 35), _EWGS84.b
777
+ Triaxials._assert( # a (Km) b (Km) c (Km) planet
778
+ Amalthea = _lazy('Amalthea', 125.0, 73.0, _64_0), # Jupiter
779
+ Ariel = _lazy('Ariel', 581.1, 577.9, 577.7), # Uranus
780
+ Earth = _lazy('Earth', 6378.173435, 6378.1039, 6356.7544),
781
+ Enceladus = _lazy('Enceladus', 256.6, 251.4, 248.3), # Saturn
782
+ Europa = _lazy('Europa', 1564.13, 1561.23, 1560.93), # Jupiter
783
+ Io = _lazy('Io', 1829.4, 1819.3, 1815.7), # Jupiter
784
+ Mars = _lazy('Mars', 3394.6, 3393.3, 3376.3),
785
+ Mimas = _lazy('Mimas', 207.4, 196.8, 190.6), # Saturn
786
+ Miranda = _lazy('Miranda', 240.4, 234.2, 232.9), # Uranus
787
+ Moon = _lazy('Moon', 1735.55, 1735.324, 1734.898), # Earth
788
+ Tethys = _lazy('Tethys', 535.6, 528.2, 525.8), # Saturn
789
+ WGS84_3 = _lazy('WGS84_3', 6378.17136, 6378.10161, 6356.75184), # C++
790
+ WGS84_3r = _lazy('WGS84_3r', 6378.172, 6378.102, 6356.752), # C++, rounded
791
+ WGS84_35 = _lazy('WGS84_35', *map(m2km, _abc84_35)))
792
+ del _abc84_35, _EWGS84
793
+
794
+
795
+ def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
796
+ '''(INTERNAL) Hartzell's "Satellite Line-of-Sight Intersection ...",
797
+ formula from a Point-Of-View to an I{un-/ordered} Triaxial.
798
+ '''
799
+ def _toUvwV3d(los, pov):
800
+ try: # pov must be LatLon or Cartesian if los is a Los
801
+ v = los.toUvw(pov)
802
+ except (AttributeError, TypeError):
803
+ v = _otherV3d(los=los)
804
+ return v
805
+
806
+ p3 = _otherV3d(pov=pov.toCartesian() if isLatLon(pov) else pov)
807
+ if los is True: # normal
808
+ a, b, c, d, i = _plumbTo5(p3.x, p3.y, p3.z, Tun)
809
+ return type(p3)(a, b, c), d, i
810
+
811
+ u3 = p3.negate() if los is False or los is None else _toUvwV3d(los, pov)
812
+
813
+ a, b, c, T = Tun._ordered4
814
+
815
+ a2 = T.a2 # largest, factored out
816
+ b2, p2 = (T.b2, T._b2_a2) if b != a else (a2, _1_0)
817
+ c2, q2 = (T.c2, T._c2_a2) if c != a else (a2, _1_0)
818
+
819
+ p3 = T._order3d(p3)
820
+ u3 = T._order3d(u3).unit() # unit vector, opposing signs
821
+
822
+ x2, y2, z2 = p3.x2y2z23 # p3.times_(p3).xyz3
823
+ ux, vy, wz = u3.times_(p3).xyz3
824
+ u2, v2, w2 = u3.x2y2z23 # u3.times_(u3).xyz3
825
+
826
+ t = (p2 * c2), c2, b2
827
+ m = fdot(t, u2, v2, w2) # a2 factored out
828
+ if m < EPS0: # zero or near-null LOS vector
829
+ raise _ValueError(_near_(_null_))
830
+
831
+ r = fsumf_(b2 * w2, c2 * v2, -v2 * z2, vy * wz * 2,
832
+ -w2 * y2, -u2 * y2 * q2, -u2 * z2 * p2, ux * wz * 2 * p2,
833
+ -w2 * x2 * p2, b2 * u2 * q2, -v2 * x2 * q2, ux * vy * 2 * q2)
834
+ if r > 0: # a2 factored out
835
+ r = sqrt(r) * b * c # == a * a * b * c / a2
836
+ elif r < 0: # LOS pointing away from or missing the triaxial
837
+ raise _ValueError(_opposite_ if max(ux, vy, wz) > 0 else _outside_)
838
+
839
+ d = Fdot(t, ux, vy, wz).fadd_(r).fover(m) # -r for antipode, a2 factored out
840
+ if d > 0: # POV inside or LOS outside or missing the triaxial
841
+ s = fsumf_(_N_1_0, _over(x2, a2), _over(y2, b2), _over(z2, c2)) # like _sideOf
842
+ raise _ValueError(_outside_ if s > 0 else _inside_)
843
+ elif fsum1f_(x2, y2, z2, -d*d) < 0: # d past triaxial's center
844
+ raise _ValueError(_too_(_distant_))
845
+
846
+ v = p3.minus(u3.times(d)) # cartesian type(pov) or Vector3d
847
+ h = p3.minus(v).length # distance to pov == -d
848
+ return T._order3d(v, reverse=True), h, None
849
+
850
+
851
+ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
852
+ '''Compute the intersection of a tri-/biaxial ellipsoid and a Line-Of-Sight from
853
+ a Point-Of-View outside.
854
+
855
+ @arg pov: Point-Of-View outside the tri-/biaxial (C{Cartesian}, L{Ecef9Tuple},
856
+ C{LatLon} or L{Vector3d}).
857
+ @kwarg los: Line-Of-Sight, I{direction} to the tri-/biaxial (L{Los}, L{Vector3d})
858
+ or C{True} for the I{normal, perpendicular, plumb} to the surface of
859
+ the tri-/biaxial or C{False} or C{None} to point to its center.
860
+ @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{Conformal} or
861
+ L{ConformalSphere}) or biaxial ellipsoid (L{Datum},
862
+ L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}) or spherical earth
863
+ radius (C{scalar}, conventionally in C{meter}).
864
+ @kwarg name: Optional name (C{str}), overriding C{B{name}="hartzell4"}.
865
+
866
+ @return: L{Vector4Tuple}C{(x, y, z, h)} on the tri-/biaxial's surface, with C{h}
867
+ the distance from B{C{pov}} to C{(x, y, z)} I{along the} B{C{los}}, all
868
+ in C{meter}, conventionally.
869
+
870
+ @raise TriaxialError: Invalid B{C{pov}} or B{C{pov}} inside the tri-/biaxial or
871
+ invalid B{C{los}} or B{C{los}} points outside or away from
872
+ the tri-/biaxial.
873
+
874
+ @raise TypeError: Invalid B{C{tri_biax}}, C{ellipsoid} or C{datum}.
875
+
876
+ @see: Class L{pygeodesy3.Los}, functions L{pygeodesy.tyr3d} and L{pygeodesy.hartzell}
877
+ and U{lookAtSpheroid<https://PyPI.org/project/pymap3d>} and U{"Satellite
878
+ Line-of-Sight Intersection with Earth"<https://StephenHartzell.Medium.com/
879
+ satellite-line-of-sight-intersection-with-earth-d786b4a6a9b6>}.
880
+ '''
881
+ T = _tri_biaxial(tri_biax, hartzell4)
882
+ try:
883
+ v, h, i = _hartzell3(pov, los, T)
884
+ except Exception as x:
885
+ raise TriaxialError(pov=pov, los=los, tri_biax=tri_biax, cause=x)
886
+ n = _name__(name, name__=hartzell4) # typename
887
+ return Vector4Tuple(v.x, v.y, v.z, h, iteration=i, name=n)
888
+
889
+
890
+ def height4(x_xyz, y=None, z=None, tri_biax=_WGS84, normal=True, eps=EPS, **name):
891
+ '''Compute the projection on and the height above or below a tri-/biaxial ellipsoid's surface.
892
+
893
+ @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
894
+ L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
895
+ @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
896
+ otherwise.
897
+ @kwarg z: Z component (C{scalar}), like B{C{y}}.
898
+ @kwarg normal: If C{True}, the projection is the I{perpendicular, plumb} to the
899
+ tri-/biaxial's surface, otherwise the C{radial} line to the center
900
+ of the tri-/biaxial (C{bool}).
901
+ @kwarg eps: Tolerance for root finding and validation (C{scalar}), use a negative
902
+ value to skip validation.
903
+ @kwarg name: Optional C{B{name}="height4"} (C{str}).
904
+
905
+ @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x}, C{y}
906
+ and C{z} of the projection on or the intersection with and with the height
907
+ C{h} above or below the tri-/biaxial's surface in C{meter}, conventionally.
908
+
909
+ @raise TriaxialError: Non-cartesian B{C{xyz}}, invalid B{C{eps}}, no convergence in
910
+ root finding or validation failed.
911
+
912
+ @see: Methods L{Triaxial.normal3d} and L{Ellipsoid.height4}, I{Eberly}'s U{Distance from a Point to ...
913
+ <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>} and I{Bektas}'
914
+ U{Shortest Distance from a Point to Triaxial Ellipsoid<https://www.ResearchGate.net/publication/
915
+ 272149005_SHORTEST_DISTANCE_FROM_A_POINT_TO_TRIAXIAL_ELLIPSOID>}.
916
+ '''
917
+ v = _otherV3d_(x_xyz, y, z)
918
+ T = _tri_biaxial(tri_biax, height4)
919
+ r = T.isSpherical
920
+
921
+ i, h = None, v.length
922
+ if h < EPS0: # EPS
923
+ x = y = z = _0_0
924
+ h -= min(T._abc3) # nearest
925
+ elif r: # .isSpherical
926
+ x, y, z = v.times(r / h).xyz3
927
+ h -= r
928
+ else:
929
+ x, y, z = v.xyz3
930
+ try:
931
+ if normal: # plumb to surface
932
+ x, y, z, h, i = _plumbTo5(x, y, z, T, eps=eps)
933
+ else: # radial to center
934
+ x, y, z = T._radialTo3(z, hypot(x, y), y, x)
935
+ h = v.minus_(x, y, z).length
936
+ except Exception as e:
937
+ raise TriaxialError(x=x, y=y, z=z, cause=e)
938
+ if h > 0 and T.sideOf(v, eps=EPS0) < 0:
939
+ h = -h # inside
940
+ n = _name__(name, name__=height4) # typename
941
+ return Vector4Tuple(x, y, z, h, iteration=i, name=n)
942
+
943
+
944
+ def _ordered(a, b, c):
945
+ '''(INTERNAL) Is C{a >= b >= c > 0}?
946
+ '''
947
+ if not (_isfinite(a) and a >= b >= c > 0):
948
+ raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
949
+
950
+
951
+ def _plumbTo3(px, py, E, eps=EPS): # in .ellipsoids.Ellipsoid.height4
952
+ '''(INTERNAL) Nearest point in 1st quadrant on a 2-D ellipse.
953
+ '''
954
+ a, b = E.a, E.b
955
+ if min(px, py, a, b) < EPS0:
956
+ raise _AssertionError(px=px, py=py, a=a, b=b, E=E)
957
+
958
+ a2 = a - b * E.b_a
959
+ b2 = b - a * E.a_b
960
+ tx = ty = _SQRT2_2
961
+ for i in range(16): # max 5
962
+ ex = tx**3 * a2
963
+ ey = ty**3 * b2
964
+
965
+ qx = px - ex
966
+ qy = py - ey
967
+ q = hypot(qx, qy)
968
+ if q < EPS0: # PYCHOK no cover
969
+ break
970
+ r = hypot(ex - tx * a,
971
+ ey - ty * b) / q
972
+
973
+ sx, tx = tx, min(_1_0, max(0, (ex + qx * r) / a))
974
+ sy, ty = ty, min(_1_0, max(0, (ey + qy * r) / b))
975
+ t = hypot(ty, tx)
976
+ if t < EPS0: # PYCHOK no cover
977
+ break
978
+ tx = tx / t # /= chokes PyChecker
979
+ ty = ty / t
980
+ if fabs(sx - tx) < eps and \
981
+ fabs(sy - ty) < eps:
982
+ break
983
+
984
+ tx *= a / px
985
+ ty *= b / py
986
+ return tx, ty, i # x and y as fractions
987
+
988
+
989
+ def _plumbTo4(x, y, a, b, eps=EPS):
990
+ '''(INTERNAL) Nearest point on and distance to a 2-D ellipse, I{unordered}.
991
+
992
+ @see: Function C{_plumbTo3} and I{Eberly}'s U{Distance from a Point to ... an Ellipse ...
993
+ <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
994
+ '''
995
+ if b > a:
996
+ b, a, d, i = _plumbTo4(y, x, b, a, eps=eps)
997
+ return a, b, d, i
998
+
999
+ if not (b > 0 and _isfinite(a)):
1000
+ raise _ValueError(a=a, b=b)
1001
+
1002
+ i = None
1003
+ if y:
1004
+ if x:
1005
+ u = fabs(x / a)
1006
+ w = fabs(y / b)
1007
+ g = _hypot2_1(u, w)
1008
+ if fabs(g) > EPS02:
1009
+ r = (b / a)**2
1010
+ t, i = _rootNd(_1_0 / r, 0, u, 0, w, g) # eps
1011
+ a = _over(x, t * r + _1_0)
1012
+ b = _over(y, t + _1_0)
1013
+ d = hypot(x - a, y - b)
1014
+ else: # on the ellipse
1015
+ a, b, d = x, y, _0_0
1016
+ else: # x == 0
1017
+ if y < 0:
1018
+ b = -b
1019
+ a = x # _copysign_0_0
1020
+ d = fabs(y - b)
1021
+
1022
+ elif x: # y == 0
1023
+ d, r = None, _over0(a * x, (a + b) * (a - b))
1024
+ if r:
1025
+ a *= r
1026
+ r = _1_0 - r**2
1027
+ if r > EPS02:
1028
+ b *= sqrt(r)
1029
+ d = hypot(x - a, y - b)
1030
+ elif x < 0:
1031
+ a = -a
1032
+ if d is None:
1033
+ b = y # _copysign_0_0
1034
+ d = fabs(x - a)
1035
+
1036
+ else: # x == y == 0
1037
+ a = x # _copysign_0_0
1038
+ d = b
1039
+
1040
+ return a, b, d, i
1041
+
1042
+
1043
+ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1044
+ '''(INTERNAL) Nearest point on and distance to an I{un-/ordered} triaxial.
1045
+
1046
+ @see: I{Eberly}'s U{Distance from a Point to ... an Ellipsoid ...<https://
1047
+ www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1048
+ '''
1049
+ a, b, c, T = Tun._ordered4
1050
+ if Tun is not T: # T is ordered, Tun isn't
1051
+ t = T._order3(x, y, z) + (T,)
1052
+ a, b, c, d, i = _plumbTo5(*t, eps=eps)
1053
+ return T._order3(a, b, c, reverse=True) + (d, i)
1054
+
1055
+ if not (c > 0 and _isfinite(a)):
1056
+ raise _ValueError(a=a, b=b, c=c)
1057
+
1058
+ if eps > 0:
1059
+ val = max(eps * 1e8, EPS)
1060
+ else: # no validation
1061
+ val, eps = 0, max(EPS0, -eps)
1062
+
1063
+ i = None
1064
+ if z:
1065
+ if y:
1066
+ if x:
1067
+ u = fabs(x / a)
1068
+ v = fabs(y / b)
1069
+ w = fabs(z / c)
1070
+ g = _hypot2_1(u, v, w)
1071
+ if fabs(g) > EPS02:
1072
+ r = T._c2_a2 # (c / a)**2
1073
+ s = T._c2_b2 # (c / b)**2
1074
+ t, i = _rootNd(_1_0 / r, _1_0 / s, u, v, w, g) # eps
1075
+ a = _over(x, t * r + _1_0)
1076
+ b = _over(y, t * s + _1_0)
1077
+ c = _over(z, t + _1_0)
1078
+ d = hypot_(x - a, y - b, z - c)
1079
+ else: # on the ellipsoid
1080
+ a, b, c, d = x, y, z, _0_0
1081
+ else: # x == 0
1082
+ a = x # = _copysign_0_0(x)
1083
+ b, c, d, i = _plumbTo4(y, z, b, c, eps=eps)
1084
+ elif x: # y == 0
1085
+ b = y # = _copysign_0_0(y)
1086
+ a, c, d, i = _plumbTo4(x, z, a, c, eps=eps)
1087
+ else: # x == y == 0
1088
+ if z < 0:
1089
+ c = -c
1090
+ a, b, d = x, y, fabs(z - c)
1091
+
1092
+ else: # z == 0
1093
+ u = _over0(a * x, T._a2c2) # (a + c) * (a - c)
1094
+ v = _over0(b * y, T._b2c2) # (b + c) * (b - c)
1095
+ s = _hypot2_1(u, v)
1096
+ if u and v and s < 0:
1097
+ a *= u
1098
+ b *= v
1099
+ c *= sqrt(-s)
1100
+ d = hypot_(x - a, y - b, c)
1101
+ else:
1102
+ c = z # _copysign_0_0(z)
1103
+ a, b, d, i = _plumbTo4(x, y, a, b, eps=eps)
1104
+
1105
+ if val > 0:
1106
+ _validate(a, b, c, d, T, x, y, z, val)
1107
+ return a, b, c, d, i
1108
+
1109
+
1110
+ def _reverseLatLon3(s, atan2_, v, forward_):
1111
+ '''(INTERNAL) Helper for C{.reverseBetaOmega} and C{.reverseLatLon}.
1112
+ '''
1113
+ x, y, z = s.xyz3
1114
+ d = hypot( x, y)
1115
+ a = atan2_(z, d)
1116
+ b = atan2_(y, x)
1117
+ h = v.minus_(*forward_(z, d, y, x)).length
1118
+ return a, b, (h or INT0)
1119
+
1120
+
1121
+ def _rootNd(r, s, u, v, w, g, eps=EPS0):
1122
+ '''(INTERNAL) Robust 2-D or 3-D root finder: 2-D if C{s == v == 0} else 3-D root.
1123
+
1124
+ @see: I{Eberly}'s U{Robust Root Finders ... and Listing 4<https://
1125
+ www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1126
+ '''
1127
+ u *= r
1128
+ v *= s # 0 for 2-D root
1129
+ t0 = w - _1_0
1130
+ t1 = _0_0 if g < 0 else (hypot_(u, w, v) - _1_0)
1131
+ # assert t0 <= t1
1132
+ for i in range(1, _TRIPS): # 48..58
1133
+ t = (t1 + t0) * _0_5
1134
+ e = t1 - t0
1135
+ if eps > e > -eps or _isin(t, t0, t1):
1136
+ break
1137
+ g = fsumf_(_N_1_0, # ~= _hypot2_1
1138
+ _over02(u, t + r),
1139
+ _over02(w, t + _1_0), (
1140
+ _over02(v, t + s) if v else _0_0))
1141
+ if g > 0:
1142
+ t0 = t
1143
+ elif g < 0:
1144
+ t1 = t
1145
+ else:
1146
+ break
1147
+ else: # PYCHOK no cover
1148
+ t = Fmt.no_convergence(e, eps)
1149
+ raise _ValueError(t, txt__=_rootNd)
1150
+ return t, i
1151
+
1152
+
1153
+ def _tri_biaxial(tri_biax, where):
1154
+ '''(INTERNAL) Get a triaxail for C{tri_biax}.
1155
+ '''
1156
+ if isinstance(tri_biax, _UnOrderedTriaxialBase):
1157
+ T = tri_biax
1158
+ else:
1159
+ D = tri_biax if isinstance(tri_biax, Datum) else \
1160
+ _spherical_datum(tri_biax, name__=where) # typename
1161
+ T = D.ellipsoid._triaxial
1162
+ return T
1163
+
1164
+
1165
+ def _validate(a, b, c, d, T, x, y, z, val):
1166
+ '''(INTERNAL) Validate an C{_plumTo5} result.
1167
+ '''
1168
+ e = T.sideOf(a, b, c, eps=val)
1169
+ if e: # not near the ellipsoid's surface
1170
+ raise _ValueError(a=a, b=b, c=c, d=d,
1171
+ sideOf=e, eps=val)
1172
+ if d: # angle of delta and normal vector
1173
+ m = Vector3d(x, y, z).minus_(a, b, c)
1174
+ if m.euclid > val:
1175
+ m = m.unit()
1176
+ n = T.normal3d(a, b, c)
1177
+ e = n.dot(m) # n.negate().dot(m)
1178
+ if not isnear1(fabs(e), eps1=val):
1179
+ raise _ValueError(n=n, m=m,
1180
+ dot=e, eps=val)
1181
+
1182
+
1183
+ if __name__ == _DMAIN_:
1184
+
1185
+ from pygeodesy import printf
1186
+ from pygeodesy.interns import _COMMA_, _NL_, _NLATvar_
1187
+
1188
+ T = Triaxial_(6378388.0, 6378318.0, 6356911.9461)
1189
+ t = T.height4(3909863.9271, 3909778.123, 3170932.5016)
1190
+ printf('# Bektas: %r', t)
1191
+
1192
+ # __doc__ of this file, force all into registery
1193
+ t = [NN] + Triaxials.toRepr(all=True, asorted=True).split(_NL_)
1194
+ printf(_NLATvar_.join(i.strip(_COMMA_) for i in t))
1195
+
1196
+ # % python3 -m pygeodesy.triaxials
1197
+ #
1198
+ # Bektas: height4(x=3909251.554667, y=3909165.750567, z=3170432.501602, h=999.999996)
1199
+
1200
+ # **) MIT License
1201
+ #
1202
+ # Copyright (C) 2022-2026 -- mrJean1 at Gmail -- All Rights Reserved.
1203
+ #
1204
+ # Permission is hereby granted, free of charge, to any person obtaining a
1205
+ # copy of this software and associated documentation files (the "Software"),
1206
+ # to deal in the Software without restriction, including without limitation
1207
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1208
+ # and/or sell copies of the Software, and to permit persons to whom the
1209
+ # Software is furnished to do so, subject to the following conditions:
1210
+ #
1211
+ # The above copyright notice and this permission notice shall be included
1212
+ # in all copies or substantial portions of the Software.
1213
+ #
1214
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1215
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1216
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1217
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1218
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1219
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1220
+ # OTHER DEALINGS IN THE SOFTWARE.