pygeodesy 25.11.5__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 (125) 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 +1 -1
  19. pygeodesy/cartesianBase.py +7 -7
  20. pygeodesy/clipy.py +1 -1
  21. pygeodesy/constants.py +27 -24
  22. pygeodesy/css.py +1 -1
  23. pygeodesy/datums.py +1 -1
  24. pygeodesy/deprecated/__init__.py +1 -1
  25. pygeodesy/deprecated/bases.py +1 -1
  26. pygeodesy/deprecated/classes.py +14 -7
  27. pygeodesy/deprecated/consterns.py +1 -1
  28. pygeodesy/deprecated/datum.py +1 -1
  29. pygeodesy/deprecated/functions.py +1 -1
  30. pygeodesy/deprecated/nvector.py +1 -1
  31. pygeodesy/deprecated/rhumbBase.py +1 -1
  32. pygeodesy/deprecated/rhumbaux.py +1 -1
  33. pygeodesy/deprecated/rhumbsolve.py +1 -1
  34. pygeodesy/deprecated/rhumbx.py +1 -1
  35. pygeodesy/dms.py +1 -1
  36. pygeodesy/ecef.py +1 -1
  37. pygeodesy/ecefLocals.py +1 -1
  38. pygeodesy/elevations.py +1 -1
  39. pygeodesy/ellipsoidalBase.py +1 -1
  40. pygeodesy/ellipsoidalBaseDI.py +1 -1
  41. pygeodesy/ellipsoidalExact.py +1 -1
  42. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  43. pygeodesy/ellipsoidalKarney.py +1 -1
  44. pygeodesy/ellipsoidalNvector.py +1 -1
  45. pygeodesy/ellipsoidalVincenty.py +1 -1
  46. pygeodesy/ellipsoids.py +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 +1 -1
  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 +23 -12
  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.py → triaxials/triaxial5.py} +353 -781
  109. pygeodesy/units.py +1 -1
  110. pygeodesy/unitsBase.py +1 -1
  111. pygeodesy/ups.py +2 -3
  112. pygeodesy/utily.py +17 -14
  113. pygeodesy/utm.py +1 -1
  114. pygeodesy/utmups.py +1 -1
  115. pygeodesy/utmupsBase.py +1 -1
  116. pygeodesy/vector2d.py +1 -1
  117. pygeodesy/vector3d.py +1 -1
  118. pygeodesy/vector3dBase.py +1 -1
  119. pygeodesy/webmercator.py +1 -1
  120. pygeodesy/wgrs.py +1 -1
  121. {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/METADATA +12 -12
  122. pygeodesy-25.12.12.dist-info/RECORD +125 -0
  123. pygeodesy-25.11.5.dist-info/RECORD +0 -119
  124. {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/WHEEL +0 -0
  125. {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,19 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- u'''Triaxal ellipsoid classes I{ordered} L{Triaxial} and I{unordered} L{Triaxial_} and Jacobi
5
- conformal projections L{ConformalTriaxial} and L{ConformalSphere}, transcoded from
6
- I{Charles Karney}'s GeographicLib 2.5.2 C++ class U{JacobiConformal
7
- <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1JacobiConformal.html#details>}
8
- to pure Python and miscellaneous classes L{BetaOmega2Tuple}, L{BetaOmega3Tuple}, L{Conformal2Tuple}
9
- and L{TriaxialError}.
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}.
10
9
 
11
- Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024). For more information,
12
- see the U{GeographicLib<https://GeographicLib.SourceForge.io>} 2.5.2 documentation.
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
13
 
14
14
  @see: U{Geodesics on a triaxial ellipsoid<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
15
15
  Geodesics_on_a_triaxial_ellipsoid>} and U{Triaxial coordinate systems and their geometrical
16
- interpretation<https://www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
16
+ interpretation<https://OLD.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
17
17
 
18
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
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)
@@ -26,46 +26,48 @@ see the U{GeographicLib<https://GeographicLib.SourceForge.io>} 2.5.2 documentati
26
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
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
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)
29
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)
30
32
  '''
31
33
  # make sure int/int division yields float quotient, see .basics
32
34
  from __future__ import division as _; del _ # noqa: E702 ;
33
35
 
34
- from pygeodesy.basics import _isin, isLatLon, isscalar
35
- from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INT0, PI2, PI_3, PI4, \
36
- _EPS2e4, float0_, _isfinite, isnear1, _over, _SQRT2_2, \
37
- _0_0, _0_5, _1_0, _N_1_0, _64_0, _4_0 # PYCHOK used!
38
- from pygeodesy.datums import Datum, _spherical_datum, _WGS84, Ellipsoid, _EWGS84, Fmt
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
39
42
  # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
40
43
  # from pygeodesy.elliptic import Elliptic # _MODS
41
- from pygeodesy.errors import _AssertionError, _ValueError
42
- from pygeodesy.fmath import Fdot, fdot, fmean_, hypot, hypot_, norm2, sqrt0
43
- from pygeodesy.fsums import _Fsumf_, fsumf_, fsum1f_
44
- from pygeodesy.interns import NN, _a_, _b_, _beta_, _c_, _distant_, _DMAIN_, \
45
- _finite_, _height_, _inside_, _near_, _negative_, \
46
- _not_, _NOTEQUAL_, _null_, _opposite_, _outside_, \
47
- _SPACE_, _spherical_, _too_, _x_, _y_
48
- # from pygeodesy.karney import _norm2 # _MODS
49
- # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .vector3d
50
- from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name__, _NamedEnum, \
51
- _NamedEnumItem, _Pass
52
- from pygeodesy.namedTuples import LatLon3Tuple, _NamedTupleTo, Vector3Tuple, \
53
- Vector4Tuple
54
- from pygeodesy.props import Property_RO, property_ROver
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
55
  # from pygeodesy.streprs import Fmt # from .datums
56
- from pygeodesy.units import Degrees, Float, Height_, Meter, Meter2, Meter3, \
57
- Radians, Radius_, Scalar_
58
- from pygeodesy.utily import asin1, atan2, atan2d, km2m, m2km, SinCos2, sincos2d_
59
- from pygeodesy.vector3d import _otherV3d, Vector3d, _ALL_LAZY, _MODS
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
60
63
 
61
- from math import fabs, sqrt
64
+ # from math import fabs, sqrt # from .fmath
62
65
 
63
- __all__ = _ALL_LAZY.triaxials
64
- __version__ = '25.10.30'
66
+ __all__ = _ALL_LAZY.triaxials_triaxial5
67
+ __version__ = '25.11.29'
65
68
 
66
- _not_ordered_ = _not_('ordered')
67
- _omega_ = 'omega'
68
- _TRIPS = 359 # Eberly 1074?
69
+ _omega_ = 'omega'
70
+ _TRIPS = 359 # Eberly 1074?
69
71
 
70
72
 
71
73
  class _NamedTupleToX(_NamedTupleTo): # in .testNamedTuples
@@ -188,9 +190,27 @@ class Conformal2Tuple(_NamedTupleToX):
188
190
  '''
189
191
  return self._toRadians(name)
190
192
 
193
+ def to5Tuple(self, b_conformal, **z_scale_name):
194
+ '''Return this L{Conformal2Tuple} as a L{Conformal5Tuple}.
191
195
 
192
- class Triaxial_(_NamedEnumItem):
193
- '''I{Unordered} triaxial ellipsoid and base class.
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.
194
214
 
195
215
  Triaxial ellipsoids with right-handed semi-axes C{a}, C{b} and C{c}, oriented
196
216
  such that the large principal ellipse C{ab} is the equator I{Z}=0, I{beta}=0,
@@ -205,568 +225,35 @@ class Triaxial_(_NamedEnumItem):
205
225
  longitude C{beta} and C{omega} are in L{Radians} by default (or in
206
226
  L{Degrees} if converted).
207
227
  '''
208
- _ijk = _kji = None
209
- _unordered = True
210
-
211
- def __init__(self, a_triaxial, b=None, c=None, **name):
212
- '''New I{unordered} L{Triaxial_}.
213
-
214
- @arg a_triaxial: Large, C{X} semi-axis (C{scalar}, conventionally in
215
- C{meter}) or an other L{Triaxial} or L{Triaxial_} instance.
216
- @kwarg b: Middle, C{Y} semi-axis (C{meter}, same units as B{C{a}}), required
217
- if C{B{a_triaxial} is scalar}, ignored otherwise.
218
- @kwarg c: Small, C{Z} semi-axis (C{meter}, like B{C{b}}).
219
- @kwarg name: Optional C{B{name}=NN} (C{str}).
220
-
221
- @raise TriaxialError: Invalid semi-axis or -axes.
222
- '''
223
- try:
224
- try:
225
- a = a_triaxial
226
- t = a._abc3
227
- except AttributeError:
228
- t = Radius_(a=a), Radius_(b=b), Radius_(c=c)
229
- except (TypeError, ValueError) as x:
230
- raise TriaxialError(a=a, b=b, c=c, cause=x)
231
- if name:
232
- self.name = name
233
-
234
- a, b, c = self._abc3 = t
235
- if self._unordered: # == not isinstance(self, Triaxial)
236
- s, _, t = sorted(t)
237
- if not (_isfinite(t) and s > 0):
238
- raise TriaxialError(a=a, b=b, c=c) # txt=_invalid_
239
- elif not (_isfinite(a) and a >= b >= c > 0): # see Triaxial2
240
- raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
241
- elif not (a > c and self._a2c2 > 0 and self.e2ac > 0):
242
- raise TriaxialError(a=a, c=c, e2ac=self.e2ac, txt=_spherical_)
243
-
244
- def __str__(self):
245
- return self.toStr()
246
-
247
- @Property_RO
248
- def a(self):
249
- '''Get the C{largest, x} semi-axis (C{meter}, conventionally).
250
- '''
251
- a, _, _ = self._abc3
252
- return a
253
-
254
- @Property_RO
255
- def a2(self):
256
- '''Get C{a**2}.
257
- '''
258
- return self.a**2
259
-
260
- @Property_RO
261
- def _a2b2(self):
262
- '''(INTERNAL) Get C{a**2 - b**2} == E_sub_e**2.
263
- '''
264
- a, b, _ = self._abc3
265
- return ((a - b) * (a + b)) if a != b else _0_0
266
-
267
- @Property_RO
268
- def _a2_b2(self):
269
- '''(INTERNAL) Get C{(a / b)**2}.
270
- '''
271
- a, b, _ = self._abc3
272
- return (a / b)**2 if a != b else _1_0
273
-
274
- @Property_RO
275
- def _a2b2c23(self):
276
- '''(INTERNAL) Get 3-tuple C{(a**2, b**2, c**2)}.
277
- '''
278
- a, b, c = self._abc3
279
- return self.a2, self.b2, self.c2
280
-
281
- @Property_RO
282
- def _a2c2(self):
283
- '''(INTERNAL) Get C{a**2 - c**2} == E_sub_x**2.
284
- '''
285
- a, _, c = self._abc3
286
- return ((a - c) * (a + c)) if a != c else _0_0
287
-
288
- @Property_RO
289
- def area(self):
290
- '''Get the surface area (C{meter} I{squared}).
291
- '''
292
- c, b, a = sorted(self._abc3)
293
- return Triaxial(a, b, c).area if a > c else \
294
- Meter2(area=self.a2 * PI4) # a == c == b
295
-
296
- def area_p(self, p=1.6075):
297
- '''I{Approximate} the surface area (C{meter} I{squared}).
298
-
299
- @kwarg p: Exponent (C{scalar} > 0), 1.6 for near-spherical or 1.5849625007
300
- for "near-flat" triaxials.
301
-
302
- @see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Approximate_formula>}.
303
- '''
304
- a, b, c = self._abc3
305
- if a == b == c:
306
- a *= a
307
- else:
308
- _p = pow
309
- a = _p(fmean_(_p(a * b, p), _p(a * c, p), _p(b * c, p)), _1_0 / p)
310
- return Meter2(area_p=a * PI4)
311
-
312
- @Property_RO
313
- def b(self):
314
- '''Get the C{middle, y} semi-axis (C{meter}, same units as B{C{a}}).
315
- '''
316
- _, b, _ = self._abc3
317
- return b
318
-
319
- @Property_RO
320
- def b2(self):
321
- '''Get C{b**2}.
322
- '''
323
- return self.b**2
324
-
325
- @Property_RO
326
- def _b2_a2(self):
327
- '''(INTERNAL) Get C{(b / a)**2}.
328
- '''
329
- a, b, _ = self._abc3
330
- return (b / a)**2 if a != b else _1_0
331
-
332
- @Property_RO
333
- def _b2c2(self):
334
- '''(INTERNAL) Get C{b**2 - c**2} == E_sub_y**2.
335
- '''
336
- _, b, c = self._abc3
337
- return ((b - c) * (b + c)) if b != c else _0_0
338
-
339
- @Property_RO
340
- def c(self):
341
- '''Get the C{smallest, z} semi-axis (C{meter}, same units as B{C{a}}).
342
- '''
343
- _, _, c = self._abc3
344
- return c
345
-
346
- @Property_RO
347
- def c2(self):
348
- '''Get C{c**2}.
349
- '''
350
- return self.c**2
351
-
352
- @Property_RO
353
- def _c2_a2(self):
354
- '''(INTERNAL) Get C{(c / a)**2}.
355
- '''
356
- a, _, c = self._abc3
357
- return (c / a)**2 if a != c else _1_0
358
-
359
- @Property_RO
360
- def _c2_b2(self):
361
- '''(INTERNAL) Get C{(c / b)**2}.
362
- '''
363
- _, b, c = self._abc3
364
- return (c / b)**2 if b != c else _1_0
365
-
366
- @Property_RO
367
- def e2ab(self):
368
- '''Get the C{ab} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (b/a)**2}.
369
- '''
370
- return Float(e2ab=(_1_0 - self._b2_a2) or _0_0)
371
-
372
- # _1e2ab = _b2_a2 # C{1 - e2ab} == C{(b / a)**2}
373
-
374
- @Property_RO
375
- def e2ac(self):
376
- '''Get the C{ac} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/a)**2}.
377
- '''
378
- return Float(e2ac=(_1_0 - self._c2_a2) or _0_0)
379
-
380
- # _1e2ac = _c2_a2 # C{1 - e2ac} == C{(c / a)**2}
381
-
382
- @Property_RO
383
- def e2bc(self):
384
- '''Get the C{bc} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/b)**2}.
385
- '''
386
- return Float(e2bc=(_1_0 - self._c2_b2) or _0_0)
387
-
388
- # _1e2bc = _c2_b2 # C{1 - e2bc} == C{(c / b)**2}
389
-
390
- @property_ROver
391
- def _Elliptic(self):
392
- '''(INTERNAL) Get class L{Elliptic}, I{once}.
393
- '''
394
- return _MODS.elliptic.Elliptic # overwrite property_ROver
395
-
396
- def hartzell4(self, pov, los=False, **name):
397
- '''Compute the intersection of this triaxial's surface with a Line-Of-Sight
398
- from a Point-Of-View in space.
399
-
400
- @see: Function L{hartzell4<triaxials.hartzell4>} for further details.
401
- '''
402
- return hartzell4(pov, los=los, tri_biax=self, **name)
403
-
404
- def height4(self, x_xyz, y=None, z=None, normal=True, eps=EPS, **name):
405
- '''Compute the projection on and the height above or below this triaxial's surface.
406
-
407
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
408
- L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
409
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
410
- otherwise.
411
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
412
- @kwarg normal: If C{True}, the projection is the I{perpendicular, plumb} to the
413
- triaxial's surface, otherwise the C{radial} line to the center of
414
- this triaxial (C{bool}).
415
- @kwarg eps: Tolerance for root finding and validation (C{scalar}), use a negative
416
- value to skip validation.
417
- @kwarg name: Optional C{B{name}="height4"} (C{str}).
418
-
419
- @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x}, C{y}
420
- and C{z} of the projection on or the intersection with and with the height
421
- C{h} above or below the triaxial's surface in C{meter}, conventionally.
422
-
423
- @raise TriaxialError: Non-cartesian B{C{xyz}}, invalid B{C{eps}}, no convergence in
424
- root finding or validation failed.
425
-
426
- @see: Methods L{Triaxial.normal3d} and L{Ellipsoid.height4}, I{Eberly}'s U{Distance from a Point to ...
427
- <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>} and I{Bektas}'
428
- U{Shortest Distance from a Point to Triaxial Ellipsoid<https://www.ResearchGate.net/publication/
429
- 272149005_SHORTEST_DISTANCE_FROM_A_POINT_TO_TRIAXIAL_ELLIPSOID>}.
430
- '''
431
- v, r = _otherV3d_(x_xyz, y, z), self.isSpherical
432
-
433
- i, h = None, v.length
434
- if h < EPS0: # EPS
435
- x = y = z = _0_0
436
- h -= min(self._abc3) # nearest
437
- elif r: # .isSpherical
438
- x, y, z = v.times(r / h).xyz3
439
- h -= r
440
- else:
441
- x, y, z = v.xyz3
442
- try:
443
- if normal: # plumb to surface
444
- x, y, z, h, i = _plumbTo5(x, y, z, self, eps=eps)
445
- else: # radial to center
446
- x, y, z = self._radialTo3(z, hypot(x, y), y, x)
447
- h = v.minus_(x, y, z).length
448
- except Exception as e:
449
- raise TriaxialError(x=x, y=y, z=z, cause=e)
450
- if h > 0 and self.sideOf(v, eps=EPS0) < 0:
451
- h = -h # inside
452
- n = _name__(name, name__=self.height4) # typename
453
- return Vector4Tuple(x, y, z, h, iteration=i, name=n)
454
-
455
- @Property_RO
456
- def isOrdered(self):
457
- '''Is this triaxial I{ordered} and I{not spherical} (C{bool})?
458
- '''
459
- a, b, c = self._abc3
460
- return bool(a >= b > c) # b > c!
461
-
462
- @Property_RO
463
- def isSpherical(self):
464
- '''Is this triaxial I{spherical} (C{Radius} or INT0)?
465
- '''
466
- a, b, c = self._abc3
467
- return a if a == b == c else INT0
468
-
469
- def _norm2(self, s, c, *a):
470
- '''(INTERNAL) Normalize C{s} and C{c} iff not already.
471
- '''
472
- if fabs(_hypot2_1(s, c)) > EPS02:
473
- s, c = norm2(s, c)
474
- if a:
475
- s, c = norm2(s * self.b, c * a[0])
476
- return float0_(s, c)
477
-
478
- def normal3d(self, x_xyz, y=None, z=None, length=_1_0):
479
- '''Get a 3-D vector I{on and perpendicular to} this triaxial's surface.
480
-
481
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
482
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
483
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
484
- otherwise.
485
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
486
- @kwarg length: Optional, signed length in out-/inward direction (C{scalar}).
487
-
488
- @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing out-
489
- or inward for postive respectively negative B{C{length}}.
490
-
491
- @raise TriaxialError: Zero length cartesian or vector.
492
-
493
- @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
494
- use method L{Triaxial.sideOf} to validate.
495
-
496
- @see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.
497
- '''
498
- # n = 2 * (x / a2, y / b2, z / c2)
499
- # == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered
500
- # == 2 * (x, y / _b2_a2, z / _c2_a2) / a2
501
- # == unit(x, y / _b2_a2, z / _c2_a2).times(length)
502
- x, y, z = _otherV3d_(x_xyz, y, z).xyz3
503
- n = Vector3d(x, y / self._b2_a2,
504
- z / self._c2_a2, name__=self.normal3d)
505
- u = n.length
506
- if u < EPS0:
507
- raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
508
- return n.times(length / u)
509
-
510
- def normal4(self, x_xyz, y=None, z=None, height=0, normal=True):
511
- '''Compute a cartesian above or below this triaxial's surface.
512
-
513
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},
514
- L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
515
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
516
- otherwise.
517
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
518
- @kwarg height: The signed height (C{scalar}, same units as the triaxial axes).
519
- @kwarg normal: If C{True}, the B{C{height}} is I{perpendicular, plumb} to the
520
- triaxial's surface, otherwise C{radially} to the center of this
521
- triaxial (C{bool}).
522
- @kwarg name: Optional C{B{name}="normal4"} (C{str}).
523
-
524
- @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x},
525
- C{y} and C{z} and C{h} the I{signed, normal distance} to the triaxial's
526
- surface in C{meter}, conventionally. Positive C{h} indicates, the
527
- cartesian is outside the triaxial, negative C{h} means inside.
528
-
529
- @raise TriaxialError: Zero length cartesian or vector.
530
-
531
- @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,
532
- use method L{Triaxial.sideOf} to validate.
533
-
534
- @see: Methods L{Triaxial.normal3d} and L{Triaxial.height4}.
535
- '''
536
- v, h = _otherV3d(x_xyz, y, z), Height_(height, low=None)
537
- if h:
538
- if v.length < EPS0:
539
- raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
540
- if normal:
541
- n = self.normal3d(v, length=h)
542
- h = n.length
543
- n += v
544
- else:
545
- h = h / v.length
546
- n = v.times(h + _1_0)
547
- else:
548
- n = v
549
- return Vector4Tuple(n.x, n.y, n.z, h, name__=self.normal4)
228
+ if _FOR_DOCS:
229
+ __init__ = _UnOrderedTriaxialBase.__init__
550
230
 
551
- def _order3(self, *abc, **reverse): # reverse=False
552
- '''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.
553
231
 
554
- @return: 3-Tuple C{(a, b, c)} ordered by or un-ordered
555
- (reverse-ordered) C{ijk} if C{B{reverse}=True}.
556
- '''
557
- ijk = self._order_ijk(**reverse)
558
- return _getitems(abc, *ijk) if ijk else abc
559
-
560
- def _order3d(self, v, **reverse): # reverse=False
561
- '''(INTERNAL) Un-/Order a C{Vector3d}.
562
-
563
- @return: Vector3d(x, y, z) un-/ordered.
564
- '''
565
- ijk = self._order_ijk(**reverse)
566
- return v.classof(*_getitems(v.xyz3, *ijk)) if ijk else v
567
-
568
- @Property_RO
569
- def _ordered4(self):
570
- '''(INTERNAL) Helper for C{_hartzell3} and C{_plumbTo5}.
571
- '''
572
- def _order2(reverse, a, b, c):
573
- '''(INTERNAL) Un-Order C{a}, C{b} and C{c}.
574
-
575
- @return: 2-Tuple C{((a, b, c), ijk)} with C{a} >= C{b} >= C{c}
576
- and C{ijk} a 3-tuple with the initial indices.
577
- '''
578
- i, j, k = range(3)
579
- if a < b:
580
- a, b, i, j = b, a, j, i
581
- if a < c:
582
- a, c, i, k = c, a, k, i
583
- if b < c:
584
- b, c, j, k = c, b, k, j
585
- # reverse (k, j, i) since (a, b, c) is reversed-sorted
586
- ijk = (k, j, i) if reverse else (None if i < j < k else (i, j, k))
587
- return (a, b, c), ijk
588
-
589
- abc, T = self._abc3, self
590
- if not self.isOrdered:
591
- abc, ijk = _order2(False, *abc)
592
- if ijk:
593
- _, kji = _order2(True, *ijk)
594
- T = Triaxial_(*abc)
595
- T._ijk, T._kji = ijk, kji
596
- return abc + (T,)
597
-
598
- def _order_ijk(self, reverse=False):
599
- '''(INTERNAL) Get the un-/order indices.
600
- '''
601
- return self._kji if reverse else self._ijk
602
-
603
- def _radialTo3(self, sbeta, cbeta, somega, comega):
604
- '''(INTERNAL) I{Unordered} helper for C{.height4}.
605
- '''
606
- def _rphi(a, b, sphi, cphi):
607
- # <https://WikiPedia.org/wiki/Ellipse#Polar_form_relative_to_focus>
608
- # polar form: radius(phi) = a * b / hypot(a * sphi, b * cphi)
609
- return (b / hypot(sphi, b / a * cphi)) if a > b else (
610
- (a / hypot(cphi, a / b * sphi)) if a < b else a)
611
-
612
- sa, ca = self._norm2(sbeta, cbeta)
613
- sb, cb = self._norm2(somega, comega)
614
-
615
- a, b, c = self._abc3
616
- if a != b:
617
- a = _rphi(a, b, sb, cb)
618
- if a != c:
619
- c = _rphi(a, c, sa, ca)
620
- t = c * ca
621
- return (t * cb), (t * sb), (c * sa)
622
-
623
- def sideOf(self, x_xyz, y=None, z=None, eps=EPS4):
624
- '''Is a cartesian on, above or below the surface of this triaxial?
625
-
626
- @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
627
- L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
628
- @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
629
- ignored otherwise.
630
- @kwarg z: Z component (C{scalar}), like B{C{y}}.
631
- @kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).
632
-
633
- @return: C{INT0} if C{(B{x}, B{y}, B{z})} is near this triaxial's surface
634
- within tolerance B{C{eps}}, otherwise the signed, radial distance
635
- I{squared} (C{float}), negative for in- or positive for outside
636
- this triaxial.
637
-
638
- @see: Methods L{Triaxial.height4} and L{Triaxial.normal3d}.
639
- '''
640
- v = _otherV3d_(x_xyz, y, z)
641
- s = fsumf_(_N_1_0, *map(_over02, v.xyz3, self._abc3))
642
- return INT0 if fabs(s) < eps else s
643
-
644
- def toEllipsoid(self, **name):
645
- '''Convert this triaxial to a I{biaxial} L{Ellipsoid}, provided 2 axes match.
646
-
647
- @kwarg name: Optional C{B{name}=NN} (C{str}).
648
-
649
- @return: An L{Ellipsoid} with north along this C{Z} axis if C{a == b},
650
- this C{Y} axis if C{a == c} or this C{X} axis if C{b == c}.
651
-
652
- @raise TriaxialError: This C{a != b}, C{b != c} and C{c != a}.
653
-
654
- @see: Method L{Ellipsoid.toTriaxial}.
655
- '''
656
- a, b, c = self._abc3
657
- if a == b:
658
- b = c # N = c-Z
659
- elif b == c: # N = a-X
660
- a, b = b, a
661
- elif a != c: # N = b-Y
662
- t = _SPACE_(_a_, _NOTEQUAL_, _b_, _NOTEQUAL_, _c_)
663
- raise TriaxialError(a=a, b=b, c=c, txt=t)
664
- return Ellipsoid(a, b=b, name=self._name__(name))
665
-
666
- toBiaxial = toEllipsoid
667
-
668
- def toStr(self, prec=9, **name): # PYCHOK signature
669
- '''Return this C{Triaxial} as a string.
670
-
671
- @kwarg prec: Precision, number of decimal digits (0..9).
672
- @kwarg name: Optional name (C{str}), to override or C{None}
673
- to exclude this triaxial's name.
674
-
675
- @return: This C{Triaxial}'s attributes (C{str}).
676
- '''
677
- T = Triaxial_
678
- t = T.a, # props
679
- J = ConformalSphere
680
- t += (J.ab, J.bc) if isinstance(self, J) else (T.b, T.c)
681
- t += T.e2ab, T.e2bc, T.e2ac
682
- J = ConformalTriaxial
683
- if isinstance(self, J):
684
- t += J.xyQ2,
685
- t += T.volume, T.area
686
- return self._instr(area_p=self.area_p(), prec=prec, props=t, **name)
687
-
688
- @Property_RO
689
- def unOrdered(self):
690
- '''Is this triaxial I{un-ordered} and I{not spherical} (C{bool})?
691
- '''
692
- return not (self.isOrdered or bool(self.isSpherical))
693
-
694
- @Property_RO
695
- def volume(self):
696
- '''Get the volume (C{meter**3}), M{4 / 3 * PI * a * b * c}.
697
- '''
698
- a, b, c = self._abc3
699
- return Meter3(volume=a * b * c * PI_3 * _4_0)
700
-
701
-
702
- class Triaxial(Triaxial_):
232
+ class Triaxial(_OrderedTriaxialBase):
703
233
  '''I{Ordered} triaxial ellipsoid.
704
234
 
705
235
  @see: L{Triaxial_} for more information.
706
236
  '''
707
- _unordered = False
708
-
709
- def __init__(self, a_triaxial, b=None, c=None, **name):
710
- '''New I{ordered} L{Triaxial}.
711
-
712
- @arg a_triaxial: Largest semi-axis (C{scalar}, conventionally in C{meter})
713
- or an other L{Triaxial} or L{Triaxial_} instance.
714
- @kwarg b: Middle semi-axis (C{meter}, same units as B{C{a}}), required
715
- if C{B{a_triaxial} is scalar}, ignored otherwise.
716
- @kwarg c: Smallest semi-axis (C{meter}, like B{C{b}}).
717
- @kwarg name: Optional C{B{name}=NN} (C{str}).
718
-
719
- @note: The semi-axes must be ordered as C{B{a} >= B{b} >= B{c} > 0} and
720
- must be ellipsoidal, C{B{a} > B{c}}.
721
-
722
- @raise TriaxialError: Semi-axes unordered, spherical or invalid.
723
- '''
724
- Triaxial_.__init__(self, a_triaxial, b=b, c=c, **name)
725
-
726
- @Property_RO
727
- def _a2b2_a2c2(self):
728
- '''@see: Methods C{.forwardBetaOmega} and property C{._k2E_kp2E}.
729
- '''
730
- s = self._a2c2
731
- if s:
732
- s = self._a2b2 / s
733
- return s or _0_0
734
-
735
- @Property_RO
736
- def area(self):
737
- '''Get the surface area (C{meter} I{squared}).
237
+ if _FOR_DOCS:
238
+ __init__ = _OrderedTriaxialBase.__init__
738
239
 
739
- @see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Surface_area>}.
740
- '''
741
- a, b, c = self._abc3
742
- if a != b:
743
- kp2, k2 = self._k2E_kp2E # swapped!
744
- aE = self._Elliptic(k2, _0_0, kp2, _1_0)
745
- c2 = self._c2_a2 # cos(phi)**2 = (c/a)**2
746
- s = sqrt(self.e2ac) # sin(phi)**2 = 1 - c2
747
- r = asin1(s) # phi = atan2(sqrt(c2), s)
748
- b *= fsum1f_(aE.fE(r) * s, (c / a) * (c / b),
749
- aE.fF(r) * c2 / s)
750
- a = Meter2(area=a * b * PI2)
751
- else: # a == b > c
752
- a = Ellipsoid(a, b=c).areax
753
- return a
754
-
755
- def forwardBetaOmega(self, beta, omega, height=0, **name):
240
+ def forwardBetaOmega(self, beta, omega, height=0, **unit_name):
756
241
  '''Convert I{ellipsoidal} lat- C{beta}, longitude C{omega} and C{height}
757
242
  to cartesian.
758
243
 
759
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
760
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
761
- @arg height: Height above or below the ellipsoid's surface (C{meter},
762
- same units as this triaxial's C{a}, semi-axes).
763
- @kwarg name: Optional C{B{name}=NN} (C{str}).
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}).
764
250
 
765
251
  @return: A L{Vector3Tuple}C{(x, y, z)}.
766
252
 
767
- @see: Method L{Triaxial.reverseBetaOmega} and U{expressions (23-25)<https://
768
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
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>}.
769
255
  '''
256
+ unit, name = _xkwds_pop2(unit_name, unit=Radians)
770
257
  if height:
771
258
  z = self._Height(height) + self.c
772
259
  if z > 0:
@@ -778,8 +265,8 @@ class Triaxial(Triaxial_):
778
265
  else:
779
266
  x, y, z = self._abc3
780
267
  if z: # and x and y:
781
- sa, ca = SinCos2(beta)
782
- sb, cb = SinCos2(omega)
268
+ sa, ca = _SinCos2(beta, unit)
269
+ sb, cb = _SinCos2(omega, unit)
783
270
 
784
271
  r = self._a2b2_a2c2
785
272
  x *= cb * (_sqrt0(ca**2 + sa**2 * r) if r else fabs(ca))
@@ -788,8 +275,8 @@ class Triaxial(Triaxial_):
788
275
  return Vector3Tuple(x, y, z, **name)
789
276
 
790
277
  def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
791
- '''Convert I{ellipsoidal} lat- C{beta} and longitude C{omega}
792
- to cartesian coordinates I{on the triaxial's surface}.
278
+ '''Convert I{ellipsoidal} lat- and longitude C{beta} and C{omega}
279
+ to cartesian coordinates I{on this triaxial's surface}.
793
280
 
794
281
  @arg sbeta: Ellipsoidal latitude C{sin(beta)} (C{scalar}).
795
282
  @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
@@ -803,44 +290,50 @@ class Triaxial(Triaxial_):
803
290
 
804
291
  @see: Method L{Triaxial.reverseBetaOmega}, U{Triaxial ellipsoid coordinate
805
292
  system<https://WikiPedia.org/wiki/Geodesics_on_an_ellipsoid#
806
- Triaxial_ellipsoid_coordinate_system>} and U{expressions (23-25)<https://
807
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
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>}.
808
295
  '''
809
296
  t = self._radialTo3(sbeta, cbeta, somega, comega)
810
297
  return Vector3Tuple(*t, **name)
811
298
 
812
- def forwardCartesian(self, x_xyz, y=None, z=None, **normal_eps_name):
813
- '''Project a cartesian on this triaxial.
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}.
814
301
 
815
302
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
816
303
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
817
304
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
818
305
  ignored otherwise.
819
306
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
820
- @kwarg normal_eps_name: Optional keyword arguments C{B{normal}=True},
821
- C{B{eps}=EPS} and overriding C{B{name}="height4"} (C{str}),
822
- see method L{Triaxial.height4}.
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}).
823
311
 
824
- @see: Method L{Triaxial.height4} for further information and method
825
- L{Triaxial.reverseCartesian} to reverse the projection.
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.
826
316
  '''
827
- return self.height4(x_xyz, y, z, **normal_eps_name)
317
+ return self.height4(x_xyz, y, z, normal=normal, **eps_name)
828
318
 
829
- def forwardLatLon(self, lat, lon, height=0, **name):
319
+ def forwardLatLon(self, lat, lon, height=0, **unit_name):
830
320
  '''Convert I{geodetic} lat-, longitude and height to cartesian.
831
321
 
832
- @arg lat: Geodetic latitude (C{degrees}).
833
- @arg lon: Geodetic longitude (C{degrees}).
322
+ @arg lat: Geodetic latitude (C{Ang} or B{C{unit}}).
323
+ @arg lon: Geodetic longitude (C{Ang} or B{C{unit}}).
834
324
  @arg height: Height above the ellipsoid (C{meter}, same units
835
325
  as this triaxial's semi-axes).
836
- @kwarg name: Optional C{B{name}=NN} (C{str}).
326
+ @kwarg unit_name: Optional scalar C{B{unit}=}L{Degrees} and
327
+ C{B{name}=NN} (C{str}).
837
328
 
838
329
  @return: A L{Vector3Tuple}C{(x, y, z)}.
839
330
 
840
- @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
841
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
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>}.
842
333
  '''
843
- return self._forwardLatLon3(height, name, *sincos2d_(lat, lon))
334
+ unit, name = _xkwds_pop2(unit_name, unit=Degrees)
335
+ return self._forwardLatLon3(height, name, *(_SinCos2(lat, unit) +
336
+ _SinCos2(lon, unit)))
844
337
 
845
338
  def forwardLatLon_(self, slat, clat, slon, clon, height=0, **name):
846
339
  '''Convert I{geodetic} lat-, longitude and height to cartesian.
@@ -855,8 +348,8 @@ class Triaxial(Triaxial_):
855
348
 
856
349
  @return: A L{Vector3Tuple}C{(x, y, z)}.
857
350
 
858
- @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
859
- www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
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>}.
860
353
  '''
861
354
  sa, ca = self._norm2(slat, clat)
862
355
  sb, cb = self._norm2(slon, clon)
@@ -865,14 +358,16 @@ class Triaxial(Triaxial_):
865
358
  def _forwardLatLon3(self, height, name, sa, ca, sb, cb): # name always **name
866
359
  '''(INTERNAL) Helper for C{.forwardLatLon} and C{.forwardLatLon_}.
