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