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