867
360
  '''
868
- ca_x_sb = ca * sb
869
361
  h = self._Height(height)
362
+ x = ca * cb
363
+ y = ca * sb
364
+ z = sa
870
365
  # 1 - (1 - (c/a)**2) * sa**2 - (1 - (b/a)**2) * ca**2 * sb**2
871
- t = fsumf_(_1_0, -self.e2ac * sa**2, -self.e2ab * ca_x_sb**2)
872
- n = _over(self.a, _sqrt0(t)) # prime vertical
873
- x = (h + n) * ca * cb
874
- y = (h + n * self._b2_a2) * ca_x_sb
875
- z = (h + n * self._c2_a2) * sa
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
876
371
  return Vector3Tuple(x, y, z, **name)
877
372
 
878
373
  def _Height(self, height):
@@ -880,41 +375,6 @@ class Triaxial(Triaxial_):
880
375
  '''
881
376
  return Height_(height=height, low=-self.c, Error=TriaxialError)
882
377
 
883
- @Property_RO
884
- def _k2E_kp2E(self):
885
- '''(INTERNAL) Get elliptic C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.area}.
886
- '''
887
- # k2 = a2b2 / a2c2 * c2_b2
888
- # kp2 = b2c2 / a2c2 * a2_b2
889
- # b2 = b**2
890
- # xE = Elliptic(k2, -a2b2 / b2, kp2, a2_b2)
891
- # yE = Elliptic(kp2, +b2c2 / b2, k2, c2_b2)
892
- # aE = Elliptic(kp2, 0, k2, 1)
893
- k2 = self._c2_b2 * self._a2b2_a2c2
894
- kp2 = (self._a2_b2 * self._b2c2 / self._a2c2) if k2 else _1_0
895
- return (k2, kp2)
896
-
897
- def _radialTo3(self, sbeta, cbeta, somega, comega):
898
- '''(INTERNAL) Convert I{ellipsoidal} lat- and longitude C{beta} and
899
- C{omega} to cartesian coordinates I{on the triaxial's surface},
900
- also I{ordered} helper for C{.height4}.
901
- '''
902
- sa, ca = self._norm2(sbeta, cbeta)
903
- sb, cb = self._norm2(somega, comega)
904
-
905
- b2_a2 = self._b2_a2 # == (b/a)**2
906
- c2_a2 = -self._c2_a2 # == -(c/a)**2
907
- a2c2_a2 = self. e2ac # (a**2 - c**2) / a**2 == 1 - (c/a)**2
908
-
909
- x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)
910
- z2 = _Fsumf_(c2_a2, sb**2, b2_a2 * cb**2).fover(a2c2_a2)
911
-
912
- x, y, z = self._abc3
913
- x *= cb * _sqrt0(x2)
914
- y *= ca * sb
915
- z *= sa * _sqrt0(z2)
916
- return x, y, z
917
-
918
378
  def reverseBetaOmega(self, x_xyz, y=None, z=None, **name):
