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
pygeodesy/triaxials.py DELETED
@@ -1,1566 +0,0 @@
1
-
2
- # -*- coding: utf-8 -*-
3
-
4
- u'''Triaxal ellipsoid classes I{ordered} L{Triaxial} and I{unordered} L{Triaxial_} and Jacobi
5
- conformal projections L{JacobiConformal} and L{JacobiConformalSpherical}, transcoded from
6
- I{Charles Karney}'s C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/C++/doc/
7
- classGeographicLib_1_1JacobiConformal.html#details>} to pure Python and miscellaneous classes
8
- L{BetaOmega2Tuple}, L{BetaOmega3Tuple}, L{Jacobi2Tuple} and L{TriaxialError}.
9
-
10
- Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024). For more information,
11
- see the U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
12
-
13
- @see: U{Geodesics on a triaxial ellipsoid<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
14
- Geodesics_on_a_triaxial_ellipsoid>} and U{Triaxial coordinate systems and their geometrical
15
- interpretation<https://www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
16
-
17
- @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)
18
- @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)
19
- @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)
20
- @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)
21
- @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)
22
- @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)
23
- @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)
24
- @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)
25
- @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)
26
- @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)
27
- @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)
28
- @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)
29
- '''
30
- # make sure int/int division yields float quotient, see .basics
31
- from __future__ import division as _; del _ # noqa: E702 ;
32
-
33
- from pygeodesy.basics import _isin, isLatLon, isscalar
34
- from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INT0, PI2, PI_3, PI4, \
35
- _EPS2e4, _SQRT2_2, float0_, isfinite, isnear1, _over, \
36
- _0_0, _0_5, _1_0, _N_1_0, _64_0, _4_0 # PYCHOK used!
37
- from pygeodesy.datums import Datum, _spherical_datum, _WGS84, Ellipsoid, _EWGS84, Fmt
38
- # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
39
- # from pygeodesy.elliptic import Elliptic # _MODS
40
- from pygeodesy.errors import _AssertionError, _ValueError
41
- from pygeodesy.fmath import Fdot, fdot, fmean_, hypot, hypot_, norm2, sqrt0
42
- from pygeodesy.fsums import _Fsumf_, fsumf_, fsum1f_
43
- from pygeodesy.interns import NN, _a_, _b_, _beta_, _c_, _distant_, _DMAIN_, \
44
- _finite_, _height_, _inside_, _near_, _negative_, \
45
- _not_, _NOTEQUAL_, _null_, _opposite_, _outside_, \
46
- _SPACE_, _spherical_, _too_, _x_, _y_
47
- # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .vector3d
48
- from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name__, _NamedEnum, \
49
- _NamedEnumItem, _Pass
50
- from pygeodesy.namedTuples import LatLon3Tuple, _NamedTupleTo, Vector3Tuple, \
51
- Vector4Tuple
52
- from pygeodesy.props import Property_RO, property_ROver
53
- # from pygeodesy.streprs import Fmt # from .datums
54
- from pygeodesy.units import Degrees, Float, Height_, Meter, Meter2, Meter3, \
55
- Radians, Radius, Scalar_
56
- from pygeodesy.utily import asin1, atan2, atan2d, km2m, m2km, SinCos2, sincos2d_
57
- from pygeodesy.vector3d import _otherV3d, Vector3d, _ALL_LAZY, _MODS
58
-
59
- from math import fabs, sqrt
60
-
61
- __all__ = _ALL_LAZY.triaxials
62
- __version__ = '25.05.12'
63
-
64
- _not_ordered_ = _not_('ordered')
65
- _omega_ = 'omega'
66
- _TRIPS = 359 # Eberly 1074?
67
-
68
-
69
- class _NamedTupleToX(_NamedTupleTo): # in .testNamedTuples
70
- '''(INTERNAL) Base class for L{BetaOmega2Tuple},
71
- L{BetaOmega3Tuple} and L{Jacobi2Tuple}.
72
- '''
73
- def _toDegrees(self, name, **toDMS_kwds):
74
- '''(INTERNAL) Convert C{self[0:2]} to L{Degrees} or C{toDMS}.
75
- '''
76
- return self._toX3U(_NamedTupleTo._Degrees3, Degrees, name, *self, **toDMS_kwds)
77
-
78
- def _toRadians(self, name):
79
- '''(INTERNAL) Convert C{self[0:2]} to L{Radians}.
80
- '''
81
- return self._toX3U(_NamedTupleTo._Radians3, Radians, name, *self)
82
-
83
- def _toX3U(self, _X3, U, name, a, b, *c, **kwds):
84
- a, b, s = _X3(self, a, b, **kwds)
85
- if s is None or name:
86
- n = self._name__(name)
87
- s = self.classof(a, b, *c, name=n).reUnit(U, U).toUnits()
88
- return s
89
-
90
-
91
- class BetaOmega2Tuple(_NamedTupleToX):
92
- '''2-Tuple C{(beta, omega)} with I{ellipsoidal} lat- and
93
- longitude C{beta} and C{omega} both in L{Radians} (or
94
- L{Degrees}).
95
- '''
96
- _Names_ = (_beta_, _omega_)
97
- _Units_ = (_Pass, _Pass)
98
-
99
- def toDegrees(self, name=NN, **toDMS_kwds):
100
- '''Convert this L{BetaOmega2Tuple} to L{Degrees} or C{toDMS}.
101
-
102
- @kwarg name: Optional C{B{name}=NN} (C{str}).
103
-
104
- @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and
105
- C{omega} both in L{Degrees} or as L{toDMS} strings
106
- provided some B{C{toDMS_kwds}} keyword arguments are
107
- specified.
108
- '''
109
- return self._toDegrees(name, **toDMS_kwds)
110
-
111
- def toRadians(self, **name):
112
- '''Convert this L{BetaOmega2Tuple} to L{Radians}.
113
-
114
- @kwarg name: Optional C{B{name}=NN} (C{str}).
115
-
116
- @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and C{omega}
117
- both in L{Radians}.
118
- '''
119
- return self._toRadians(name)
120
-
121
-
122
- class BetaOmega3Tuple(_NamedTupleToX):
123
- '''3-Tuple C{(beta, omega, height)} with I{ellipsoidal} lat- and
124
- longitude C{beta} and C{omega} both in L{Radians} (or L{Degrees})
125
- and the C{height}, rather the (signed) I{distance} to the triaxial's
126
- surface (measured along the radial line to the triaxial's center)
127
- in C{meter}, conventionally.
128
- '''
129
- _Names_ = BetaOmega2Tuple._Names_ + (_height_,)
130
- _Units_ = BetaOmega2Tuple._Units_ + ( Meter,)
131
-
132
- def toDegrees(self, name=NN, **toDMS_kwds):
133
- '''Convert this L{BetaOmega3Tuple} to L{Degrees} or C{toDMS}.
134
-
135
- @kwarg name: Optional C{B{name}=NN} (C{str}).
136
-
137
- @return: L{BetaOmega3Tuple}C{(beta, omega, height)} with
138
- C{beta} and C{omega} both in L{Degrees} or as
139
- L{toDMS} strings provided some B{C{toDMS_kwds}}
140
- keyword arguments are specified.
141
- '''
142
- return self._toDegrees(name, **toDMS_kwds)
143
-
144
- def toRadians(self, **name):
145
- '''Convert this L{BetaOmega3Tuple} to L{Radians}.
146
-
147
- @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
148
-
149
- @return: L{BetaOmega3Tuple}C{(beta, omega, height)} with C{beta}
150
- and C{omega} both in L{Radians}.
151
- '''
152
- return self._toRadians(name)
153
-
154
- def to2Tuple(self, **name):
155
- '''Reduce this L{BetaOmega3Tuple} to a L{BetaOmega2Tuple}.
156
-
157
- @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
158
- '''
159
- return BetaOmega2Tuple(*self[:2], name=self._name__(name))
160
-
161
-
162
- class Jacobi2Tuple(_NamedTupleToX):
163
- '''2-Tuple C{(x, y)} with a Jacobi Conformal C{x} and C{y}
164
- projection, both in L{Radians} (or L{Degrees}).
165
- '''
166
- _Names_ = (_x_, _y_)
167
- _Units_ = (_Pass, _Pass)
168
-
169
- def toDegrees(self, name=NN, **toDMS_kwds):
170
- '''Convert this L{Jacobi2Tuple} to L{Degrees} or C{toDMS}.
171
-
172
- @kwarg name: Optional C{B{name}=NN} (C{str}).
173
-
174
- @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both
175
- in L{Degrees} or as L{toDMS} strings provided some
176
- B{C{toDMS_kwds}} keyword arguments are specified.
177
- '''
178
- return self._toDegrees(name, **toDMS_kwds)
179
-
180
- def toRadians(self, **name):
181
- '''Convert this L{Jacobi2Tuple} to L{Radians}.
182
-
183
- @kwarg name: Optional C{B{name}=NN} (C{str}).
184
-
185
- @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both in L{Radians}.
186
- '''
187
- return self._toRadians(name)
188
-
189
-
190
- class Triaxial_(_NamedEnumItem):
191
- '''I{Unordered} triaxial ellipsoid and base class.
192
-
193
- Triaxial ellipsoids with right-handed semi-axes C{a}, C{b} and C{c}, oriented
194
- such that the large principal ellipse C{ab} is the equator I{Z}=0, I{beta}=0,
195
- while the small principal ellipse C{ac} is the prime meridian, plane I{Y}=0,
196
- I{omega}=0.
197
-
198
- The four umbilic points, C{abs}(I{omega}) = C{abs}(I{beta}) = C{PI/2}, lie on
199
- the middle principal ellipse C{bc} in plane I{X}=0, I{omega}=C{PI/2}.
200
-
201
- @note: I{Geodetic} C{lat}- and C{lon}gitudes are in C{degrees}, I{geodetic}
202
- C{phi} and C{lam}bda are in C{radians}, but I{ellipsoidal} lat- and
203
- longitude C{beta} and C{omega} are in L{Radians} by default (or in
204
- L{Degrees} if converted).
205
- '''
206
- _ijk = _kji = None
207
- _unordered = True
208
-
209
- def __init__(self, a_triaxial, b=None, c=None, **name):
210
- '''New I{unordered} L{Triaxial_}.
211
-
212
- @arg a_triaxial: Large, C{X} semi-axis (C{scalar}, conventionally in
213
- C{meter}) or an other L{Triaxial} or L{Triaxial_} instance.
214
- @kwarg b: Middle, C{Y} semi-axis (C{meter}, same units as B{C{a}}), required
215
- if C{B{a_triaxial} is scalar}, ignored otherwise.
216
- @kwarg c: Small, C{Z} semi-axis (C{meter}, like B{C{b}}).
217
- @kwarg name: Optional C{B{name}=NN} (C{str}).
218
-
219
- @raise TriaxialError: Invalid semi-axis or -axes.
220
- '''
221
- try:
222
- try:
223
- a = a_triaxial
224
- t = a._abc3
225
- except AttributeError:
226
- t = Radius(a=a), Radius(b=b), Radius(c=c)
227
- except (TypeError, ValueError) as x:
228
- raise TriaxialError(a=a, b=b, c=c, cause=x)
229
- if name:
230
- self.name = name
231
-
232
- a, b, c = self._abc3 = t
233
- if self._unordered: # == not isinstance(self, Triaxial)
234
- s, _, t = sorted(t)
235
- if not (isfinite(t) and s > 0):
236
- raise TriaxialError(a=a, b=b, c=c) # txt=_invalid_
237
- elif not (isfinite(a) and a >= b >= c > 0):
238
- raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
239
- elif not (a > c and self._a2c2 > 0 and self.e2ac > 0):
240
- raise TriaxialError(a=a, c=c, e2ac=self.e2ac, txt=_spherical_)
241
-
242
- def __str__(self):
243
- return self.toStr()
244
-
245
- @Property_RO
246
- def a(self):
247
- '''Get the C{largest, x} semi-axis (C{meter}, conventionally).
248
- '''
249
- a, _, _ = self._abc3
250
- return a
251
-
252
- @Property_RO
253
- def _a2b2(self):
254
- '''(INTERNAL) Get C{a**2 - b**2} == E_sub_e**2.
255
- '''
256
- a, b, _ = self._abc3
257
- return ((a - b) * (a + b)) if a != b else _0_0
258
-
259
- @Property_RO
260
- def _a2_b2(self):
261
- '''(INTERNAL) Get C{(a / b)**2}.
262
- '''
263
- a, b, _ = self._abc3
264
- return (a / b)**2 if a != b else _1_0
265
-
266
- @Property_RO
267
- def _a2c2(self):
268
- '''(INTERNAL) Get C{a**2 - c**2} == E_sub_x**2.
269
- '''
270
- a, _, c = self._abc3
271
- return ((a - c) * (a + c)) if a != c else _0_0
272
-
273
- @Property_RO
274
- def area(self):
275
- '''Get the surface area (C{meter} I{squared}).
276
- '''
277
- c, b, a = sorted(self._abc3)
278
- if a > c:
279
- a = Triaxial(a, b, c).area if a > b else \
280
- Ellipsoid(a, b=c).areax # a == b
281
- else: # a == c == b
282
- a = Meter2(area=a**2 * PI4)
283
- return a
284
-
285
- def area_p(self, p=1.6075):
286
- '''I{Approximate} the surface area (C{meter} I{squared}).
287
-
288
- @kwarg p: Exponent (C{scalar} > 0), 1.6 for near-spherical or 1.5849625007
289
- for "near-flat" triaxials.
290
-
291
- @see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Approximate_formula>}.
292
- '''
293
- a, b, c = self._abc3
294
- if a == b == c:
295
- a *= a
296
- else:
297
- _p = pow
298
- a = _p(fmean_(_p(a * b, p), _p(a * c, p), _p(b * c, p)), _1_0 / p)
299
- return Meter2(area_p=a * PI4)
300
-
301
- @Property_RO
302
- def b(self):
303
- '''Get the C{middle, y} semi-axis (C{meter}, same units as B{C{a}}).
304
- '''
305
- _, b, _ = self._abc3
306
- return b
307
-
308
- @Property_RO
309
- def _b2c2(self):
310
- '''(INTERNAL) Get C{b**2 - c**2} == E_sub_y**2.
311
- '''
312
- _, b, c = self._abc3
313
- return ((b - c) * (b + c)) if b != c else _0_0
314
-
315
- @Property_RO
316
- def c(self):
317
- '''Get the C{smallest, z} semi-axis (C{meter}, same units as B{C{a}}).
318
- '''
319
- _, _, c = self._abc3
320
- return c
321
-
322
- @Property_RO
323
- def _c2_b2(self):
324
- '''(INTERNAL) Get C{(c / b)**2}.
325
- '''
326
- _, b, c = self._abc3
327
- return (c / b)**2 if b != c else _1_0
328
-
329
- @Property_RO
330
- def e2ab(self):
331
- '''Get the C{ab} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (b/a)**2}.
332
- '''
333
- return Float(e2ab=(_1_0 - self._1e2ab) or _0_0)
334
-
335
- @Property_RO
336
- def _1e2ab(self):
337
- '''(INTERNAL) Get C{1 - e2ab} == C{(b/a)**2}.
338
- '''
339
- a, b, _ = self._abc3
340
- return (b / a)**2 if a != b else _1_0
341
-
342
- @Property_RO
343
- def e2ac(self):
344
- '''Get the C{ac} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/a)**2}.
345
- '''
346
- return Float(e2ac=(_1_0 - self._1e2ac) or _0_0)
347
-
348
- @Property_RO
349
- def _1e2ac(self):
350
- '''(INTERNAL) Get C{1 - e2ac} == C{(c/a)**2}.
351
- '''
352
- a, _, c = self._abc3
353
- return (c / a)**2 if a != c else _1_0
354
-
355
- @Property_RO
356
- def e2bc(self):
357
- '''Get the C{bc} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/b)**2}.
358
- '''
359
- return Float(e2bc=(_1_0 - self._1e2bc) or _0_0)
360
-
361
- _1e2bc = _c2_b2 # C{1 - e2bc} == C{(c/b)**2}
362
-
363
- @property_ROver
364
- def _Elliptic(self):
365
- '''(INTERNAL) Get class L{Elliptic}, I{once}.
366
- '''
367
- return _MODS.elliptic.Elliptic # overwrite property_ROver
368
-
369
- def hartzell4(self, pov, los=False, **name):
370
- '''Compute the intersection of this triaxial's surface with a Line-Of-Sight
371
- from a Point-Of-View in space.
372
-
373
- @see: Function L{hartzell4<triaxials.hartzell4>} for further details.
374
- '''
375
- return hartzell4(pov, los=los, tri_biax=self, **name)
376
-
377
- def height4(self, x_xyz, y=None, z=None, normal=True, eps=EPS, **name):
378
- '''Compute the projection on and the height above or below this triaxial's surface.
379
-
380
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
381
- L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
382
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
383
- otherwise.
384
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
385
- @kwarg normal: If C{True}, the projection is the I{perpendicular, plumb} to the
386
- triaxial's surface, otherwise the C{radial} line to the center of
387
- this triaxial (C{bool}).
388
- @kwarg eps: Tolerance for root finding and validation (C{scalar}), use a negative
389
- value to skip validation.
390
- @kwarg name: Optional C{B{name}="heigh4"} (C{str}).
391
-
392
- @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x}, C{y}
393
- and C{z} of the projection on or the intersection with and with the height
394
- C{h} above or below the triaxial's surface in C{meter}, conventionally.
395
-
396
- @raise TriaxialError: Non-cartesian B{C{xyz}}, invalid B{C{eps}}, no convergence in
397
- root finding or validation failed.
398
-
399
- @see: Methods L{Triaxial.normal3d} and L{Ellipsoid.height4}, I{Eberly}'s U{Distance from a Point to ...
400
- <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>} and I{Bektas}'
401
- U{Shortest Distance from a Point to Triaxial Ellipsoid<https://www.ResearchGate.net/publication/
402
- 272149005_SHORTEST_DISTANCE_FROM_A_POINT_TO_TRIAXIAL_ELLIPSOID>}.
403
- '''
404
- v, r = _otherV3d_(x_xyz, y, z), self.isSpherical
405
-
406
- i, h = None, v.length
407
- if h < EPS0: # EPS
408
- x = y = z = _0_0
409
- h -= min(self._abc3) # nearest
410
- elif r: # .isSpherical
411
- x, y, z = v.times(r / h).xyz3
412
- h -= r
413
- else:
414
- x, y, z = v.xyz3
415
- try:
416
- if normal: # plumb to surface
417
- x, y, z, h, i = _plumbTo5(x, y, z, self, eps=eps)
418
- else: # radial to center
419
- x, y, z = self._radialTo3(z, hypot(x, y), y, x)
420
- h = v.minus_(x, y, z).length
421
- except Exception as e:
422
- raise TriaxialError(x=x, y=y, z=z, cause=e)
423
- if h > 0 and self.sideOf(v, eps=EPS0) < 0:
424
- h = -h # inside
425
- n = _name__(name, name__=self.height4) # typename
426
- return Vector4Tuple(x, y, z, h, iteration=i, name=n)
427
-
428
- @Property_RO
429
- def isOrdered(self):
430
- '''Is this triaxial I{ordered} and I{not spherical} (C{bool})?
431
- '''
432
- a, b, c = self._abc3
433
- return bool(a >= b > c) # b > c!
434
-
435
- @Property_RO
436
- def isSpherical(self):
437
- '''Is this triaxial I{spherical} (C{Radius} or INT0)?
438
- '''
439
- a, b, c = self._abc3
440
- return a if a == b == c else INT0
441
-
442
- def _norm2(self, s, c, *a):
443
- '''(INTERNAL) Normalize C{s} and C{c} iff not already.
444
- '''
445
- if fabs(_hypot2_1(s, c)) > EPS02:
446
- s, c = norm2(s, c)
447
- if a:
448
- s, c = norm2(s * self.b, c * a[0])
449
- return float0_(s, c)
450
-
451
- def normal3d(self, x_xyz, y=None, z=None, length=_1_0):
452
- '''Get a 3-D vector at a cartesian I{on and perpendicular to} this triaxial's surface.
453
-
454
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
455
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
456
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
457
- otherwise.
458
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
459
- @kwarg length: Optional, signed length and in-/outward direction (C{scalar}).
460
-
461
- @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing in- or
462
- outward for neg- respectively positive B{C{length}}.
463
-
464
- @raise TriaxialError: Zero length cartesian or vector.
465
-
466
- @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface, use
467
- method L{Triaxial.sideOf} to validate.
468
-
469
- @see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.
470
- '''
471
- # n = 2 * (x / a2, y / b2, z / c2)
472
- # == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered
473
- # == 2 * (x, y / _1e2ab, z / _1e2ac) / a2
474
- # == unit(x, y / _1e2ab, z / _1e2ac).times(length)
475
- x, y, z = _otherV3d_(x_xyz, y, z).xyz3
476
- n = Vector3d(x, y / self._1e2ab,
477
- z / self._1e2ac, name__=self.normal3d)
478
- u = n.length
479
- if u < EPS0:
480
- raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
481
- return n.times(length / u)
482
-
483
- def _order3(self, *abc, **reverse): # reverse=False
484
- '''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.
485
-
486
- @return: 3-Tuple C{(a, b, c)} ordered by or un-ordered
487
- (reverse-ordered) C{ijk} if C{B{reverse}=True}.
488
- '''
489
- ijk = self._order_ijk(**reverse)
490
- return _getitems(abc, *ijk) if ijk else abc
491
-
492
- def _order3d(self, v, **reverse): # reverse=False
493
- '''(INTERNAL) Un-/Order a C{Vector3d}.
494
-
495
- @return: Vector3d(x, y, z) un-/ordered.
496
- '''
497
- ijk = self._order_ijk(**reverse)
498
- return v.classof(*_getitems(v.xyz3, *ijk)) if ijk else v
499
-
500
- @Property_RO
501
- def _ordered4(self):
502
- '''(INTERNAL) Helper for C{_hartzell3} and C{_plumbTo5}.
503
- '''
504
- def _order2(reverse, a, b, c):
505
- '''(INTERNAL) Un-Order C{a}, C{b} and C{c}.
506
-
507
- @return: 2-Tuple C{((a, b, c), ijk)} with C{a} >= C{b} >= C{c}
508
- and C{ijk} a 3-tuple with the initial indices.
509
- '''
510
- i, j, k = range(3)
511
- if a < b:
512
- a, b, i, j = b, a, j, i
513
- if a < c:
514
- a, c, i, k = c, a, k, i
515
- if b < c:
516
- b, c, j, k = c, b, k, j
517
- # reverse (k, j, i) since (a, b, c) is reversed-sorted
518
- ijk = (k, j, i) if reverse else (None if i < j < k else (i, j, k))
519
- return (a, b, c), ijk
520
-
521
- abc, T = self._abc3, self
522
- if not self.isOrdered:
523
- abc, ijk = _order2(False, *abc)
524
- if ijk:
525
- _, kji = _order2(True, *ijk)
526
- T = Triaxial_(*abc)
527
- T._ijk, T._kji = ijk, kji
528
- return abc + (T,)
529
-
530
- def _order_ijk(self, reverse=False):
531
- '''(INTERNAL) Get the un-/order indices.
532
- '''
533
- return self._kji if reverse else self._ijk
534
-
535
- def _radialTo3(self, sbeta, cbeta, somega, comega):
536
- '''(INTERNAL) I{Unordered} helper for C{.height4}.
537
- '''
538
- def _rphi(a, b, sphi, cphi):
539
- # <https://WikiPedia.org/wiki/Ellipse#Polar_form_relative_to_focus>
540
- # polar form: radius(phi) = a * b / hypot(a * sphi, b * cphi)
541
- return (b / hypot(sphi, b / a * cphi)) if a > b else (
542
- (a / hypot(cphi, a / b * sphi)) if a < b else a)
543
-
544
- sa, ca = self._norm2(sbeta, cbeta)
545
- sb, cb = self._norm2(somega, comega)
546
-
547
- a, b, c = self._abc3
548
- if a != b:
549
- a = _rphi(a, b, sb, cb)
550
- if a != c:
551
- c = _rphi(a, c, sa, ca)
552
- t = c * ca
553
- return (t * cb), (t * sb), (c * sa)
554
-
555
- def sideOf(self, x_xyz, y=None, z=None, eps=EPS4):
556
- '''Is a cartesian on, above or below the surface of this triaxial?
557
-
558
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
559
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
560
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
561
- ignored otherwise.
562
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
563
- @kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).
564
-
565
- @return: C{INT0} if C{(B{x}, B{y}, B{z})} is near this triaxial's surface
566
- within tolerance B{C{eps}}, otherwise the signed, radial distance
567
- I{squared} (C{float}), negative for in- or positive for outside
568
- this triaxial.
569
-
570
- @see: Methods L{Triaxial.height4} and L{Triaxial.normal3d}.
571
- '''
572
- v = _otherV3d_(x_xyz, y, z)
573
- s = fsumf_(_N_1_0, *map(_over02, v.xyz3, self._abc3))
574
- return INT0 if fabs(s) < eps else s
575
-
576
- def toEllipsoid(self, **name):
577
- '''Convert this triaxial to an L{Ellipsoid}, provided 2 axes match.
578
-
579
- @kwarg name: Optional C{B{name}=NN} (C{str}).
580
-
581
- @return: An L{Ellipsoid} with north along this C{Z} axis if C{a == b},
582
- this C{Y} axis if C{a == c} or this C{X} axis if C{b == c}.
583
-
584
- @raise TriaxialError: This C{a != b}, C{b != c} and C{c != a}.
585
-
586
- @see: Method L{Ellipsoid.toTriaxial}.
587
- '''
588
- a, b, c = self._abc3
589
- if a == b:
590
- b = c # N = c-Z
591
- elif b == c: # N = a-X
592
- a, b = b, a
593
- elif a != c: # N = b-Y
594
- t = _SPACE_(_a_, _NOTEQUAL_, _b_, _NOTEQUAL_, _c_)
595
- raise TriaxialError(a=a, b=b, c=c, txt=t)
596
- return Ellipsoid(a, b=b, name=self._name__(name))
597
-
598
- def toStr(self, prec=9, **name): # PYCHOK signature
599
- '''Return this C{Triaxial} as a string.
600
-
601
- @kwarg prec: Precision, number of decimal digits (0..9).
602
- @kwarg name: Optional name (C{str}), to override or C{None}
603
- to exclude this triaxial's name.
604
-
605
- @return: This C{Triaxial}'s attributes (C{str}).
606
- '''
607
- T = Triaxial_
608
- t = T.a,
609
- J = JacobiConformalSpherical
610
- t += (J.ab, J.bc) if isinstance(self, J) else (T.b, T.c)
611
- t += T.e2ab, T.e2bc, T.e2ac
612
- J = JacobiConformal
613
- if isinstance(self, J):
614
- t += J.xyQ2,
615
- t += T.volume, T.area
616
- return self._instr(area_p=self.area_p(), prec=prec, props=t, **name)
617
-
618
- @Property_RO
619
- def unOrdered(self):
620
- '''Is this triaxial I{un-ordered} and I{not spherical} (C{bool})?
621
- '''
622
- return not (self.isOrdered or bool(self.isSpherical))
623
-
624
- @Property_RO
625
- def volume(self):
626
- '''Get the volume (C{meter**3}), M{4 / 3 * PI * a * b * c}.
627
- '''
628
- a, b, c = self._abc3
629
- return Meter3(volume=a * b * c * PI_3 * _4_0)
630
-
631
-
632
- class Triaxial(Triaxial_):
633
- '''I{Ordered} triaxial ellipsoid.
634
-
635
- @see: L{Triaxial_} for more information.
636
- '''
637
- _unordered = False
638
-
639
- def __init__(self, a_triaxial, b=None, c=None, **name):
640
- '''New I{ordered} L{Triaxial}.
641
-
642
- @arg a_triaxial: Largest semi-axis (C{scalar}, conventionally in C{meter})
643
- or an other L{Triaxial} or L{Triaxial_} instance.
644
- @kwarg b: Middle semi-axis (C{meter}, same units as B{C{a}}), required
645
- if C{B{a_triaxial} is scalar}, ignored otherwise.
646
- @kwarg c: Smallest semi-axis (C{meter}, like B{C{b}}).
647
- @kwarg name: Optional C{B{name}=NN} (C{str}).
648
-
649
- @note: The semi-axes must be ordered as C{B{a} >= B{b} >= B{c} > 0} and
650
- must be ellipsoidal, C{B{a} > B{c}}.
651
-
652
- @raise TriaxialError: Semi-axes unordered, spherical or invalid.
653
- '''
654
- Triaxial_.__init__(self, a_triaxial, b=b, c=c, **name)
655
-
656
- @Property_RO
657
- def _a2b2_a2c2(self):
658
- '''@see: Methods C{.forwardBetaOmega} and C{._k2_kp2}.
659
- '''
660
- return self._a2b2 / self._a2c2
661
-
662
- @Property_RO
663
- def area(self):
664
- '''Get the surface area (C{meter} I{squared}).
665
-
666
- @see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Surface_area>}.
667
- '''
668
- a, b, c = self._abc3
669
- if a != b:
670
- kp2, k2 = self._k2_kp2 # swapped!
671
- aE = self._Elliptic(k2, _0_0, kp2, _1_0)
672
- c2 = self._1e2ac # cos(phi)**2 = (c/a)**2
673
- s = sqrt(self.e2ac) # sin(phi)**2 = 1 - c2
674
- r = asin1(s) # phi = atan2(sqrt(c2), s)
675
- b *= fsum1f_(aE.fE(r) * s, (c / a) * (c / b),
676
- aE.fF(r) * c2 / s)
677
- a = Meter2(area=a * b * PI2)
678
- else: # a == b > c
679
- a = Ellipsoid(a, b=c).areax
680
- return a
681
-
682
- def forwardBetaOmega(self, beta, omega, height=0, **name):
683
- '''Convert I{ellipsoidal} lat- C{beta}, longitude C{omega} and C{height}
684
- to cartesian.
685
-
686
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
687
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
688
- @arg height: Height above or below the ellipsoid's surface (C{meter}, same
689
- units as this triaxial's C{a}, C{b} and C{c} semi-axes).
690
- @kwarg name: Optional C{B{name}=NN} (C{str}).
691
-
692
- @return: A L{Vector3Tuple}C{(x, y, z)}.
693
-
694
- @see: Method L{Triaxial.reverseBetaOmega} and U{expressions (23-25)<https://
695
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
696
- '''
697
- if height:
698
- z = self._Height(height) + self.c
699
- if z > 0:
700
- z2 = z**2
701
- x = z * _sqrt0(_1_0 + self._a2c2 / z2)
702
- y = z * _sqrt0(_1_0 + self._b2c2 / z2)
703
- else:
704
- x = y = z = _0_0
705
- else:
706
- x, y, z = self._abc3
707
- if z: # and x and y:
708
- sa, ca = SinCos2(beta)
709
- sb, cb = SinCos2(omega)
710
-
711
- r = self._a2b2_a2c2
712
- x *= cb * _sqrt0(ca**2 + sa**2 * r)
713
- y *= ca * sb
714
- z *= sa * _sqrt0(_1_0 - cb**2 * r)
715
- return Vector3Tuple(x, y, z, **name)
716
-
717
- def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
718
- '''Convert I{ellipsoidal} lat- and longitude C{beta} and C{omega}
719
- to cartesian coordinates I{on the triaxial's surface}.
720
-
721
- @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
722
- @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
723
- @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
724
- @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
725
- @kwarg name: Optional C{B{name}=NN} (C{str}).
726
-
727
- @return: A L{Vector3Tuple}C{(x, y, z)} on the surface.
728
-
729
- @raise TriaxialError: This triaxial is near-spherical.
730
-
731
- @see: Method L{Triaxial.reverseBetaOmega}, U{Triaxial ellipsoid coordinate
732
- system<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
733
- Triaxial_ellipsoid_coordinate_system>} and U{expressions (23-25)<https://
734
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
735
- '''
736
- t = self._radialTo3(sbeta, cbeta, somega, comega)
737
- return Vector3Tuple(*t, **name)
738
-
739
- def forwardCartesian(self, x_xyz, y=None, z=None, **normal_eps_name):
740
- '''Project a cartesian on this triaxial.
741
-
742
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
743
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
744
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
745
- ignored otherwise.
746
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
747
- @kwarg normal_eps_name: Optional keyword arguments C{B{normal}=True},
748
- C{B{eps}=EPS} and overriding C{B{name}="height4"} (C{str}),
749
- see method L{Triaxial.height4}.
750
-
751
- @see: Method L{Triaxial.height4} for further information and method
752
- L{Triaxial.reverseCartesian} to reverse the projection.
753
- '''
754
- return self.height4(x_xyz, y, z, **normal_eps_name)
755
-
756
- def forwardLatLon(self, lat, lon, height=0, **name):
757
- '''Convert I{geodetic} lat-, longitude and height to cartesian.
758
-
759
- @arg lat: Geodetic latitude (C{degrees}).
760
- @arg lon: Geodetic longitude (C{degrees}).
761
- @arg height: Height above the ellipsoid (C{meter}, same units
762
- as this triaxial's C{a}, C{b} and C{c} axes).
763
- @kwarg name: Optional C{B{name}=NN} (C{str}).
764
-
765
- @return: A L{Vector3Tuple}C{(x, y, z)}.
766
-
767
- @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
768
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
769
- '''
770
- return self._forwardLatLon3(height, name, *sincos2d_(lat, lon))
771
-
772
- def forwardLatLon_(self, slat, clat, slon, clon, height=0, **name):
773
- '''Convert I{geodetic} lat-, longitude and height to cartesian.
774
-
775
- @arg slat: Geodetic latitude C{sin(lat)} (C{scalar}).
776
- @arg clat: Geodetic latitude C{cos(lat)} (C{scalar}).
777
- @arg slon: Geodetic longitude C{sin(lon)} (C{scalar}).
778
- @arg clon: Geodetic longitude C{cos(lon)} (C{scalar}).
779
- @arg height: Height above the ellipsoid (C{meter}, same units
780
- as this triaxial's axes C{a}, C{b} and C{c}).
781
- @kwarg name: Optional C{B{name}=NN} (C{str}).
782
-
783
- @return: A L{Vector3Tuple}C{(x, y, z)}.
784
-
785
- @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
786
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
787
- '''
788
- sa, ca = self._norm2(slat, clat)
789
- sb, cb = self._norm2(slon, clon)
790
- return self._forwardLatLon3(height, name, sa, ca, sb, cb)
791
-
792
- def _forwardLatLon3(self, height, name, sa, ca, sb, cb): # name always **name
793
- '''(INTERNAL) Helper for C{.forwardLatLon} and C{.forwardLatLon_}.
794
- '''
795
- ca_x_sb = ca * sb
796
- h = self._Height(height)
797
- # 1 - (1 - (c/a)**2) * sa**2 - (1 - (b/a)**2) * ca**2 * sb**2
798
- t = fsumf_(_1_0, -self.e2ac * sa**2, -self.e2ab * ca_x_sb**2)
799
- n = self.a / _sqrt0(t) # prime vertical
800
- x = (h + n) * ca * cb
801
- y = (h + n * self._1e2ab) * ca_x_sb
802
- z = (h + n * self._1e2ac) * sa
803
- return Vector3Tuple(x, y, z, **name)
804
-
805
- def _Height(self, height):
806
- '''(INTERNAL) Validate a C{height}.
807
- '''
808
- return Height_(height=height, low=-self.c, Error=TriaxialError)
809
-
810
- @Property_RO
811
- def _k2_kp2(self):
812
- '''(INTERNAL) Get C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.area}.
813
- '''
814
- # k2 = a2b2 / a2c2 * c2_b2
815
- # kp2 = b2c2 / a2c2 * a2_b2
816
- # b2 = b**2
817
- # xE = Elliptic(k2, -a2b2 / b2, kp2, a2_b2)
818
- # yE = Elliptic(kp2, +b2c2 / b2, k2, c2_b2)
819
- # aE = Elliptic(kp2, 0, k2, 1)
820
- return (self._a2b2_a2c2 * self._c2_b2,
821
- self._b2c2 / self._a2c2 * self._a2_b2)
822
-
823
- def _radialTo3(self, sbeta, cbeta, somega, comega):
824
- '''(INTERNAL) Convert I{ellipsoidal} lat- and longitude C{beta} and
825
- C{omega} to cartesian coordinates I{on the triaxial's surface},
826
- also I{ordered} helper for C{.height4}.
827
- '''
828
- sa, ca = self._norm2(sbeta, cbeta)
829
- sb, cb = self._norm2(somega, comega)
830
-
831
- b2_a2 = self._1e2ab # == (b/a)**2
832
- c2_a2 = -self._1e2ac # == -(c/a)**2
833
- a2c2_a2 = self. e2ac # (a**2 - c**2) / a**2 == 1 - (c/a)**2
834
-
835
- x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)
836
- z2 = _Fsumf_(c2_a2, sb**2, b2_a2 * cb**2).fover(a2c2_a2)
837
-
838
- x, y, z = self._abc3
839
- x *= cb * _sqrt0(x2)
840
- y *= ca * sb
841
- z *= sa * _sqrt0(z2)
842
- return x, y, z
843
-
844
- def reverseBetaOmega(self, x_xyz, y=None, z=None, **name):
845
- '''Convert cartesian to I{ellipsoidal} lat- and longitude, C{beta}, C{omega}
846
- and height.
847
-
848
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
849
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
850
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
851
- ignored otherwise.
852
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
853
- @kwarg name: Optional C{B{name}=NN} (C{str}).
854
-
855
- @return: A L{BetaOmega3Tuple}C{(beta, omega, height)} with C{beta} and
856
- C{omega} in L{Radians} and (radial) C{height} in C{meter}, same
857
- units as this triaxial's axes.
858
-
859
- @see: Methods L{Triaxial.forwardBetaOmega} and L{Triaxial.forwardBetaOmega_}
860
- and U{expressions (21-22)<https://www.Topo.Auth.GR/wp-content/uploads/
861
- sites/111/2021/12/09_Panou.pdf>}.
862
- '''
863
- v = _otherV3d_(x_xyz, y, z)
864
- a, b, h = self._reverseLatLon3(v, atan2, v, self.forwardBetaOmega_)
865
- return BetaOmega3Tuple(Radians(beta=a), Radians(omega=b), h, **name)
866
-
867
- def reverseCartesian(self, x_xyz, y=None, z=None, h=0, normal=True, eps=_EPS2e4, **name):
868
- '''Unproject" a cartesian I{on} this triaxial's surface to a cartesion I{off}
869
- this triaxial's surface.
870
-
871
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
872
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
873
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
874
- ignored otherwise.
875
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
876
- @arg h: Height above or below this triaxial's surface (C{meter}, same units
877
- as this triaxial's axes).
878
- @kwarg normal: If C{True}, the height is C{normal} to the surface, otherwise
879
- C{radially} to the center of this triaxial (C{bool}).
880
- @kwarg eps: Tolerance for on-surface test (C{scalar}), see method L{Triaxial.sideOf}.
881
- @kwarg name: Optional C{B{name}=NN} (C{str}).
882
-
883
- @return: A L{Vector3Tuple}C{(x, y, z)}.
884
-
885
- @raise TrialError: Cartesian C{(x, y, z)} not on this triaxial's surface.
886
-
887
- @see: Methods L{Triaxial.forwardCartesian} and L{Triaxial.height4}.
888
- '''
889
- v = _otherV3d_(x_xyz, y, z, **name)
890
- s = self.sideOf(v.xyz, eps=eps)
891
- if s: # PYCHOK no cover
892
- t = _SPACE_((_inside_ if s < 0 else _outside_), self.toRepr())
893
- raise TriaxialError(eps=eps, sideOf=s, x=v.x, y=v.y, z=v.z, txt=t)
894
-
895
- if h:
896
- if normal:
897
- v = v.plus(self.normal3d(*v.xyz, length=h))
898
- elif v.length > EPS0:
899
- v = v.times(_1_0 + (h / v.length))
900
- return v.xyz # Vector3Tuple
901
-
902
- def reverseLatLon(self, x_xyz, y=None, z=None, **name):
903
- '''Convert cartesian to I{geodetic} lat-, longitude and height.
904
-
905
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
906
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
907
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
908
- ignored otherwise.
909
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
910
- @kwarg name: Optional C{B{name}=NN} (C{str}).
911
-
912
- @return: A L{LatLon3Tuple}C{(lat, lon, height)} with C{lat} and C{lon}
913
- in C{degrees} and (radial) C{height} in C{meter}, same units
914
- as this triaxial's axes.
915
-
916
- @see: Methods L{Triaxial.forwardLatLon} and L{Triaxial.forwardLatLon_}
917
- and U{expressions (4-5)<https://www.Topo.Auth.GR/wp-content/uploads/
918
- sites/111/2021/12/09_Panou.pdf>}.
919
- '''
920
- v = _otherV3d_(x_xyz, y, z)
921
- s = v.times_(self._1e2ac, # == 1 - e_sub_x**2
922
- self._1e2bc, # == 1 - e_sub_y**2
923
- _1_0)
924
- a, b, h = self._reverseLatLon3(s, atan2d, v, self.forwardLatLon_)
925
- return LatLon3Tuple(Degrees(lat=a), Degrees(lon=b), h, **name)
926
-
927
- def _reverseLatLon3(self, s, atan2_, v, forward_):
928
- '''(INTERNAL) Helper for C{.reverseBetOmg} and C{.reverseLatLon}.
929
- '''
930
- x, y, z = s.xyz3
931
- d = hypot( x, y)
932
- a = atan2_(z, d)
933
- b = atan2_(y, x)
934
- h = v.minus_(*forward_(z, d, y, x)).length
935
- return a, b, h
936
-
937
-
938
- class JacobiConformal(Triaxial):
939
- '''This is a conformal projection of a triaxial ellipsoid to a plane in which the
940
- C{X} and C{Y} grid lines are straight.
941
-
942
- Ellipsoidal coordinates I{beta} and I{omega} are converted to Jacobi Conformal
943
- I{y} respectively I{x} separately. Jacobi's coordinates have been multiplied
944
- by C{sqrt(B{a}**2 - B{c}**2) / (2 * B{b})} so that the customary results are
945
- returned in the case of an ellipsoid of revolution.
946
-
947
- Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2014-2024) and
948
- licensed under the MIT/X11 License.
949
-
950
- @note: This constructor can I{not be used to specify a sphere}, see alternate
951
- L{JacobiConformalSpherical}.
952
-
953
- @see: L{Triaxial}, C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/
954
- C++/doc/classGeographicLib_1_1JacobiConformal.html#details>}, U{Jacobi's conformal
955
- projection<https://GeographicLib.SourceForge.io/C++/doc/jacobi.html>} and Jacobi,
956
- C. G. J. I{U{Vorlesungen über Dynamik<https://Books.Google.com/books?
957
- id=ryEOAAAAQAAJ&pg=PA212>}}, page 212ff.
958
- '''
959
-
960
- @Property_RO
961
- def _xE(self):
962
- '''(INTERNAL) Get the x-elliptic function.
963
- '''
964
- k2, kp2 = self._k2_kp2
965
- # -a2b2 / b2 == (b2 - a2) / b2 == 1 - a2 / b2 == 1 - a2_b2
966
- return self._Elliptic(k2, _1_0 - self._a2_b2, kp2, self._a2_b2)
967
-
968
- def xR(self, omega):
969
- '''Compute a Jacobi Conformal C{x} projection.
970
-
971
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
972
-
973
- @return: The C{x} projection (L{Radians}).
974
- '''
975
- return self.xR_(*SinCos2(omega))
976
-
977
- def xR_(self, somega, comega):
978
- '''Compute a Jacobi Conformal C{x} projection.
979
-
980
- @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
981
- @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
982
-
983
- @return: The C{x} projection (L{Radians}).
984
- '''
985
- s, c = self._norm2(somega, comega, self.a)
986
- return Radians(x=self._xE.fPi(s, c) * self._a2_b2)
987
-
988
- @Property_RO
989
- def xyQ2(self):
990
- '''Get the Jacobi Conformal quadrant size (L{Jacobi2Tuple}C{(x, y)}).
991
- '''
992
- return Jacobi2Tuple(Radians(x=self._a2_b2 * self._xE.cPi),
993
- Radians(y=self._c2_b2 * self._yE.cPi),
994
- name=JacobiConformal.xyQ2.name)
995
-
996
- def xyR2(self, beta, omega, **name):
997
- '''Compute a Jacobi Conformal C{x} and C{y} projection.
998
-
999
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
1000
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
1001
- @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2"}.
1002
-
1003
- @return: A L{Jacobi2Tuple}C{(x, y)}.
1004
- '''
1005
- return self.xyR2_(*(SinCos2(beta) + SinCos2(omega)),
1006
- name=_name__(name, name__=self.xyR2))
1007
-
1008
- def xyR2_(self, sbeta, cbeta, somega, comega, **name):
1009
- '''Compute a Jacobi Conformal C{x} and C{y} projection.
1010
-
1011
- @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
1012
- @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
1013
- @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
1014
- @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
1015
- @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2_"}.
1016
-
1017
- @return: A L{Jacobi2Tuple}C{(x, y)}.
1018
- '''
1019
- return Jacobi2Tuple(self.xR_(somega, comega),
1020
- self.yR_(sbeta, cbeta),
1021
- name=_name__(name, name__=self.xyR2_))
1022
-
1023
- @Property_RO
1024
- def _yE(self):
1025
- '''(INTERNAL) Get the x-elliptic function.
1026
- '''
1027
- kp2, k2 = self._k2_kp2 # swapped!
1028
- # b2c2 / b2 == (b2 - c2) / b2 == 1 - c2 / b2 == e2bc
1029
- return self._Elliptic(k2, self.e2bc, kp2, self._c2_b2)
1030
-
1031
- def yR(self, beta):
1032
- '''Compute a Jacobi Conformal C{y} projection.
1033
-
1034
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
1035
-
1036
- @return: The C{y} projection (L{Radians}).
1037
- '''
1038
- return self.yR_(*SinCos2(beta))
1039
-
1040
- def yR_(self, sbeta, cbeta):
1041
- '''Compute a Jacobi Conformal C{y} projection.
1042
-
1043
- @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
1044
- @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
1045
-
1046
- @return: The C{y} projection (L{Radians}).
1047
- '''
1048
- s, c = self._norm2(sbeta, cbeta, self.c)
1049
- return Radians(y=self._yE.fPi(s, c) * self._c2_b2)
1050
-
1051
-
1052
- class JacobiConformalSpherical(JacobiConformal):
1053
- '''An alternate, I{spherical} L{JacobiConformal} projection.
1054
-
1055
- @see: L{JacobiConformal} for other and more details.
1056
- '''
1057
- _ab = _bc = 0
1058
-
1059
- def __init__(self, radius_triaxial, ab=0, bc=0, **name):
1060
- '''New L{JacobiConformalSpherical}.
1061
-
1062
- @arg radius_triaxial: Radius (C{scalar}, conventionally in
1063
- C{meter}) or an other L{JacobiConformalSpherical},
1064
- L{JacobiConformal} or ordered L{Triaxial}.
1065
- @kwarg ab: Relative magnitude of C{B{a} - B{b}} (C{meter},
1066
- same units as C{scalar B{radius}}.
1067
- @kwarg bc: Relative magnitude of C{B{b} - B{c}} (C{meter},
1068
- same units as C{scalar B{radius}}.
1069
- @kwarg name: Optional C{B{name}=NN} (C{str}).
1070
-
1071
- @raise TriaxialError: Invalid B{C{radius_triaxial}}, negative
1072
- B{C{ab}}, negative B{C{bc}} or C{(B{ab}
1073
- + B{bc})} not positive.
1074
-
1075
- @note: If B{C{radius_triaxial}} is a L{JacobiConformalSpherical}
1076
- and if B{C{ab}} and B{C{bc}} are both zero or C{None},
1077
- the B{C{radius_triaxial}}'s C{ab}, C{bc}, C{a}, C{b}
1078
- and C{c} are copied.
1079
- '''
1080
- try:
1081
- r = radius_triaxial
1082
- if isinstance(r, Triaxial): # ordered only
1083
- t = r._abc3
1084
- j = isinstance(r, JacobiConformalSpherical) and not bool(ab or bc)
1085
- else:
1086
- t = (Radius(radius=r),) * 3
1087
- j = False
1088
- self._ab = r.ab if j else Scalar_(ab=ab) # low=0
1089
- self._bc = r.bc if j else Scalar_(bc=bc) # low=0
1090
- if (self.ab + self.bc) <= 0:
1091
- raise ValueError(_negative_)
1092
- a, _, c = self._abc3 = t
1093
- if not (a >= c and isfinite(self._a2b2)
1094
- and isfinite(self._a2c2)):
1095
- raise ValueError(_not_(_finite_))
1096
- except (TypeError, ValueError) as x:
1097
- raise TriaxialError(radius_triaxial=r, ab=ab, bc=bc, cause=x)
1098
- if name:
1099
- self.name = name
1100
-
1101
- @Property_RO
1102
- def ab(self):
1103
- '''Get relative magnitude C{a - b} (C{meter}, same units as B{C{a}}).
1104
- '''
1105
- return self._ab
1106
-
1107
- @Property_RO
1108
- def _a2b2(self):
1109
- '''(INTERNAL) Get C{a**2 - b**2} == ab * (a + b).
1110
- '''
1111
- a, b, _ = self._abc3
1112
- return self.ab * (a + b)
1113
-
1114
- @Property_RO
1115
- def _a2c2(self):
1116
- '''(INTERNAL) Get C{a**2 - c**2} == a2b2 + b2c2.
1117
- '''
1118
- return self._a2b2 + self._b2c2
1119
-
1120
- @Property_RO
1121
- def bc(self):
1122
- '''Get relative magnitude C{b - c} (C{meter}, same units as B{C{a}}).
1123
- '''
1124
- return self._bc
1125
-
1126
- @Property_RO
1127
- def _b2c2(self):
1128
- '''(INTERNAL) Get C{b**2 - c**2} == bc * (b + c).
1129
- '''
1130
- _, b, c = self._abc3
1131
- return self.bc * (b + c)
1132
-
1133
- @Property_RO
1134
- def radius(self):
1135
- '''Get radius (C{meter}, conventionally).
1136
- '''
1137
- return self.a
1138
-
1139
-
1140
- class TriaxialError(_ValueError):
1141
- '''Raised for L{Triaxial} issues.
1142
- '''
1143
- pass # ...
1144
-
1145
-
1146
- class Triaxials(_NamedEnum):
1147
- '''(INTERNAL) L{Triaxial} registry, I{must} be a sub-class
1148
- to accommodate the L{_LazyNamedEnumItem} properties.
1149
- '''
1150
- def _Lazy(self, *abc, **name):
1151
- '''(INTERNAL) Instantiate the C{Triaxial}.
1152
- '''
1153
- a, b, c = map(km2m, abc)
1154
- return Triaxial(a, b, c, **name)
1155
-
1156
- Triaxials = Triaxials(Triaxial, Triaxial_) # PYCHOK singleton
1157
- '''Some pre-defined L{Triaxial}s, all I{lazily} instantiated.'''
1158
- # <https://ArxIV.org/pdf/1909.06452.pdf> Table 1 Semi-axes in Km
1159
- # <https://www.JPS.NASA.gov/education/images/pdf/ss-moons.pdf>
1160
- # <https://link.Springer.com/article/10.1007/s00190-022-01650-9>
1161
- _abc84_35 = (_EWGS84.a + 35), (_EWGS84.a - 35), _EWGS84.b
1162
- Triaxials._assert( # a (Km) b (Km) c (Km) planet
1163
- Amalthea = _lazy('Amalthea', 125.0, 73.0, _64_0), # Jupiter
1164
- Ariel = _lazy('Ariel', 581.1, 577.9, 577.7), # Uranus
1165
- Earth = _lazy('Earth', 6378.173435, 6378.1039, 6356.7544),
1166
- Enceladus = _lazy('Enceladus', 256.6, 251.4, 248.3), # Saturn
1167
- Europa = _lazy('Europa', 1564.13, 1561.23, 1560.93), # Jupiter
1168
- Io = _lazy('Io', 1829.4, 1819.3, 1815.7), # Jupiter
1169
- Mars = _lazy('Mars', 3394.6, 3393.3, 3376.3),
1170
- Mimas = _lazy('Mimas', 207.4, 196.8, 190.6), # Saturn
1171
- Miranda = _lazy('Miranda', 240.4, 234.2, 232.9), # Uranus
1172
- Moon = _lazy('Moon', 1735.55, 1735.324, 1734.898), # Earth
1173
- Tethys = _lazy('Tethys', 535.6, 528.2, 525.8), # Saturn
1174
- WGS84_35 = _lazy('WGS84_35', *map(m2km, _abc84_35)))
1175
- del _abc84_35, _EWGS84
1176
-
1177
-
1178
- def _getitems(items, *indices):
1179
- '''(INTERNAL) Get the C{items} at the given I{indices}.
1180
-
1181
- @return: C{Type(items[i] for i in indices)} with
1182
- C{Type = type(items)}, any C{type} having
1183
- the special method C{__getitem__}.
1184
- '''
1185
- return type(items)(map(items.__getitem__, indices))
1186
-
1187
-
1188
- def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1189
- '''(INTERNAL) Hartzell's "Satellite Line-of-Sight Intersection ...",
1190
- formula from a Point-Of-View to an I{un-/ordered} Triaxial.
1191
- '''
1192
- def _toUvwV3d(los, pov):
1193
- try: # pov must be LatLon or Cartesian if los is a Los
1194
- v = los.toUvw(pov)
1195
- except (AttributeError, TypeError):
1196
- v = _otherV3d(los=los)
1197
- return v
1198
-
1199
- p3 = _otherV3d(pov=pov.toCartesian() if isLatLon(pov) else pov)
1200
- if los is True: # normal
1201
- a, b, c, d, i = _plumbTo5(p3.x, p3.y, p3.z, Tun)
1202
- return type(p3)(a, b, c), d, i
1203
-
1204
- u3 = p3.negate() if los is False or los is None else _toUvwV3d(los, pov)
1205
-
1206
- a, b, c, T = Tun._ordered4
1207
-
1208
- a2 = a**2 # largest, factored out
1209
- b2, p2 = (b**2, T._1e2ab) if b != a else (a2, _1_0)
1210
- c2, q2 = (c**2, T._1e2ac) if c != a else (a2, _1_0)
1211
-
1212
- p3 = T._order3d(p3)
1213
- u3 = T._order3d(u3).unit() # unit vector, opposing signs
1214
-
1215
- x2, y2, z2 = p3.x2y2z2 # p3.times_(p3).xyz3
1216
- ux, vy, wz = u3.times_(p3).xyz3
1217
- u2, v2, w2 = u3.x2y2z2 # u3.times_(u3).xyz3
1218
-
1219
- t = (p2 * c2), c2, b2
1220
- m = fdot(t, u2, v2, w2) # a2 factored out
1221
- if m < EPS0: # zero or near-null LOS vector
1222
- raise _ValueError(_near_(_null_))
1223
-
1224
- r = fsumf_(b2 * w2, c2 * v2, -v2 * z2, vy * wz * 2,
1225
- -w2 * y2, -u2 * y2 * q2, -u2 * z2 * p2, ux * wz * 2 * p2,
1226
- -w2 * x2 * p2, b2 * u2 * q2, -v2 * x2 * q2, ux * vy * 2 * q2)
1227
- if r > 0: # a2 factored out
1228
- r = sqrt(r) * b * c # == a * a * b * c / a2
1229
- elif r < 0: # LOS pointing away from or missing the triaxial
1230
- raise _ValueError(_opposite_ if max(ux, vy, wz) > 0 else _outside_)
1231
-
1232
- d = Fdot(t, ux, vy, wz).fadd_(r).fover(m) # -r for antipode, a2 factored out
1233
- if d > 0: # POV inside or LOS outside or missing the triaxial
1234
- s = fsumf_(_N_1_0, _over(x2, a2), _over(y2, b2), _over(z2, c2)) # like _sideOf
1235
- raise _ValueError(_outside_ if s > 0 else _inside_)
1236
- elif fsum1f_(x2, y2, z2, -d*d) < 0: # d past triaxial's center
1237
- raise _ValueError(_too_(_distant_))
1238
-
1239
- v = p3.minus(u3.times(d)) # cartesian type(pov) or Vector3d
1240
- h = p3.minus(v).length # distance to pov == -d
1241
- return T._order3d(v, reverse=True), h, None
1242
-
1243
-
1244
- def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1245
- '''Compute the intersection of a tri-/biaxial ellipsoid and a Line-Of-Sight from
1246
- a Point-Of-View outside.
1247
-
1248
- @arg pov: Point-Of-View outside the tri-/biaxial (C{Cartesian}, L{Ecef9Tuple},
1249
- C{LatLon} or L{Vector3d}).
1250
- @kwarg los: Line-Of-Sight, I{direction} to the tri-/biaxial (L{Los}, L{Vector3d})
1251
- or C{True} for the I{normal, perpendicular, plumb} to the surface of
1252
- the tri-/biaxial or C{False} or C{None} to point to its center.
1253
- @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{JacobiConformal} or
1254
- L{JacobiConformalSpherical}) or biaxial ellipsoid (L{Datum},
1255
- L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}) or spherical earth
1256
- radius (C{scalar}, conventionally in C{meter}).
1257
- @kwarg name: Optional name (C{str}), overriding C{B{name}="hartzell4"}.
1258
-
1259
- @return: L{Vector4Tuple}C{(x, y, z, h)} on the tri-/biaxial's surface, with C{h}
1260
- the distance from B{C{pov}} to C{(x, y, z)} I{along the} B{C{los}}, all
1261
- in C{meter}, conventionally.
1262
-
1263
- @raise TriaxialError: Invalid B{C{pov}} or B{C{pov}} inside the tri-/biaxial or
1264
- invalid B{C{los}} or B{C{los}} points outside or away from
1265
- the tri-/biaxial.
1266
-
1267
- @raise TypeError: Invalid B{C{tri_biax}}, C{ellipsoid} or C{datum}.
1268
-
1269
- @see: Class L{pygeodesy3.Los}, functions L{pygeodesy.tyr3d} and L{pygeodesy.hartzell}
1270
- and U{lookAtSpheroid<https://PyPI.org/project/pymap3d>} and U{"Satellite
1271
- Line-of-Sight Intersection with Earth"<https://StephenHartzell.Medium.com/
1272
- satellite-line-of-sight-intersection-with-earth-d786b4a6a9b6>}.
1273
- '''
1274
- if isinstance(tri_biax, Triaxial_):
1275
- T = tri_biax
1276
- else:
1277
- D = tri_biax if isinstance(tri_biax, Datum) else \
1278
- _spherical_datum(tri_biax, name__=hartzell4) # typename
1279
- T = D.ellipsoid._triaxial
1280
- try:
1281
- v, h, i = _hartzell3(pov, los, T)
1282
- except Exception as x:
1283
- raise TriaxialError(pov=pov, los=los, tri_biax=tri_biax, cause=x)
1284
- n = _name__(name, name__=hartzell4) # typename
1285
- return Vector4Tuple(v.x, v.y, v.z, h, iteration=i, name=n)
1286
-
1287
-
1288
- def _hypot2_1(x, y, z=0):
1289
- '''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x), fabs(y),
1290
- fabs(z))} rarely greater than 1.0.
1291
- '''
1292
- return fsumf_(_N_1_0, x*x, y*y, z*z)
1293
-
1294
-
1295
- def _otherV3d_(x_xyz, y, z, **name):
1296
- '''(INTERNAL) Get a Vector3d from C{x_xyz}, C{y} and C{z}.
1297
- '''
1298
- return Vector3d(x_xyz, y, z, **name) if isscalar(x_xyz) else \
1299
- _otherV3d(x_xyz=x_xyz, **name)
1300
-
1301
-
1302
- def _over0(p, q):
1303
- '''(INTERNAL) Return C{p / q} or C{0}.
1304
- '''
1305
- return (p / q) if q > fabs(p) else _0_0
1306
-
1307
-
1308
- def _over02(p, q):
1309
- '''(INTERNAL) Return C{(p / q)**2} or C{0}.
1310
- '''
1311
- return (p / q)**2 if p and q else _0_0
1312
-
1313
-
1314
- def _plumbTo3(px, py, E, eps=EPS): # in .ellipsoids.Ellipsoid.height4
1315
- '''(INTERNAL) Nearest point on a 2-D ellipse in 1st quadrant.
1316
- '''
1317
- a, b = E.a, E.b
1318
- if min(px, py, a, b) < EPS0:
1319
- raise _AssertionError(px=px, py=py, a=a, b=b, E=E)
1320
-
1321
- a2 = a - b * E.b_a
1322
- b2 = b - a * E.a_b
1323
- tx = ty = _SQRT2_2
1324
- for i in range(16): # max 5
1325
- ex = tx**3 * a2
1326
- ey = ty**3 * b2
1327
-
1328
- qx = px - ex
1329
- qy = py - ey
1330
- q = hypot(qx, qy)
1331
- if q < EPS0: # PYCHOK no cover
1332
- break
1333
- r = hypot(ex - tx * a,
1334
- ey - ty * b) / q
1335
-
1336
- sx, tx = tx, min(_1_0, max(0, (ex + qx * r) / a))
1337
- sy, ty = ty, min(_1_0, max(0, (ey + qy * r) / b))
1338
- t = hypot(ty, tx)
1339
- if t < EPS0: # PYCHOK no cover
1340
- break
1341
- tx = tx / t # /= chokes PyChecker
1342
- ty = ty / t
1343
- if fabs(sx - tx) < eps and \
1344
- fabs(sy - ty) < eps:
1345
- break
1346
-
1347
- tx *= a / px
1348
- ty *= b / py
1349
- return tx, ty, i # x and y as fractions
1350
-
1351
-
1352
- def _plumbTo4(x, y, a, b, eps=EPS):
1353
- '''(INTERNAL) Nearest point on and distance to a 2-D ellipse, I{unordered}.
1354
-
1355
- @see: Function C{_plumbTo3} and I{Eberly}'s U{Distance from a Point to ... an Ellipse ...
1356
- <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1357
- '''
1358
- if b > a:
1359
- b, a, d, i = _plumbTo4(y, x, b, a, eps=eps)
1360
- return a, b, d, i
1361
-
1362
- if not (b > 0 and isfinite(a)):
1363
- raise _ValueError(a=a, b=b)
1364
-
1365
- i = None
1366
- if y:
1367
- if x:
1368
- u = fabs(x / a)
1369
- w = fabs(y / b)
1370
- g = _hypot2_1(u, w)
1371
- if fabs(g) > EPS02:
1372
- r = (b / a)**2
1373
- t, i = _rootNd(_1_0 / r, 0, u, 0, w, g) # eps
1374
- a = _over(x, t * r + _1_0)
1375
- b = _over(y, t + _1_0)
1376
- d = hypot(x - a, y - b)
1377
- else: # on the ellipse
1378
- a, b, d = x, y, _0_0
1379
- else: # x == 0
1380
- if y < 0:
1381
- b = -b
1382
- a = x # signed-0
1383
- d = fabs(y - b)
1384
-
1385
- elif x: # y == 0
1386
- d, r = None, _over0(a * x, (a + b) * (a - b))
1387
- if r:
1388
- a *= r
1389
- r = _1_0 - r**2
1390
- if r > EPS02:
1391
- b *= sqrt(r)
1392
- d = hypot(x - a, y - b)
1393
- elif x < 0:
1394
- a = -a
1395
- if d is None:
1396
- b = y # signed-0
1397
- d = fabs(x - a)
1398
-
1399
- else: # x == y == 0
1400
- a = x # signed-0
1401
- d = b
1402
-
1403
- return a, b, d, i
1404
-
1405
-
1406
- def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1407
- '''(INTERNAL) Nearest point on and distance to an I{un-/ordered} triaxial.
1408
-
1409
- @see: I{Eberly}'s U{Distance from a Point to ... an Ellipsoid ...<https://
1410
- www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1411
- '''
1412
- a, b, c, T = Tun._ordered4
1413
- if Tun is not T: # T is ordered, Tun isn't
1414
- t = T._order3(x, y, z) + (T,)
1415
- a, b, c, d, i = _plumbTo5(*t, eps=eps)
1416
- return T._order3(a, b, c, reverse=True) + (d, i)
1417
-
1418
- if not (c > 0 and isfinite(a)):
1419
- raise _ValueError(a=a, b=b, c=c)
1420
-
1421
- if eps > 0:
1422
- val = max(eps * 1e8, EPS)
1423
- else: # no validation
1424
- val, eps = 0, max(EPS0, -eps)
1425
-
1426
- i = None
1427
- if z:
1428
- if y:
1429
- if x:
1430
- u = fabs(x / a)
1431
- v = fabs(y / b)
1432
- w = fabs(z / c)
1433
- g = _hypot2_1(u, v, w)
1434
- if fabs(g) > EPS02:
1435
- r = T._1e2ac # (c / a)**2
1436
- s = T._1e2bc # (c / b)**2
1437
- t, i = _rootNd(_1_0 / r, _1_0 / s, u, v, w, g) # eps
1438
- a = _over(x, t * r + _1_0)
1439
- b = _over(y, t * s + _1_0)
1440
- c = _over(z, t + _1_0)
1441
- d = hypot_(x - a, y - b, z - c)
1442
- else: # on the ellipsoid
1443
- a, b, c, d = x, y, z, _0_0
1444
- else: # x == 0
1445
- a = x # 0
1446
- b, c, d, i = _plumbTo4(y, z, b, c, eps=eps)
1447
- elif x: # y == 0
1448
- b = y # 0
1449
- a, c, d, i = _plumbTo4(x, z, a, c, eps=eps)
1450
- else: # x == y == 0
1451
- if z < 0:
1452
- c = -c
1453
- a, b, d = x, y, fabs(z - c)
1454
-
1455
- else: # z == 0
1456
- u = _over0(a * x, T._a2c2) # (a + c) * (a - c)
1457
- v = _over0(b * y, T._b2c2) # (b + c) * (b - c)
1458
- s = _hypot2_1(u, v)
1459
- if u and v and s < 0:
1460
- a *= u
1461
- b *= v
1462
- c *= sqrt(-s)
1463
- d = hypot_(x - a, y - b, c)
1464
- else:
1465
- c = z # signed-0
1466
- a, b, d, i = _plumbTo4(x, y, a, b, eps=eps)
1467
-
1468
- if val > 0:
1469
- _validate(a, b, c, d, T, x, y, z, val)
1470
- return a, b, c, d, i
1471
-
1472
-
1473
- def _rootNd(r, s, u, v, w, g, eps=EPS0):
1474
- '''(INTERNAL) Robust 2-D or 3-D root finder: 2-D if C{s == v == 0} else 3-D root.
1475
-
1476
- @see: I{Eberly}'s U{Robust Root Finders ... and Listing 4<https://
1477
- www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1478
- '''
1479
- u *= r
1480
- v *= s # 0 for 2-D root
1481
- t0 = w - _1_0
1482
- t1 = _0_0 if g < 0 else (hypot_(u, w, v) - _1_0)
1483
- # assert t0 <= t1
1484
- for i in range(1, _TRIPS): # 48..58
1485
- t = (t1 + t0) * _0_5
1486
- e = t1 - t0
1487
- if eps > e > -eps or _isin(t, t0, t1):
1488
- break
1489
- g = fsumf_(_N_1_0, # ~= _hypot2_1
1490
- _over02(u, t + r),
1491
- _over02(w, t + _1_0), (
1492
- _over02(v, t + s) if v else _0_0))
1493
- if g > 0:
1494
- t0 = t
1495
- elif g < 0:
1496
- t1 = t
1497
- else:
1498
- break
1499
- else: # PYCHOK no cover
1500
- t = Fmt.no_convergence(e, eps)
1501
- raise _ValueError(t, txt__=_rootNd)
1502
- return t, i
1503
-
1504
-
1505
- def _sqrt0(x):
1506
- '''(INTERNAL) C{sqrt0} with C{TriaxialError}.
1507
- '''
1508
- return sqrt0(x, Error=TriaxialError)
1509
-
1510
-
1511
- def _validate(a, b, c, d, T, x, y, z, val):
1512
- '''(INTERNAL) Validate an C{_plumTo5} result.
1513
- '''
1514
- e = T.sideOf(a, b, c, eps=val)
1515
- if e: # not near the ellipsoid's surface
1516
- raise _ValueError(a=a, b=b, c=c, d=d,
1517
- sideOf=e, eps=val)
1518
- if d: # angle of delta and normal vector
1519
- m = Vector3d(x, y, z).minus_(a, b, c)
1520
- if m.euclid > val:
1521
- m = m.unit()
1522
- n = T.normal3d(a, b, c)
1523
- e = n.dot(m) # n.negate().dot(m)
1524
- if not isnear1(fabs(e), eps1=val):
1525
- raise _ValueError(n=n, m=m,
1526
- dot=e, eps=val)
1527
-
1528
-
1529
- if __name__ == _DMAIN_:
1530
-
1531
- from pygeodesy import printf
1532
- from pygeodesy.interns import _COMMA_, _NL_, _NLATvar_
1533
-
1534
- t = Triaxial_(6378388.0, 6378318.0, 6356911.9461)
1535
- t = t.height4(3909863.9271, 3909778.123, 3170932.5016)
1536
- printf('# Bektas: %r', t)
1537
-
1538
- # __doc__ of this file, force all into registery
1539
- t = [NN] + Triaxials.toRepr(all=True, asorted=True).split(_NL_)
1540
- printf(_NLATvar_.join(i.strip(_COMMA_) for i in t))
1541
-
1542
- # % python3 -m pygeodesy.triaxials
1543
- #
1544
- # Bektas: height4(x=3909251.554667, y=3909165.750567, z=3170432.501602, h=999.999996)
1545
-
1546
- # **) MIT License
1547
- #
1548
- # Copyright (C) 2022-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1549
- #
1550
- # Permission is hereby granted, free of charge, to any person obtaining a
1551
- # copy of this software and associated documentation files (the "Software"),
1552
- # to deal in the Software without restriction, including without limitation
1553
- # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1554
- # and/or sell copies of the Software, and to permit persons to whom the
1555
- # Software is furnished to do so, subject to the following conditions:
1556
- #
1557
- # The above copyright notice and this permission notice shall be included
1558
- # in all copies or substantial portions of the Software.
1559
- #
1560
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1561
- # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1562
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1563
- # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1564
- # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1565
- # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1566
- # OTHER DEALINGS IN THE SOFTWARE.