pygeodesy 24.3.24__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-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/vector2d.py
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''2- or 3-D vectorial functions L{circin6}, L{circum3}, L{circum4_},
|
|
5
|
+
L{iscolinearWith}, L{meeus2}, L{nearestOn}, L{radii11} and L{soddy4}.
|
|
6
|
+
'''
|
|
7
|
+
|
|
8
|
+
from pygeodesy.basics import len2, map2, _xnumpy
|
|
9
|
+
from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INF, INT0, \
|
|
10
|
+
_EPS4e8, isnear0, _0_0, _0_25, _0_5, _N_0_5, \
|
|
11
|
+
_1_0, _1_0_1T, _N_1_0, _2_0, _N_2_0, _4_0
|
|
12
|
+
from pygeodesy.errors import _and, _AssertionError, IntersectionError, NumPyError, \
|
|
13
|
+
PointsError, TriangleError, _xError, _xkwds
|
|
14
|
+
from pygeodesy.fmath import fabs, fdot, hypot, hypot2_, sqrt
|
|
15
|
+
from pygeodesy.fsums import Fsum, fsumf_, fsum1f_
|
|
16
|
+
from pygeodesy.interns import NN, _a_, _and_, _b_, _c_, _center_, _coincident_, \
|
|
17
|
+
_colinear_, _COMMASPACE_, _concentric_, _few_, \
|
|
18
|
+
_intersection_, _invalid_, _near_, _no_, _of_, \
|
|
19
|
+
_radius_, _rIn_, _s_, _SPACE_, _too_, _with_
|
|
20
|
+
# from pygeodesy.lazily import _ALL_LAZY # from .named
|
|
21
|
+
from pygeodesy.named import _ALL_LAZY, _NamedTuple, _Pass, Property_RO
|
|
22
|
+
from pygeodesy.namedTuples import LatLon3Tuple, Vector2Tuple
|
|
23
|
+
# from pygeodesy.props import Property_RO # from .named
|
|
24
|
+
from pygeodesy.streprs import Fmt, unstr
|
|
25
|
+
from pygeodesy.units import Float, Int, Meter, Radius, Radius_
|
|
26
|
+
from pygeodesy.vector3d import iscolinearWith, nearestOn, _nearestOn2, _nVc, _otherV3d, \
|
|
27
|
+
trilaterate2d2, trilaterate3d2, Vector3d # PYCHOK unused
|
|
28
|
+
|
|
29
|
+
from contextlib import contextmanager
|
|
30
|
+
# from math import fabs, sqrt # from .fmath
|
|
31
|
+
|
|
32
|
+
__all__ = _ALL_LAZY.vector2d
|
|
33
|
+
__version__ = '23.11.17'
|
|
34
|
+
|
|
35
|
+
_cA_ = 'cA'
|
|
36
|
+
_cB_ = 'cB'
|
|
37
|
+
_cC_ = 'cC'
|
|
38
|
+
_deltas_ = 'deltas'
|
|
39
|
+
_outer_ = 'outer'
|
|
40
|
+
_raise_ = 'raise' # PYCHOK used!
|
|
41
|
+
_rank_ = 'rank'
|
|
42
|
+
_residuals_ = 'residuals'
|
|
43
|
+
_Type_ = 'Type'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Circin6Tuple(_NamedTuple):
|
|
47
|
+
'''6-Tuple C{(radius, center, deltas, cA, cB, cC)} with the C{radius}, the
|
|
48
|
+
trilaterated C{center} and contact points of the I{inscribed} aka I{In-
|
|
49
|
+
circle} of a triangle. The C{center} is I{un}ambiguous if C{deltas} is
|
|
50
|
+
C{None}, otherwise C{center} is the mean and C{deltas} the differences of
|
|
51
|
+
the L{pygeodesy.trilaterate3d2} results. Contact points C{cA}, C{cB} and
|
|
52
|
+
C{cC} are the points of tangency, aka the corners of the U{Contact Triangle
|
|
53
|
+
<https://MathWorld.Wolfram.com/ContactTriangle.html>}.
|
|
54
|
+
'''
|
|
55
|
+
_Names_ = (_radius_, _center_, _deltas_, _cA_, _cB_, _cC_)
|
|
56
|
+
_Units_ = ( Radius, _Pass, _Pass, _Pass, _Pass, _Pass)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Circum3Tuple(_NamedTuple): # in .latlonBase
|
|
60
|
+
'''3-Tuple C{(radius, center, deltas)} with the C{circumradius} and trilaterated
|
|
61
|
+
C{circumcenter} of the C{circumcircle} through 3 points (aka {Meeus}' Type II
|
|
62
|
+
circle) or the C{radius} and C{center} of the smallest I{Meeus}' Type I circle.
|
|
63
|
+
The C{center} is I{un}ambiguous if C{deltas} is C{None}, otherwise C{center}
|
|
64
|
+
is the mean and C{deltas} the differences of the L{pygeodesy.trilaterate3d2}
|
|
65
|
+
results.
|
|
66
|
+
'''
|
|
67
|
+
_Names_ = (_radius_, _center_, _deltas_)
|
|
68
|
+
_Units_ = ( Radius, _Pass, _Pass)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Circum4Tuple(_NamedTuple):
|
|
72
|
+
'''4-Tuple C{(radius, center, rank, residuals)} with C{radius} and C{center}
|
|
73
|
+
of a sphere I{least-squares} fitted through given points and the C{rank}
|
|
74
|
+
and C{residuals} -if any- from U{numpy.linalg.lstsq
|
|
75
|
+
<https://NumPy.org/doc/stable/reference/generated/numpy.linalg.lstsq.html>}.
|
|
76
|
+
'''
|
|
77
|
+
_Names_ = (_radius_, _center_, _rank_, _residuals_)
|
|
78
|
+
_Units_ = ( Radius, _Pass, Int, _Pass)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Meeus2Tuple(_NamedTuple):
|
|
82
|
+
'''2-Tuple C{(radius, Type)} with C{radius} and I{Meeus}' C{Type} of the smallest
|
|
83
|
+
circle I{containing} 3 points. C{Type} is C{None} for a I{Meeus}' Type II
|
|
84
|
+
C{circumcircle} passing through all 3 points. Otherwise C{Type} is the center
|
|
85
|
+
of a I{Meeus}' Type I circle with 2 points on (a diameter of) and 1 point
|
|
86
|
+
inside the circle.
|
|
87
|
+
'''
|
|
88
|
+
_Names_ = (_radius_, _Type_)
|
|
89
|
+
_Units_ = ( Radius, _Pass)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Radii11Tuple(_NamedTuple):
|
|
93
|
+
'''11-Tuple C{(rA, rB, rC, cR, rIn, riS, roS, a, b, c, s)} with the C{Tangent}
|
|
94
|
+
circle radii C{rA}, C{rB} and C{rC}, the C{circumradius} C{cR}, the C{Incircle}
|
|
95
|
+
radius C{rIn} aka C{inradius}, the inner and outer I{Soddy} circle radii C{riS}
|
|
96
|
+
and C{roS}, the sides C{a}, C{b} and C{c} and semi-perimeter C{s} of a triangle,
|
|
97
|
+
all in C{meter} conventionally.
|
|
98
|
+
|
|
99
|
+
@note: C{Circumradius} C{cR} and outer I{Soddy} radius C{roS} may be C{INF}.
|
|
100
|
+
'''
|
|
101
|
+
_Names_ = ('rA', 'rB', 'rC', 'cR', _rIn_, 'riS', 'roS', _a_, _b_, _c_, _s_)
|
|
102
|
+
_Units_ = ( Meter,) * len(_Names_)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Soddy4Tuple(_NamedTuple):
|
|
106
|
+
'''4-Tuple C{(radius, center, deltas, outer)} with C{radius} and (trilaterated)
|
|
107
|
+
C{center} of the I{inner} I{Soddy} circle and the radius of the C{outer}
|
|
108
|
+
I{Soddy} circle. The C{center} is I{un}ambiguous if C{deltas} is C{None},
|
|
109
|
+
otherwise C{center} is the mean and C{deltas} the differences of the
|
|
110
|
+
L{pygeodesy.trilaterate3d2} results.
|
|
111
|
+
|
|
112
|
+
@note: The outer I{Soddy} radius C{outer} may be C{INF}.
|
|
113
|
+
'''
|
|
114
|
+
_Names_ = (_radius_, _center_, _deltas_, _outer_)
|
|
115
|
+
_Units_ = ( Radius, _Pass, _Pass, Radius)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def circin6(point1, point2, point3, eps=EPS4, useZ=True):
|
|
119
|
+
'''Return the radius and center of the I{inscribed} aka I{Incircle} of
|
|
120
|
+
a (2- or 3-D) triangle.
|
|
121
|
+
|
|
122
|
+
@arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
123
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
124
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
125
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
126
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
127
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
128
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2} if
|
|
129
|
+
C{B{useZ} is True} else L{pygeodesy.trilaterate2d2}.
|
|
130
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
131
|
+
|
|
132
|
+
@return: L{Circin6Tuple}C{(radius, center, deltas, cA, cB, cC)}. The
|
|
133
|
+
C{center} and contact points C{cA}, C{cB} and C{cC}, each an
|
|
134
|
+
instance of B{C{point1}}'s (sub-)class, are co-planar with
|
|
135
|
+
the three given points.
|
|
136
|
+
|
|
137
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
138
|
+
than version 1.10 and C{B{useZ} is True}.
|
|
139
|
+
|
|
140
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
141
|
+
a trilateration or C{numpy} issue.
|
|
142
|
+
|
|
143
|
+
@raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
|
|
144
|
+
|
|
145
|
+
@see: Functions L{radii11} and L{circum3}, U{Contact Triangle
|
|
146
|
+
<https://MathWorld.Wolfram.com/ContactTriangle.html>} and
|
|
147
|
+
U{Incircle<https://MathWorld.Wolfram.com/Incircle.html>}.
|
|
148
|
+
'''
|
|
149
|
+
try:
|
|
150
|
+
return _circin6(point1, point2, point3, eps=eps, useZ=useZ)
|
|
151
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
152
|
+
raise _xError(x, point1=point1, point2=point2, point3=point3)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _circin6(point1, point2, point3, eps=EPS4, useZ=True, dLL3=False, **Vector_kwds):
|
|
156
|
+
# (INTERNAL) Radius, center, deltas, 3 contact points
|
|
157
|
+
|
|
158
|
+
def _fraction(r, a):
|
|
159
|
+
return (r / a) if a > EPS0 else _0_5
|
|
160
|
+
|
|
161
|
+
def _contact2(a, p2, r2, p3, r3, V, V_kwds):
|
|
162
|
+
c = p2.intermediateTo(p3, _fraction(r2, a)) if r2 > r3 else \
|
|
163
|
+
p3.intermediateTo(p2, _fraction(r3, a))
|
|
164
|
+
C = V(c.x, c.y, c.z, **V_kwds)
|
|
165
|
+
return c, C
|
|
166
|
+
|
|
167
|
+
t, p1, p2, p3 = _radii11ABC(point1, point2, point3, useZ=useZ)
|
|
168
|
+
V, r1, r2, r3 = point1.classof, t.rA, t.rB, t.rC
|
|
169
|
+
|
|
170
|
+
c1, cA = _contact2(t.a, p2, r2, p3, r3, V, _xkwds(Vector_kwds, name=_cA_))
|
|
171
|
+
c2, cB = _contact2(t.b, p3, r3, p1, r1, V, _xkwds(Vector_kwds, name=_cB_))
|
|
172
|
+
c3, cC = _contact2(t.c, p1, r1, p2, r2, V, _xkwds(Vector_kwds, name=_cC_))
|
|
173
|
+
|
|
174
|
+
r = t.rIn
|
|
175
|
+
c, d = _tricenter3d2(c1, r, c2, r, c3, r, eps=eps, useZ=useZ, dLL3=dLL3,
|
|
176
|
+
**_xkwds(Vector_kwds, Vector=V, name=circin6.__name__))
|
|
177
|
+
return Circin6Tuple(r, c, d, cA, cB, cC)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def circum3(point1, point2, point3, circum=True, eps=EPS4, useZ=True):
|
|
181
|
+
'''Return the radius and center of the smallest circle I{through} or
|
|
182
|
+
I{containing} three (2- or 3-D) points.
|
|
183
|
+
|
|
184
|
+
@arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
185
|
+
C{Vector4Tuple}).
|
|
186
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
187
|
+
C{Vector4Tuple}).
|
|
188
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
189
|
+
C{Vector4Tuple}).
|
|
190
|
+
@kwarg circum: If C{True} return the C{circumradius} and C{circumcenter}
|
|
191
|
+
always, ignoring the I{Meeus}' Type I case (C{bool}).
|
|
192
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2} if C{B{useZ}
|
|
193
|
+
is True} else L{pygeodesy.trilaterate2d2}.
|
|
194
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
195
|
+
|
|
196
|
+
@return: A L{Circum3Tuple}C{(radius, center, deltas)}. The C{center}, an
|
|
197
|
+
instance of B{C{point1}}'s (sub-)class, is co-planar with the three
|
|
198
|
+
given points.
|
|
199
|
+
|
|
200
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
201
|
+
than version 1.10 and C{B{useZ} is True}.
|
|
202
|
+
|
|
203
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
204
|
+
a trilateration or C{numpy} issue.
|
|
205
|
+
|
|
206
|
+
@raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
|
|
207
|
+
|
|
208
|
+
@see: Functions L{pygeodesy.circum4_} and L{pygeodesy.meeus2} and Meeus, J.
|
|
209
|
+
U{I{Astronomical Algorithms}<http://www.Agopax.IT/Libri_astronomia/pdf/
|
|
210
|
+
Astronomical%20Algorithms.pdf>}, 2nd Ed. 1998, page 127ff, U{circumradius
|
|
211
|
+
<https://MathWorld.Wolfram.com/Circumradius.html>} and U{circumcircle
|
|
212
|
+
<https://MathWorld.Wolfram.com/Circumcircle.html>}.
|
|
213
|
+
'''
|
|
214
|
+
try:
|
|
215
|
+
p1 = _otherV3d(useZ=useZ, point1=point1)
|
|
216
|
+
return _circum3(p1, point2, point3, circum=circum, eps=eps, useZ=useZ,
|
|
217
|
+
clas=point1.classof)
|
|
218
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
219
|
+
raise _xError(x, point1=point1, point2=point2, point3=point3, circum=circum)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _circum3(p1, point2, point3, circum=True, eps=EPS4, useZ=True, dLL3=False,
|
|
223
|
+
clas=Vector3d, **clas_kwds): # in .latlonBase
|
|
224
|
+
# (INTERNAL) Radius, center, deltas
|
|
225
|
+
r, d, p2, p3 = _meeus4(p1, point2, point3, circum=circum, useZ=useZ,
|
|
226
|
+
clas=clas, **clas_kwds)
|
|
227
|
+
if d is None: # Meeus' Type II or circum=True
|
|
228
|
+
kwds = _xkwds(clas_kwds, eps=eps, Vector=clas, name=circum3.__name__)
|
|
229
|
+
c, d = _tricenter3d2(p1, r, p2, r, p3, r, useZ=useZ, dLL3=dLL3, **kwds)
|
|
230
|
+
else: # Meeus' Type I
|
|
231
|
+
c, d = d, None
|
|
232
|
+
return Circum3Tuple(r, c, d)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def circum4_(*points, **useZ_Vector_and_kwds):
|
|
236
|
+
'''Best-fit a sphere through three or more (3-D) points.
|
|
237
|
+
|
|
238
|
+
@arg points: The points (each a C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
239
|
+
or C{Vector4Tuple}).
|
|
240
|
+
@kwarg useZ_Vector_and_kwds: Keyword arguments C{B{useZ}=True} (C{bool})
|
|
241
|
+
to use the Z components, otherwise force all C{z=INT0}, class
|
|
242
|
+
C{B{Vector}=None} to return the center point with optionally,
|
|
243
|
+
additional nB{C{Vector}} keyword arguments, otherwise the
|
|
244
|
+
first B{C{points}}' (sub-)class is used.
|
|
245
|
+
|
|
246
|
+
@return: L{Circum4Tuple}C{(radius, center, rank, residuals)} with C{center} an
|
|
247
|
+
instance of C{B{points}[0]}' (sub-)class or B{C{Vector}} if specified.
|
|
248
|
+
|
|
249
|
+
@raise ImportError: Package C{numpy} not found, not installed or older than
|
|
250
|
+
version 1.10.
|
|
251
|
+
|
|
252
|
+
@raise NumPyError: Some C{numpy} issue.
|
|
253
|
+
|
|
254
|
+
@raise PointsError: Too few B{C{points}}.
|
|
255
|
+
|
|
256
|
+
@raise TypeError: One of the B{C{points}} is invalid.
|
|
257
|
+
|
|
258
|
+
@see: Functions L{pygeodesy.circum3} and L{pygeodesy.meeus2}, Jekel, Charles F. U{I{Least
|
|
259
|
+
Squares Sphere Fit}<https://Jekel.me/2015/Least-Squares-Sphere-Fit/>} Sep 13, 2015,
|
|
260
|
+
U{Appendix A<https://hdl.handle.net/10019.1/98627>}, U{numpy.linalg.lstsq<https://
|
|
261
|
+
NumPy.org/doc/stable/reference/generated/numpy.linalg.lstsq.html>} and U{Eberly 6
|
|
262
|
+
<https://www.sci.Utah.EDU/~balling/FEtools/doc_files/LeastSquaresFitting.pdf>}.
|
|
263
|
+
'''
|
|
264
|
+
def _useZ_kwds(useZ=True, **kwds):
|
|
265
|
+
return useZ, kwds
|
|
266
|
+
|
|
267
|
+
n, ps = len2(points)
|
|
268
|
+
if n < 3:
|
|
269
|
+
raise PointsError(points=n, txt=_too_(_few_))
|
|
270
|
+
useZ, kwds = _useZ_kwds(**useZ_Vector_and_kwds)
|
|
271
|
+
|
|
272
|
+
A, b = [], []
|
|
273
|
+
for i, p in enumerate(ps):
|
|
274
|
+
v = _otherV3d(useZ=useZ, i=i, points=p)
|
|
275
|
+
A.append(v.times(_2_0).xyz + _1_0_1T)
|
|
276
|
+
b.append(v.length2)
|
|
277
|
+
|
|
278
|
+
with _numpy(circum4_, n=n) as _np:
|
|
279
|
+
A = _np.array(A).reshape((n, 4))
|
|
280
|
+
b = _np.array(b).reshape((n, 1))
|
|
281
|
+
C, R, rk, _ = _np.least_squares4(A, b, rcond=None) # to silence warning
|
|
282
|
+
C = map2(float, C)
|
|
283
|
+
R = map2(float, R) # empty if rk < 4 or n <= 4
|
|
284
|
+
|
|
285
|
+
n = circum4_.__name__
|
|
286
|
+
c = Vector3d(*C[:3], name=n)
|
|
287
|
+
r = Radius(sqrt(fsumf_(C[3], *c.x2y2z2)), name=n)
|
|
288
|
+
|
|
289
|
+
c = _nVc(c, **_xkwds(kwds, clas=ps[0].classof, name=n))
|
|
290
|
+
return Circum4Tuple(r, c, rk, R)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _iscolinearWith(p, point1, point2, eps=EPS, useZ=True):
|
|
294
|
+
# (INTERNAL) Check colinear, see L{iscolinearWith} above,
|
|
295
|
+
# separated to allow callers to embellish any exceptions
|
|
296
|
+
p1 = _otherV3d(useZ=useZ, point1=point1)
|
|
297
|
+
p2 = _otherV3d(useZ=useZ, point2=point2)
|
|
298
|
+
n, _ = _nearestOn2(p, p1, p2, within=False, eps=eps)
|
|
299
|
+
return n is p1 or n.minus(p).length2 < eps
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def meeus2(point1, point2, point3, circum=False, useZ=True):
|
|
303
|
+
'''Return the radius and I{Meeus}' Type of the smallest circle I{through}
|
|
304
|
+
or I{containing} three (2- or 3-D) points.
|
|
305
|
+
|
|
306
|
+
@arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
307
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
308
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
309
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
310
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
311
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
312
|
+
@kwarg circum: If C{True} return the C{circumradius} and C{circumcenter}
|
|
313
|
+
always, overriding I{Meeus}' Type II case (C{bool}).
|
|
314
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
315
|
+
|
|
316
|
+
@return: L{Meeus2Tuple}C{(radius, Type)}, with C{Type} the C{circumcenter}
|
|
317
|
+
iff C{B{circum}=True}.
|
|
318
|
+
|
|
319
|
+
@raise IntersectionError: Near-coincident or -colinear points, iff C{B{circum}=True}.
|
|
320
|
+
|
|
321
|
+
@raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
|
|
322
|
+
|
|
323
|
+
@see: Functions L{pygeodesy.circum3} and L{pygeodesy.circum4_} and Meeus, J.
|
|
324
|
+
U{I{Astronomical Algorithms}<http://www.Agopax.IT/Libri_astronomia/pdf/
|
|
325
|
+
Astronomical%20Algorithms.pdf>}, 2nd Ed. 1998, page 127ff, U{circumradius
|
|
326
|
+
<https://MathWorld.Wolfram.com/Circumradius.html>} and U{circumcircle
|
|
327
|
+
<https://MathWorld.Wolfram.com/Circumcircle.html>}.
|
|
328
|
+
'''
|
|
329
|
+
try:
|
|
330
|
+
A = _otherV3d(useZ=useZ, point1=point1)
|
|
331
|
+
return _meeus2(A, point2, point3, circum, useZ=useZ, clas=point1.classof)
|
|
332
|
+
except (TypeError, ValueError) as x:
|
|
333
|
+
raise _xError(x, point1=point1, point2=point2, point3=point3, circum=circum)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _meeus2(A, point2, point3, circum, useZ=True, **clas_and_kwds): # in .vector3d
|
|
337
|
+
# (INTERNAL) Radius and center or Meeus' Type
|
|
338
|
+
f = _circum3 if circum else _meeus4
|
|
339
|
+
t = f(A, point2, point3, circum=circum, useZ=useZ, **clas_and_kwds)[:2]
|
|
340
|
+
return Meeus2Tuple(t)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _meeus4(A, point2, point3, circum=False, useZ=True, clas=None, **clas_kwds):
|
|
344
|
+
# (INTERNAL) Radius and Meeus' Type
|
|
345
|
+
B = p2 = _otherV3d(useZ=useZ, point2=point2)
|
|
346
|
+
C = p3 = _otherV3d(useZ=useZ, point3=point3)
|
|
347
|
+
|
|
348
|
+
a = B.minus(C).length2
|
|
349
|
+
b = C.minus(A).length2
|
|
350
|
+
c = A.minus(B).length2
|
|
351
|
+
if a < b:
|
|
352
|
+
a, b, A, B = b, a, B, A
|
|
353
|
+
if a < c:
|
|
354
|
+
a, c, A, C = c, a, C, A
|
|
355
|
+
|
|
356
|
+
if a > EPS02 and (circum or a < (b + c)): # circumradius
|
|
357
|
+
b = sqrt(b / a)
|
|
358
|
+
c = sqrt(c / a)
|
|
359
|
+
R = Fsum(_1_0, b, c) * Fsum(_1_0, b, -c) * Fsum(_1_0, -b, c) * Fsum(_N_1_0, b, c)
|
|
360
|
+
r = R.fover(a)
|
|
361
|
+
if r < EPS02:
|
|
362
|
+
raise IntersectionError(_coincident_ if b < EPS0 or c < EPS0 else (
|
|
363
|
+
_colinear_ if _iscolinearWith(A, B, C) else _invalid_))
|
|
364
|
+
r = b * c / sqrt(r)
|
|
365
|
+
t = None # Meeus' Type II
|
|
366
|
+
else: # obtuse or right angle at A
|
|
367
|
+
r = sqrt(a * _0_25) if a > EPS02 else INT0
|
|
368
|
+
t = B.plus(C).times(_0_5) # Meeus' Type I
|
|
369
|
+
if clas is not None:
|
|
370
|
+
t = clas(t.x, t.y, t.z, **_xkwds(clas_kwds, name=meeus2.__name__))
|
|
371
|
+
return r, t, p2, p3
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class _numpy(object): # see also .formy._idllmn6, .geodesicw._wargs, .latlonBase._toCartesian3
|
|
375
|
+
'''(INTERNAL) Partial C{NumPy} wrapper.
|
|
376
|
+
'''
|
|
377
|
+
@contextmanager # <https://www.Python.org/dev/peps/pep-0343/> Examples
|
|
378
|
+
def __call__(self, where, *args, **kwds):
|
|
379
|
+
'''(INTERNAL) Yield self with any errors raised as L{NumPyError}
|
|
380
|
+
embellished with all B{C{args}} and B{C{kwds}}.
|
|
381
|
+
'''
|
|
382
|
+
np = self.np
|
|
383
|
+
try: # <https://NumPy.org/doc/stable/reference/generated/numpy.seterr.html>
|
|
384
|
+
e = np.seterr(all=_raise_) # throw FloatingPointError for numpy errors
|
|
385
|
+
yield self
|
|
386
|
+
except Exception as x: # mostly FloatingPointError?
|
|
387
|
+
t = unstr(where, *args, **kwds)
|
|
388
|
+
raise NumPyError(t, cause=x) # _xError2?
|
|
389
|
+
finally: # restore numpy error handling
|
|
390
|
+
np.seterr(**e)
|
|
391
|
+
|
|
392
|
+
@Property_RO
|
|
393
|
+
def array(self):
|
|
394
|
+
return self.np.array
|
|
395
|
+
|
|
396
|
+
@Property_RO
|
|
397
|
+
def least_squares4(self):
|
|
398
|
+
'''Linear least-squares function.
|
|
399
|
+
'''
|
|
400
|
+
return self.np.linalg.lstsq
|
|
401
|
+
|
|
402
|
+
@Property_RO
|
|
403
|
+
def np(self):
|
|
404
|
+
'''Import numpy 1.10+ once.
|
|
405
|
+
'''
|
|
406
|
+
return _xnumpy(self.__class__, 1, 10)
|
|
407
|
+
|
|
408
|
+
def null_space2(self, A, rcond=None):
|
|
409
|
+
'''Return the C{null_space} and C{rank} of matrix B{C{A}}.
|
|
410
|
+
|
|
411
|
+
@see: U{Source<https://docs.SciPy.org/doc/scipy/reference/generated/scipy.linalg.null_space.html>}
|
|
412
|
+
U{SciPY Cookbook<https://SciPy-Cookbook.ReadTheDocs.io/items/RankNullspace.html>}, U{here
|
|
413
|
+
<https://NumPy.org/doc/stable/reference/generated/numpy.linalg.svd.html>}, U{here
|
|
414
|
+
<https://StackOverflow.com/questions/19820921>}, U{here
|
|
415
|
+
<https://StackOverflow.com/questions/2992947>} and U{here
|
|
416
|
+
<https://StackOverflow.com/questions/5889142>}.
|
|
417
|
+
'''
|
|
418
|
+
def _Error(txt=self.null_space2.__name__, **kwds):
|
|
419
|
+
return _AssertionError(txt=txt, **kwds)
|
|
420
|
+
|
|
421
|
+
np = self.np
|
|
422
|
+
A = np.array(A)
|
|
423
|
+
m = max(A.shape)
|
|
424
|
+
if m != 4: # for this usage
|
|
425
|
+
raise _Error(shape=m)
|
|
426
|
+
# if needed, square A, pad with zeros
|
|
427
|
+
A = np.resize(A, m * m).reshape(m, m)
|
|
428
|
+
# try: # no np.linalg.null_space <https://docs.SciPy.org/doc/>
|
|
429
|
+
# Z = scipy.linalg.null_space(A) # XXX no scipy.linalg?
|
|
430
|
+
# return Z, ...
|
|
431
|
+
# except AttributeError:
|
|
432
|
+
# pass
|
|
433
|
+
U, S, V = np.linalg.svd(A)
|
|
434
|
+
s = max(EPS, rcond) if rcond else (EPS * max(U.shape[0], V.shape[1]))
|
|
435
|
+
t = max(EPS, float(np.max(S) * s)) # abs_tol, rel_tol * largest singular
|
|
436
|
+
r = int(np.sum(S > t)) # rank
|
|
437
|
+
if r == 3: # get null_space
|
|
438
|
+
Z = np.transpose(V[r:])
|
|
439
|
+
s = map2(int, Z.shape)
|
|
440
|
+
if s != (m, 1): # bad null_space shape
|
|
441
|
+
raise _Error(shape=s, m=m)
|
|
442
|
+
D = A.dot(Z) # near-zeros-vector
|
|
443
|
+
n = float(np.linalg.norm(D, INF)) # INF = max(fabs(D)), 2 = hypot_(*D)
|
|
444
|
+
if n > t: # largest exceed tol
|
|
445
|
+
raise _Error(dot=tuple(D.ravel()), norm=n, tol=t)
|
|
446
|
+
else: # coincident, colinear, concentric centers, ambiguous, etc.
|
|
447
|
+
Z = None
|
|
448
|
+
# del A, S, U, V # release numpy
|
|
449
|
+
return Z, r
|
|
450
|
+
|
|
451
|
+
@Property_RO
|
|
452
|
+
def pseudo_inverse(self):
|
|
453
|
+
'''Moore-Penrose pseudo-inverse function.
|
|
454
|
+
'''
|
|
455
|
+
return self.np.linalg.pinv
|
|
456
|
+
|
|
457
|
+
def real_roots(self, *coeffs):
|
|
458
|
+
'''Compute the real, non-complex roots of a polynomial.
|
|
459
|
+
'''
|
|
460
|
+
np = self.np
|
|
461
|
+
rs = np.polynomial.polynomial.polyroots(coeffs)
|
|
462
|
+
return tuple(float(r) for r in rs if not np.iscomplex(r))
|
|
463
|
+
|
|
464
|
+
_numpy = _numpy() # PYCHOK singleton
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def radii11(point1, point2, point3, useZ=True):
|
|
468
|
+
'''Return the radii of the C{In-}, I{Soddy} and C{Tangent} circles of a
|
|
469
|
+
(2- or 3-D) triangle.
|
|
470
|
+
|
|
471
|
+
@arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
472
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
473
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
474
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
475
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
476
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
477
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
478
|
+
|
|
479
|
+
@return: L{Radii11Tuple}C{(rA, rB, rC, cR, rIn, riS, roS, a, b, c, s)}.
|
|
480
|
+
|
|
481
|
+
@raise TriangleError: Near-coincident or -colinear points.
|
|
482
|
+
|
|
483
|
+
@raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
|
|
484
|
+
|
|
485
|
+
@see: U{Circumradius<https://MathWorld.Wolfram.com/Circumradius.html>},
|
|
486
|
+
U{Incircle<https://MathWorld.Wolfram.com/Incircle.html>}, U{Soddy
|
|
487
|
+
Circles<https://MathWorld.Wolfram.com/SoddyCircles.html>} and
|
|
488
|
+
U{Tangent Circles<https://MathWorld.Wolfram.com/TangentCircles.html>}.
|
|
489
|
+
'''
|
|
490
|
+
try:
|
|
491
|
+
return _radii11ABC(point1, point2, point3, useZ=useZ)[0]
|
|
492
|
+
except (TypeError, ValueError) as x:
|
|
493
|
+
raise _xError(x, point1=point1, point2=point2, point3=point3)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _radii11ABC(point1, point2, point3, useZ=True):
|
|
497
|
+
# (INTERNAL) Tangent, Circum, Incircle, Soddy radii, sides and semi-perimeter
|
|
498
|
+
A = _otherV3d(useZ=useZ, point1=point1, NN_OK=False)
|
|
499
|
+
B = _otherV3d(useZ=useZ, point2=point2, NN_OK=False)
|
|
500
|
+
C = _otherV3d(useZ=useZ, point3=point3, NN_OK=False)
|
|
501
|
+
|
|
502
|
+
a = B.minus(C).length
|
|
503
|
+
b = C.minus(A).length
|
|
504
|
+
c = A.minus(B).length
|
|
505
|
+
|
|
506
|
+
S = Fsum(a, b, c) * _0_5
|
|
507
|
+
s = float(S) # semi-perimeter
|
|
508
|
+
if s > EPS0:
|
|
509
|
+
rs = float(S - a), float(S - b), float(S - c)
|
|
510
|
+
r3, r2, r1 = sorted(rs) # r3 <= r2 <= r1
|
|
511
|
+
if r3 > EPS0: # and r2 > EPS0 and r1 > EPS0
|
|
512
|
+
r3_r1 = r3 / r1
|
|
513
|
+
r3_r2 = r3 / r2
|
|
514
|
+
# t = r1 * r2 * r3 * (r1 + r2 + r3)
|
|
515
|
+
# = r1**2 * r2 * r3 * (1 + r2 / r1 + r3 / r1)
|
|
516
|
+
# = (r1 * r2)**2 * (r3 / r2) * (1 + r2 / r1 + r3 / r1)
|
|
517
|
+
t = r3_r2 * fsum1f_(_1_0, r2 / r1, r3_r1) # * (r1 * r2)**2
|
|
518
|
+
if t > EPS02:
|
|
519
|
+
t = sqrt(t) * _2_0 # * r1 * r2
|
|
520
|
+
# d = r1 * r2 + r2 * r3 + r3 * r1
|
|
521
|
+
# = r1 * (r2 + r2 * r3 / r1 + r3)
|
|
522
|
+
# = r1 * r2 * (1 + r3 / r1 + r3 / r2)
|
|
523
|
+
d = fsum1f_(_1_0, r3_r1, r3_r2) # * r1 * r2
|
|
524
|
+
# si/o = r1 * r2 * r3 / (r1 * r2 * (d +/- t))
|
|
525
|
+
# = r3 / (d +/- t)
|
|
526
|
+
si = r3 / (d + t)
|
|
527
|
+
so = (r3 / (d - t)) if d > t else INF
|
|
528
|
+
# ci = sqrt(r1 * r2 * r3 / s)
|
|
529
|
+
# = r1 * sqrt(r2 * r3 / r1 / s)
|
|
530
|
+
ci = r1 * sqrt(r2 * r3_r1 / s)
|
|
531
|
+
# co = a * b * c / (4 * ci * s)
|
|
532
|
+
t = ci * s * _4_0
|
|
533
|
+
co = (a * b * c / t) if t > EPS0 else INF
|
|
534
|
+
r1, r2, r3 = rs # original order
|
|
535
|
+
t = Radii11Tuple(r1, r2, r3, co, ci, si, so, a, b, c, s)
|
|
536
|
+
return t, A, B, C
|
|
537
|
+
|
|
538
|
+
raise TriangleError(_near_(_coincident_) if min(a, b, c) < EPS0 else (
|
|
539
|
+
_colinear_ if _iscolinearWith(A, B, C) else _invalid_))
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def soddy4(point1, point2, point3, eps=EPS4, useZ=True):
|
|
543
|
+
'''Return the radius and center of the C{inner} I{Soddy} circle of a
|
|
544
|
+
(2- or 3-D) triangle.
|
|
545
|
+
|
|
546
|
+
@arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
547
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
548
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
549
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
550
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
551
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
552
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2} if
|
|
553
|
+
C{B{useZ} is True} otherwise L{pygeodesy.trilaterate2d2}.
|
|
554
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
555
|
+
|
|
556
|
+
@return: L{Soddy4Tuple}C{(radius, center, deltas, outer)}. The C{center},
|
|
557
|
+
an instance of B{C{point1}}'s (sub-)class, is co-planar with the
|
|
558
|
+
three given points. The C{outer} I{Soddy} radius may be C{INF}.
|
|
559
|
+
|
|
560
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
561
|
+
than version 1.10 and C{B{useZ} is True}.
|
|
562
|
+
|
|
563
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
564
|
+
a trilateration or C{numpy} issue.
|
|
565
|
+
|
|
566
|
+
@raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
|
|
567
|
+
|
|
568
|
+
@see: Functions L{radii11} and L{circum3} and U{Soddy Circles
|
|
569
|
+
<https://MathWorld.Wolfram.com/SoddyCircles.html>}.
|
|
570
|
+
'''
|
|
571
|
+
t, p1, p2, p3 = _radii11ABC(point1, point2, point3, useZ=useZ)
|
|
572
|
+
|
|
573
|
+
r = t.riS
|
|
574
|
+
c, d = _tricenter3d2(p1, t.rA + r,
|
|
575
|
+
p2, t.rB + r,
|
|
576
|
+
p3, t.rC + r, eps=eps, useZ=useZ,
|
|
577
|
+
Vector=point1.classof, name=soddy4.__name__)
|
|
578
|
+
return Soddy4Tuple(r, c, d, t.roS)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _tricenter3d2(p1, r1, p2, r2, p3, r3, eps=EPS4, useZ=True, dLL3=False, **kwds):
|
|
582
|
+
# (INTERNAL) Trilaterate and disambiguate the 3-D center
|
|
583
|
+
d, kwds = None, _xkwds(kwds, eps=eps, coin=True)
|
|
584
|
+
if useZ and p1.z != p2.z != p3.z: # ignore z if all match
|
|
585
|
+
a, b = _trilaterate3d2(p1, r1, p2, r2, p3, r3, **kwds)
|
|
586
|
+
if a is b: # no unambiguity
|
|
587
|
+
c = a # == b
|
|
588
|
+
else:
|
|
589
|
+
c = a.plus(b).times(_0_5) # mean
|
|
590
|
+
if not a.isconjugateTo(b, minum=0, eps=eps):
|
|
591
|
+
if dLL3: # deltas as (lat, lon, height)
|
|
592
|
+
a = a.toLatLon()
|
|
593
|
+
b = b.toLatLon()
|
|
594
|
+
d = LatLon3Tuple(b.lat - a.lat,
|
|
595
|
+
b.lon - a.lon,
|
|
596
|
+
b.height - a.height, name=_deltas_)
|
|
597
|
+
else:
|
|
598
|
+
d = b.minus(a) # vectorial deltas
|
|
599
|
+
else:
|
|
600
|
+
if useZ: # pass z to Vector if given
|
|
601
|
+
kwds = _xkwds(kwds, z=p1.z)
|
|
602
|
+
c = _trilaterate2d2(p1.x, p1.y, r1,
|
|
603
|
+
p2.x, p2.y, r2,
|
|
604
|
+
p3.x, p3.y, r3, **kwds)
|
|
605
|
+
return c, d
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _trilaterate2d2(x1, y1, radius1, x2, y2, radius2, x3, y3, radius3,
|
|
609
|
+
coin=False, eps=None,
|
|
610
|
+
Vector=None, **Vector_kwds):
|
|
611
|
+
# (INTERNAL) Trilaterate three circles, see L{pygeodesy.trilaterate2d2}
|
|
612
|
+
|
|
613
|
+
def _abct4(x1, y1, r1, x2, y2, r2):
|
|
614
|
+
a = x2 - x1
|
|
615
|
+
b = y2 - y1
|
|
616
|
+
t = _tri3near2far(r1, r2, hypot(a, b), coin)
|
|
617
|
+
c = _0_0 if t else (hypot2_(r1, x2, y2) - hypot2_(r2, x1, y1))
|
|
618
|
+
return a, b, c, t
|
|
619
|
+
|
|
620
|
+
def _astr(**kwds): # kwds as (name=value, ...) strings
|
|
621
|
+
return Fmt.PAREN(_COMMASPACE_(*(Fmt.EQUALg(*t) for t in kwds.items())))
|
|
622
|
+
|
|
623
|
+
r1 = Radius_(radius1=radius1)
|
|
624
|
+
r2 = Radius_(radius2=radius2)
|
|
625
|
+
r3 = Radius_(radius3=radius3)
|
|
626
|
+
|
|
627
|
+
a, b, c, t = _abct4(x1, y1, r1, x2, y2, r2)
|
|
628
|
+
if t:
|
|
629
|
+
raise IntersectionError(_and(_astr(x1=x1, y1=y1, radius1=r1),
|
|
630
|
+
_astr(x2=x2, y2=y2, radius2=r2)), txt=t)
|
|
631
|
+
|
|
632
|
+
d, e, f, t = _abct4(x2, y2, r2, x3, y3, r3)
|
|
633
|
+
if t:
|
|
634
|
+
raise IntersectionError(_and(_astr(x2=x2, y2=y2, radius2=r2),
|
|
635
|
+
_astr(x3=x3, y3=y3, radius3=r3)), txt=t)
|
|
636
|
+
|
|
637
|
+
_, _, _, t = _abct4(x3, y3, r3, x1, y1, r1)
|
|
638
|
+
if t:
|
|
639
|
+
raise IntersectionError(_and(_astr(x3=x3, y3=y3, radius3=r3),
|
|
640
|
+
_astr(x1=x1, y1=y1, radius1=r1)), txt=t)
|
|
641
|
+
|
|
642
|
+
q = (a * e - b * d) * _2_0
|
|
643
|
+
if isnear0(q):
|
|
644
|
+
t = _no_(_intersection_)
|
|
645
|
+
raise IntersectionError(_and(_astr(x1=x1, y1=y1, radius1=r1),
|
|
646
|
+
_astr(x2=x2, y2=y2, radius2=r2),
|
|
647
|
+
_astr(x3=x3, y3=y3, radius3=r3)), txt=t)
|
|
648
|
+
t = Vector2Tuple((c * e - b * f) / q,
|
|
649
|
+
(a * f - c * d) / q, name=trilaterate2d2.__name__)
|
|
650
|
+
|
|
651
|
+
if eps and eps > 0: # check distances to center vs radius
|
|
652
|
+
for x, y, r in ((x1, y1, r1), (x2, y2, r2), (x3, y3, r3)):
|
|
653
|
+
d = hypot(x - t.x, y - t.y)
|
|
654
|
+
e = fabs(d - r)
|
|
655
|
+
if e > eps:
|
|
656
|
+
t = _and(Float(delta=e).toRepr(), r.toRepr(),
|
|
657
|
+
Float(distance=d).toRepr(), t.toRepr())
|
|
658
|
+
raise IntersectionError(t, txt=Fmt.exceeds_eps(eps))
|
|
659
|
+
|
|
660
|
+
if Vector is not None:
|
|
661
|
+
t = Vector(t.x, t.y, **_xkwds(Vector_kwds, name=t.name))
|
|
662
|
+
return t
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _trilaterate3d2(c1, r1, c2, r2, c3, r3, eps=EPS, coin=False,
|
|
666
|
+
**clas_Vector_and_kwds):
|
|
667
|
+
# (INTERNAL) Intersect three spheres or circles, see function
|
|
668
|
+
# L{pygeodesy.trilaterate3d2}, separated to allow callers to
|
|
669
|
+
# embellish exceptions, like C{FloatingPointError}s from C{numpy}
|
|
670
|
+
|
|
671
|
+
def _F3d2(F):
|
|
672
|
+
# map numpy 4-vector to floats tuple and Vector3d
|
|
673
|
+
T = map2(float, F)
|
|
674
|
+
return T, Vector3d(*T[1:])
|
|
675
|
+
|
|
676
|
+
def _N3(t01, x, z):
|
|
677
|
+
# compute x, y and z and return as B{C{clas}} or B{C{Vector}}
|
|
678
|
+
v = x.plus(z.times(t01))
|
|
679
|
+
n = trilaterate3d2.__name__
|
|
680
|
+
return _nVc(v, **_xkwds(clas_Vector_and_kwds, name=n))
|
|
681
|
+
|
|
682
|
+
c2 = _otherV3d(center2=c2, NN_OK=False)
|
|
683
|
+
c3 = _otherV3d(center3=c3, NN_OK=False)
|
|
684
|
+
rs = (r1, Radius_(radius2=r2, low=EPS),
|
|
685
|
+
Radius_(radius3=r3, low=EPS))
|
|
686
|
+
|
|
687
|
+
# get matrix A[3 x 4], its pseudo-inverse and null_space Z
|
|
688
|
+
A = [(_1_0_1T + c.times(_N_2_0).xyz) for c in (c1, c2, c3)]
|
|
689
|
+
with _numpy(trilaterate3d2, A=A, eps=eps) as _np:
|
|
690
|
+
Z, _ = _np.null_space2(A, eps)
|
|
691
|
+
if Z is not None:
|
|
692
|
+
Z, z = _F3d2(Z) # [4 x 1]
|
|
693
|
+
z2 = z.length2
|
|
694
|
+
A = _np.pseudo_inverse(A) # [4 x 3]
|
|
695
|
+
bs = [c.length2 for c in (c1, c2, c3)]
|
|
696
|
+
# perturbe radii and vector b slightly by eps and eps * 4
|
|
697
|
+
for p in _tri5perturbs(eps, min(rs)):
|
|
698
|
+
b = [((r + p)**2 - b) for r, b in zip(rs, bs)] # [3 x 1]
|
|
699
|
+
X, x = _F3d2(A.dot(b))
|
|
700
|
+
# quadratic polynomial, coefficients ordered (^0, ^1, ^2)
|
|
701
|
+
t = _np.real_roots(fdot(X, _N_1_0, *x.xyz),
|
|
702
|
+
fdot(Z, _N_0_5, *x.xyz) * _2_0, z2)
|
|
703
|
+
if t:
|
|
704
|
+
v = _N3(t[0], x, z)
|
|
705
|
+
if len(t) < 2: # one intersection
|
|
706
|
+
t = v, v
|
|
707
|
+
elif fabs(t[0] - t[1]) < eps: # abutting
|
|
708
|
+
t = v, v
|
|
709
|
+
else: # "lowest" intersection first (to avoid test failures)
|
|
710
|
+
u = _N3(t[1], x, z)
|
|
711
|
+
t = (u, v) if u.x < v.x else (v, u)
|
|
712
|
+
return t
|
|
713
|
+
|
|
714
|
+
# coincident, concentric, colinear, too distant, no intersection:
|
|
715
|
+
# create the explanation and and throw an IntersectionError
|
|
716
|
+
|
|
717
|
+
def _no_intersection(coin):
|
|
718
|
+
t = _no_(_intersection_)
|
|
719
|
+
if coin:
|
|
720
|
+
def _reprs(*crs):
|
|
721
|
+
return _and(*map(repr, crs))
|
|
722
|
+
|
|
723
|
+
r = repr(r1) if r1 == r2 == r3 else _reprs(r1, r2, r3)
|
|
724
|
+
t = _SPACE_(t, _of_, _reprs(c1, c2, c3), _with_, _radius_, r)
|
|
725
|
+
elif Z is None:
|
|
726
|
+
t = _COMMASPACE_(t, _no_(_numpy.null_space2.__name__))
|
|
727
|
+
return t
|
|
728
|
+
|
|
729
|
+
t = _tri4near2far(c1, r1, c2, r2, coin) or \
|
|
730
|
+
_tri4near2far(c1, r1, c3, r3, coin) or \
|
|
731
|
+
_tri4near2far(c2, r2, c3, r3, coin) or (
|
|
732
|
+
_colinear_ if _iscolinearWith(c1, c2, c3, eps=eps) else
|
|
733
|
+
_no_intersection(coin))
|
|
734
|
+
raise IntersectionError(t, txt=None)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _tri3near2far(r1, r2, h, coin):
|
|
738
|
+
# check for near-coincident/-concentric or too distant spheres/circles
|
|
739
|
+
return _too_(Fmt.distant(h)) if h > (r1 + r2) else (_near_(
|
|
740
|
+
_coincident_ if coin else _concentric_) if h < fabs(r1 - r2) else NN)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _tri4near2far(c1, r1, c2, r2, coin):
|
|
744
|
+
# check for near-coincident/-concentric or too distant spheres/circles
|
|
745
|
+
t = _tri3near2far(r1, r2, c1.minus(c2).length, coin)
|
|
746
|
+
return _SPACE_(c1.name, _and_, c2.name, t) if t else NN
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _tri5perturbs(eps, r):
|
|
750
|
+
# perturb the radii to handle this corner case
|
|
751
|
+
# <https://GitHub.com/mrJean1/PyGeodesy/issues/49>
|
|
752
|
+
yield _0_0
|
|
753
|
+
if eps and eps > 0:
|
|
754
|
+
p = max(eps, EPS)
|
|
755
|
+
yield p
|
|
756
|
+
m = min(p, r)
|
|
757
|
+
yield -m
|
|
758
|
+
q = max(eps * _4_0, _EPS4e8)
|
|
759
|
+
if q > p:
|
|
760
|
+
yield q
|
|
761
|
+
q = min(q, r)
|
|
762
|
+
if q > m:
|
|
763
|
+
yield -q
|
|
764
|
+
|
|
765
|
+
# **) MIT License
|
|
766
|
+
#
|
|
767
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
768
|
+
#
|
|
769
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
770
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
771
|
+
# to deal in the Software without restriction, including without limitation
|
|
772
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
773
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
774
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
775
|
+
#
|
|
776
|
+
# The above copyright notice and this permission notice shall be included
|
|
777
|
+
# in all copies or substantial portions of the Software.
|
|
778
|
+
#
|
|
779
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
780
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
781
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
782
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
783
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
784
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
785
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|