919
379
  '''Convert cartesian to I{ellipsoidal} lat- and longitude, C{beta}, C{omega}
920
380
  and height.
@@ -931,41 +391,40 @@ class Triaxial(Triaxial_):
931
391
  units as this triaxial's semi-axes.
932
392
 
933
393
  @see: Methods L{Triaxial.forwardBetaOmega} and L{Triaxial.forwardBetaOmega_}
934
- and U{expressions (21-22)<https://www.Topo.Auth.GR/wp-content/uploads/
394
+ and U{equations (21-22)<https://OLD.Topo.Auth.GR/wp-content/uploads/
935
395
  sites/111/2021/12/09_Panou.pdf>}.
936
396
  '''
937
397
  v = _otherV3d_(x_xyz, y, z)
938
398
  a, b, h = _reverseLatLon3(v, atan2, v, self.forwardBetaOmega_)
939
399
  return BetaOmega3Tuple(Radians(beta=a), Radians(omega=b), h, **name)
940
400
 
941
- def reverseCartesian(self, x_xyz, y=None, z=None, h=0, normal=True, eps=_EPS2e4, **name):
942
- '''"Unproject" a cartesian I{on} this triaxial's surface to a cartesion I{off}
943
- this triaxial's surface.
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.
944
403
 
945
404
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
946
405
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
947
406
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},
948
407
  ignored otherwise.
949
408
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
950
- @arg h: Height above or below this triaxial's surface (C{meter}, same units
951
- as this triaxial's semi-axes).
952
- @kwarg normal: If C{True}, the height is C{normal} to the surface, otherwise
953
- C{radially} to the center of this triaxial (C{bool}).
954
- @kwarg eps: Tolerance for on-surface test (C{scalar}), see method L{Triaxial.sideOf}.
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}.
955
415
  @kwarg name: Optional C{B{name}=NN} (C{str}).
956
416
 
957
417
  @return: A L{Vector3Tuple}C{(x, y, z)}.
958
418
 
959
- @raise TrialError: Cartesian C{(x, y, z)} not on this triaxial's surface.
419
+ @raise TrialError: Cartesian B{C{x_xyz}} or C{(x, y, z)} not on this triaxial's
420
+ surface.
960
421
 
961
422
  @see: Methods L{Triaxial.forwardCartesian} and L{Triaxial.height4}.
962
423
  '''
424
+ h, name = _xkwds_pop2(name, h=height) # h=height for backward compatibility
963
425
  v = _otherV3d_(x_xyz, y, z, **name)
964
- s = self.sideOf(v.xyz, eps=eps)
965
- if s: # PYCHOK no cover
966
- t = _SPACE_((_inside_ if s < 0 else _outside_), self.toRepr())
967
- raise TriaxialError(eps=eps, sideOf=s, x=v.x, y=v.y, z=v.z, txt=t)
968
-
426
+ _ = self._sideOn(v, eps=eps)
427
+ h = _HeightINT0(h)
969
428
  if h:
970
429
  if normal:
971
430
  v = v.plus(self.normal3d(*v.xyz, length=h))
@@ -988,7 +447,7 @@ class Triaxial(Triaxial_):
988
447
  as this triaxial's semi-axes.
989
448
 
990
449
  @see: Methods L{Triaxial.forwardLatLon} and L{Triaxial.forwardLatLon_}
991
- and U{expressions (4-5)<https://www.Topo.Auth.GR/wp-content/uploads/
450
+ and U{equations (4-5)<https://OLD.Topo.Auth.GR/wp-content/uploads/
992
451
  sites/111/2021/12/09_Panou.pdf>}.
993
452
  '''
994
453
  v = _otherV3d_(x_xyz, y, z)
@@ -996,14 +455,40 @@ class Triaxial(Triaxial_):
996
455
  self._c2_b2, # == 1 - e_sub_y**2
997
456
  _1_0)
998
457
  a, b, h = _reverseLatLon3(s, atan2d, v, self.forwardLatLon_)
999
- return LatLon3Tuple(Degrees(lat=a), Degrees(lon=b), h, **name)
458
+ return LatLon3Tuple(Lat(a), Lon(b), h, **name)
1000
459
 
1001
460
 
1002
- class ConformalTriaxial(Triaxial):
1003
- '''This is a I{Jacobi Conformal} projection of a triaxial ellipsoid to a plane
1004
- where the C{X} and C{Y} grid lines are straight.
461
+ class TriaxialB(_Triaxial3Base):
462
+ '''I{Ordered} triaxial ellipsoid specified by its middle semi-axis and shape.
1005
463
 
1006
- Ellipsoidal coordinates I{beta} and I{omega} are converted to Jacobi Conformal
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
1007
492
  I{y} respectively I{x} separately. Jacobi's coordinates have been multiplied
1008
493
  by C{sqrt(B{a}**2 - B{c}**2) / (2 * B{b})} so that the customary results are
1009
494
  returned in the case of an ellipsoid of revolution.
@@ -1020,6 +505,34 @@ class ConformalTriaxial(Triaxial):
1020
505
  C. G. J. I{U{Vorlesungen über Dynamik<https://Books.Google.com/books?
1021
506
  id=ryEOAAAAQAAJ&pg=PA212>}}, page 212ff.
1022
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_
1023
536
 
1024
537
  @Property_RO
1025
538
  def _xE(self):
@@ -1029,14 +542,15 @@ class ConformalTriaxial(Triaxial):
1029
542
  # -a2b2 / b2 == (b2 - a2) / b2 == 1 - a2 / b2 == 1 - a2_b2
1030
543
  return self._Elliptic(k2, _1_0 - self._a2_b2, kp2, self._a2_b2)
1031
544
 
1032
- def xR(self, omega):
545
+ def xR(self, omega, unit=Radians):
1033
546
  '''Compute a I{Jacobi Conformal} C{x} projection.
1034
547
 
1035
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
548
+ @arg omega: Ellipsoidal longitude (C{Ang} or B{C{unit}}).
549
+ @kwarg unit: Unit of scalar B{C{omega}} (or C{Degrees}).
1036
550
 
1037
551
  @return: The C{x} projection (L{Radians}).
1038
552
  '''
1039
- return self.xR_(*SinCos2(omega))
553
+ return self.xR_(*_SinCos2(omega, unit))
1040
554
 
1041
555
  def xR_(self, somega, comega):
1042
556
  '''Compute a I{Jacobi Conformal} C{x} projection.
@@ -1046,28 +560,55 @@ class ConformalTriaxial(Triaxial):
1046
560
 
1047
561
  @return: The C{x} projection (L{Radians}).
1048
562
  '''
1049
- s, c = self._norm2(somega, comega, self.a)
1050
- return Radians(x=self._xE.fPi(s, c) * self._a2_b2)
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))
1051
580
 
1052
581
  @Property_RO
1053
582
  def xyQ2(self):
1054
- '''Get the I{Jacobi Conformal} quadrant size (L{Conformal2Tuple}C{(x, y)}).
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)}).
1055
594
  '''
1056
595
  return Conformal2Tuple(Radians(x=self._a2_b2 * self._xE.cPi),
1057
596
  Radians(y=self._c2_b2 * self._yE.cPi),
1058
- name=ConformalTriaxial.xyQ2.name)
597
+ name=Conformal.xyQR2.name)
1059
598
 
1060
- def xyR2(self, beta, omega, **name):
599
+ def xyR2(self, beta, omega, **unit_name):
1061
600
  '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
1062
601
 
1063
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
1064
- @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
1065
- @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2"}.
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"}.
1066
606
 
1067
- @return: A L{Conformal2Tuple}C{(x, y)}.
607
+ @return: A L{Conformal2Tuple}C{(x, y)}, both in C{Radians}.
1068
608
  '''
1069
- return self.xyR2_(*(SinCos2(beta) + SinCos2(omega)),
1070
- name=_name__(name, name__=self.xyR2))
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))
1071
612
 
1072
613
  def xyR2_(self, sbeta, cbeta, somega, comega, **name):
1073
614
  '''Compute a I{Jacobi Conformal} C{x} and C{y} projection.
@@ -1084,22 +625,41 @@ class ConformalTriaxial(Triaxial):
1084
625
  self.yR_(sbeta, cbeta),
1085
626
  name=_name__(name, name__=self.xyR2_))
1086
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
+
1087
646
  @Property_RO
1088
647
  def _yE(self):
1089
648
  '''(INTERNAL) Get the y-elliptic function.
1090
649
  '''
1091
- kp2, k2 = self._k2E_kp2E # swapped!
650
+ k2, kp2 = self._k2E_kp2E
1092
651
  # b2c2 / b2 == (b2 - c2) / b2 == 1 - c2 / b2 == e2bc
1093
- return self._Elliptic(k2, self.e2bc, kp2, self._c2_b2)
652
+ return self._Elliptic(kp2, self.e2bc, k2, self._c2_b2)
1094
653
 
1095
- def yR(self, beta):
654
+ def yR(self, beta, unit=Radians):
1096
655
  '''Compute a I{Jacobi Conformal} C{y} projection.
1097
656
 
1098
- @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
657
+ @arg beta: Ellipsoidal latitude (C{Ang} or B{C{unit}}).
658
+ @kwarg unit: Unit of scalar B{C{beta}} (or C{Degrees}).
1099
659
 
1100
660
  @return: The C{y} projection (L{Radians}).
1101
661
  '''
1102
- return self.yR_(*SinCos2(beta))
662
+ return self.yR_(*_SinCos2(beta, unit))
1103
663
 
1104
664
  def yR_(self, sbeta, cbeta):
1105
665
  '''Compute a I{Jacobi Conformal} C{y} projection.
@@ -1109,40 +669,36 @@ class ConformalTriaxial(Triaxial):
1109
669
 
1110
670
  @return: The C{y} projection (L{Radians}).
1111
671
  '''
1112
- s, c = self._norm2(sbeta, cbeta, self.c)
1113
- return Radians(y=self._yE.fPi(s, c) * self._c2_b2)
672
+ return Radians(y=self._y(sbeta, cbeta, self._c2_b2))
1114
673
 
1115
674
 
1116
- class ConformalSphere(ConformalTriaxial):
1117
- '''An alternate, I{spherical Jacobi Conformal} projection.
675
+ class ConformalSphere(Conformal):
676
+ '''Alternate, I{Jacobi Conformal projection} on a I{spherical} triaxial.
1118
677
 
1119
- @see: L{ConformalTriaxial} for other and more details.
678
+ @see: L{Conformal<triaxials.triaxial5.Conformal>} for more information.
1120
679
  '''
1121
680
  _ab = _bc = 0
1122
681
 
1123
- def __init__(self, radius_triaxial, ab=0, bc=0, **name):
682
+ def __init__(self, radius_conformal, ab=0, bc=0, **name):
1124
683
  '''New L{ConformalSphere}.
1125
684
 
1126
- @arg radius_triaxial: Radius (C{scalar}, conventionally in
1127
- C{meter}) or an other L{ConformalSphere},
1128
- L{ConformalTriaxial} or ordered L{Triaxial}.
1129
- @kwarg ab: Relative magnitude of C{B{a} - B{b}} (C{meter},
1130
- same units as C{scalar B{radius}}.
1131
- @kwarg bc: Relative magnitude of C{B{b} - B{c}} (C{meter},
1132
- same units as C{scalar B{radius}}.
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}}.
1133
691
  @kwarg name: Optional C{B{name}=NN} (C{str}).
1134
692
 
1135
- @raise TriaxialError: Invalid B{C{radius_triaxial}}, negative
1136
- B{C{ab}}, negative B{C{bc}} or C{(B{ab}
1137
- + B{bc})} not positive.
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.
1138
695
 
1139
- @note: If B{C{radius_triaxial}} is a L{ConformalSphere}
1140
- and if B{C{ab}} and B{C{bc}} are both zero or C{None},
1141
- the B{C{radius_triaxial}}'s C{ab}, C{bc}, C{a}, C{b}
1142
- and C{c} are copied.
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.
1143
699
  '''
1144
700
  try:
1145
- r = radius_triaxial
701
+ r = radius_conformal
1146
702
  if isinstance(r, Triaxial): # ordered only
1147
703
  t = r._abc3
1148
704
  j = isinstance(r, ConformalSphere) and not bool(ab or bc)
@@ -1158,7 +714,7 @@ class ConformalSphere(ConformalTriaxial):
1158
714
  and _isfinite(self._a2c2)):
1159
715
  raise ValueError(_not_(_finite_))
1160
716
  except (TypeError, ValueError) as x:
1161
- raise TriaxialError(radius_triaxial=r, ab=ab, bc=bc, cause=x)
717
+ raise TriaxialError(radius=r, ab=ab, bc=bc, cause=x)
1162
718
  if name:
1163
719
  self.name = name
1164
720
 
@@ -1201,12 +757,6 @@ class ConformalSphere(ConformalTriaxial):
1201
757
  return self.a
1202
758
 
1203
759
 
1204
- class TriaxialError(_ValueError):
1205
- '''Raised for L{Triaxial} issues.
1206
- '''
1207
- pass # ...
1208
-
1209
-
1210
760
  class Triaxials(_NamedEnum):
1211
761
  '''(INTERNAL) L{Triaxial} registry, I{must} be a sub-class
1212
762
  to accommodate the L{_LazyNamedEnumItem} properties.
@@ -1222,33 +772,26 @@ Triaxials = Triaxials(Triaxial, Triaxial_) # PYCHOK singleton
1222
772
  # <https://ArxIV.org/pdf/1909.06452.pdf> Table 1 Semi-axes in Km
1223
773
  # <https://www.JPS.NASA.gov/education/images/pdf/ss-moons.pdf>
1224
774
  # <https://link.Springer.com/article/10.1007/s00190-022-01650-9>
775
+ # <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Constants.html>
1225
776
  _abc84_35 = (_EWGS84.a + 35), (_EWGS84.a - 35), _EWGS84.b
1226
- Triaxials._assert( # a (Km) b (Km) c (Km) planet
1227
- Amalthea = _lazy('Amalthea', 125.0, 73.0, _64_0), # Jupiter
1228
- Ariel = _lazy('Ariel', 581.1, 577.9, 577.7), # Uranus
1229
- Earth = _lazy('Earth', 6378.173435, 6378.1039, 6356.7544),
1230
- Enceladus = _lazy('Enceladus', 256.6, 251.4, 248.3), # Saturn
1231
- Europa = _lazy('Europa', 1564.13, 1561.23, 1560.93), # Jupiter
1232
- Io = _lazy('Io', 1829.4, 1819.3, 1815.7), # Jupiter
1233
- Mars = _lazy('Mars', 3394.6, 3393.3, 3376.3),
1234
- Mimas = _lazy('Mimas', 207.4, 196.8, 190.6), # Saturn
1235
- Miranda = _lazy('Miranda', 240.4, 234.2, 232.9), # Uranus
1236
- Moon = _lazy('Moon', 1735.55, 1735.324, 1734.898), # Earth
1237
- Tethys = _lazy('Tethys', 535.6, 528.2, 525.8), # Saturn
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
1238
791
  WGS84_35 = _lazy('WGS84_35', *map(m2km, _abc84_35)))
1239
792
  del _abc84_35, _EWGS84
1240
793
 
1241
794
 
1242
- def _getitems(items, *indices):
1243
- '''(INTERNAL) Get the C{items} at the given I{indices}.
1244
-
1245
- @return: C{Type(items[i] for i in indices)} with
1246
- C{Type = type(items)}, any C{type} having
1247
- the special method C{__getitem__}.
1248
- '''
1249
- return type(items)(map(items.__getitem__, indices))
1250
-
1251
-
1252
795
  def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1253
796
  '''(INTERNAL) Hartzell's "Satellite Line-of-Sight Intersection ...",
1254
797
  formula from a Point-Of-View to an I{un-/ordered} Triaxial.
@@ -1314,7 +857,7 @@ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1314
857
  @kwarg los: Line-Of-Sight, I{direction} to the tri-/biaxial (L{Los}, L{Vector3d})
1315
858
  or C{True} for the I{normal, perpendicular, plumb} to the surface of
1316
859
  the tri-/biaxial or C{False} or C{None} to point to its center.
1317
- @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{ConformalTriaxial} or
860
+ @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{Conformal} or
1318
861
  L{ConformalSphere}) or biaxial ellipsoid (L{Datum},
1319
862
  L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}) or spherical earth
1320
863
  radius (C{scalar}, conventionally in C{meter}).
@@ -1335,12 +878,7 @@ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1335
878
  Line-of-Sight Intersection with Earth"<https://StephenHartzell.Medium.com/
1336
879
  satellite-line-of-sight-intersection-with-earth-d786b4a6a9b6>}.
1337
880
  '''
1338
- if isinstance(tri_biax, Triaxial_):
1339
- T = tri_biax
1340
- else:
1341
- D = tri_biax if isinstance(tri_biax, Datum) else \
1342
- _spherical_datum(tri_biax, name__=hartzell4) # typename
1343
- T = D.ellipsoid._triaxial
881
+ T = _tri_biaxial(tri_biax, hartzell4)
1344
882
  try:
1345
883
  v, h, i = _hartzell3(pov, los, T)
1346
884
  except Exception as x:
@@ -1349,11 +887,58 @@ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1349
887
  return Vector4Tuple(v.x, v.y, v.z, h, iteration=i, name=n)
1350
888
 
1351
889
 
1352
- def _hypot2_1(x, y, z=0):
1353
- '''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x), fabs(y),
1354
- fabs(z))} rarely greater than 1.0.
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>}.
1355
916
  '''
1356
- return fsumf_(_N_1_0, x*x, y*y, z*z)
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)
1357
942
 
1358
943
 
1359
944
  def _ordered(a, b, c):
@@ -1363,27 +948,8 @@ def _ordered(a, b, c):
1363
948
  raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)
1364
949
 
1365
950
 
1366
- def _otherV3d_(x_xyz, y, z, **name):
1367
- '''(INTERNAL) Get a Vector3d from C{x_xyz}, C{y} and C{z}.
1368
- '''
1369
- return Vector3d(x_xyz, y, z, **name) if isscalar(x_xyz) else \
1370
- _otherV3d(x_xyz=x_xyz, **name)
1371
-
1372
-
1373
- def _over0(p, q):
1374
- '''(INTERNAL) Return C{p / q} or C{0}.
1375
- '''
1376
- return (p / q) if q > fabs(p) else _0_0
1377
-
1378
-
1379
- def _over02(p, q):
1380
- '''(INTERNAL) Return C{(p / q)**2} or C{0}.
1381
- '''
1382
- return (p / q)**2 if p and q else _0_0
1383
-
1384
-
1385
951
  def _plumbTo3(px, py, E, eps=EPS): # in .ellipsoids.Ellipsoid.height4
1386
- '''(INTERNAL) Nearest point on a 2-D ellipse in 1st quadrant.
952
+ '''(INTERNAL) Nearest point in 1st quadrant on a 2-D ellipse.
1387
953
  '''
1388
954
  a, b = E.a, E.b
1389
955
  if min(px, py, a, b) < EPS0:
@@ -1548,8 +1114,8 @@ def _reverseLatLon3(s, atan2_, v, forward_):
1548
1114
  d = hypot( x, y)
1549
1115
  a = atan2_(z, d)
1550
1116
  b = atan2_(y, x)
1551
- h = v.minus_(*forward_(z, d, y, x)).length or INT0
1552
- return a, b, h
1117
+ h = v.minus_(*forward_(z, d, y, x)).length
1118
+ return a, b, (h or INT0)
1553
1119
 
1554
1120
 
1555
1121
  def _rootNd(r, s, u, v, w, g, eps=EPS0):
@@ -1584,10 +1150,16 @@ def _rootNd(r, s, u, v, w, g, eps=EPS0):
1584
1150
  return t, i
1585
1151
 
1586
1152
 
1587
- def _sqrt0(x):
1588
- '''(INTERNAL) C{sqrt0} with C{TriaxialError}.
1153
+ def _tri_biaxial(tri_biax, where):
1154
+ '''(INTERNAL) Get a triaxail for C{tri_biax}.
1589
1155
  '''
1590
- return sqrt0(x, Error=TriaxialError)
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
1591
1163
 
1592
1164
 
1593
1165
  def _validate(a, b, c, d, T, x, y, z, val):
@@ -1613,8 +1185,8 @@ if __name__ == _DMAIN_:
1613
1185
  from pygeodesy import printf
1614
1186
  from pygeodesy.interns import _COMMA_, _NL_, _NLATvar_
1615
1187
 
1616
- t = Triaxial_(6378388.0, 6378318.0, 6356911.9461)
1617
- t = t.height4(3909863.9271, 3909778.123, 3170932.5016)
1188
+ T = Triaxial_(6378388.0, 6378318.0, 6356911.9461)
1189
+ t = T.height4(3909863.9271, 3909778.123, 3170932.5016)
1618
1190
  printf('# Bektas: %r', t)
1619
1191
 
1620
1192
  # __doc__ of this file, force all into registery
@@ -1627,7 +1199,7 @@ if __name__ == _DMAIN_:
1627
1199
 
1628
1200
  # **) MIT License
1629
1201
  #
1630
- # Copyright (C) 2022-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1202
+ # Copyright (C) 2022-2026 -- mrJean1 at Gmail -- All Rights Reserved.
1631
1203
  #
1632
1204
  # Permission is hereby granted, free of charge, to any person obtaining a
1633
1205
  # copy of this software and associated documentation files (the "Software"),