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/vector3d.py
ADDED
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Extended 3-D vector class L{Vector3d} and functions.
|
|
5
|
+
|
|
6
|
+
Function L{intersection3d3}, L{intersections2}, L{parse3d}, L{sumOf},
|
|
7
|
+
L{trilaterate2d2} and L{trilaterate3d2}.
|
|
8
|
+
'''
|
|
9
|
+
|
|
10
|
+
from pygeodesy.constants import EPS, EPS0, EPS1, EPS4, INT0, isnear0, \
|
|
11
|
+
_0_0, _1_0
|
|
12
|
+
from pygeodesy.errors import IntersectionError, _ValueError, VectorError, \
|
|
13
|
+
_xattr, _xError, _xkwds, _xkwds_get, _xkwds_item2
|
|
14
|
+
from pygeodesy.fmath import euclid, fabs, fdot, hypot, sqrt, fsum1_
|
|
15
|
+
# from pygeodesy.fsums import fsum1_ # from .fmath
|
|
16
|
+
# from pygeodesy.formy import _radical2 # in _intersects2 below
|
|
17
|
+
from pygeodesy.interns import NN, _COMMA_, _concentric_, _intersection_, \
|
|
18
|
+
_near_, _negative_, _no_, _too_
|
|
19
|
+
from pygeodesy.iters import PointsIter, Fmt
|
|
20
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
21
|
+
from pygeodesy.named import _xnamed, _xotherError
|
|
22
|
+
from pygeodesy.namedTuples import Intersection3Tuple, NearestOn2Tuple, \
|
|
23
|
+
NearestOn6Tuple, Vector3Tuple # Vector4Tuple
|
|
24
|
+
# from pygeodesy.nvectorBase import _nsumOf # _MODS
|
|
25
|
+
# from pygeodesy.streprs import Fmt # from .iters
|
|
26
|
+
from pygeodesy.units import _fi_j2, _isDegrees, Radius, Radius_
|
|
27
|
+
from pygeodesy.utily import atan2b, sincos2d
|
|
28
|
+
# from pygeodesy.vector2d import .... # in .... below
|
|
29
|
+
from pygeodesy.vector3dBase import Vector3dBase
|
|
30
|
+
|
|
31
|
+
# from math import fabs, sqrt # from .fmath
|
|
32
|
+
|
|
33
|
+
__all__ = _ALL_LAZY.vector3d
|
|
34
|
+
__version__ = '24.02.20'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Vector3d(Vector3dBase):
|
|
38
|
+
'''Extended 3-D vector.
|
|
39
|
+
|
|
40
|
+
In a geodesy context, these may be used to represent:
|
|
41
|
+
- n-vector, the normal to a point on the earth's surface
|
|
42
|
+
- Earth-Centered, Earth-Fixed (ECEF) cartesian (== spherical n-vector)
|
|
43
|
+
- great circle normal to the vector
|
|
44
|
+
- motion vector on the earth's surface
|
|
45
|
+
- etc.
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
def bearing(self, useZ=True):
|
|
49
|
+
'''Get this vector's "bearing", the angle off the +Z axis, clockwise.
|
|
50
|
+
|
|
51
|
+
@kwarg useZ: If C{True}, use the Z component, otherwise ignore the
|
|
52
|
+
Z component and consider the +Y as the +Z axis.
|
|
53
|
+
|
|
54
|
+
@return: Bearing (compass C{degrees}).
|
|
55
|
+
'''
|
|
56
|
+
x, y = self.x, self.y
|
|
57
|
+
if useZ:
|
|
58
|
+
x, y = hypot(x, y), self.z
|
|
59
|
+
return atan2b(x, y)
|
|
60
|
+
|
|
61
|
+
def circin6(self, point2, point3, eps=EPS4):
|
|
62
|
+
'''Return the radius and center of the I{inscribed} aka I{In- circle}
|
|
63
|
+
of a (3-D) triangle formed by this and two other points.
|
|
64
|
+
|
|
65
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
66
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
67
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
68
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
69
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2} if
|
|
70
|
+
C{B{useZ} is True} otherwise L{pygeodesy.trilaterate2d2}.
|
|
71
|
+
|
|
72
|
+
@return: L{Circin6Tuple}C{(radius, center, deltas, cA, cB, cC)}. The
|
|
73
|
+
C{center} and contact points C{cA}, C{cB} and C{cC}, each an
|
|
74
|
+
instance of this (sub-)class, are co-planar with this and the
|
|
75
|
+
two given points.
|
|
76
|
+
|
|
77
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
78
|
+
than version 1.10.
|
|
79
|
+
|
|
80
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
81
|
+
a trilateration or C{numpy} issue.
|
|
82
|
+
|
|
83
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
84
|
+
|
|
85
|
+
@see: Function L{pygeodesy.circin6}, U{Incircle
|
|
86
|
+
<https://MathWorld.Wolfram.com/Incircle.html>} and U{Contact
|
|
87
|
+
Triangle<https://MathWorld.Wolfram.com/ContactTriangle.html>}.
|
|
88
|
+
'''
|
|
89
|
+
try:
|
|
90
|
+
return _MODS.vector2d._circin6(self, point2, point3, eps=eps, useZ=True)
|
|
91
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
92
|
+
raise _xError(x, point=self, point2=point2, point3=point3)
|
|
93
|
+
|
|
94
|
+
def circum3(self, point2, point3, circum=True, eps=EPS4):
|
|
95
|
+
'''Return the radius and center of the smallest circle I{through} or
|
|
96
|
+
I{containing} this and two other (3-D) points.
|
|
97
|
+
|
|
98
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
99
|
+
or C{Vector4Tuple}).
|
|
100
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
101
|
+
or C{Vector4Tuple}).
|
|
102
|
+
@kwarg circum: If C{True} return the C{circumradius} and C{circumcenter},
|
|
103
|
+
always, ignoring the I{Meeus}' Type I case (C{bool}).
|
|
104
|
+
@kwarg eps: Tolerance passed to function L{pygeodesy.trilaterate3d2}.
|
|
105
|
+
|
|
106
|
+
@return: A L{Circum3Tuple}C{(radius, center, deltas)}. The C{center}, an
|
|
107
|
+
instance of this (sub-)class, is co-planar with this and the two
|
|
108
|
+
given points.
|
|
109
|
+
|
|
110
|
+
@raise ImportError: Package C{numpy} not found, not installed or older than
|
|
111
|
+
version 1.10.
|
|
112
|
+
|
|
113
|
+
@raise IntersectionError: Near-concentric, -coincident or -colinear points
|
|
114
|
+
or a trilateration or C{numpy} issue.
|
|
115
|
+
|
|
116
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
117
|
+
|
|
118
|
+
@see: Function L{pygeodesy.circum3} and methods L{circum4_} and L{meeus2}.
|
|
119
|
+
'''
|
|
120
|
+
try:
|
|
121
|
+
return _MODS.vector2d._circum3(self, point2, point3, circum=circum,
|
|
122
|
+
eps=eps, useZ=True, clas=self.classof)
|
|
123
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
124
|
+
raise _xError(x, point=self, point2=point2, point3=point3, circum=circum)
|
|
125
|
+
|
|
126
|
+
def circum4_(self, *points):
|
|
127
|
+
'''Best-fit a sphere through this and two or more other (3-D) points.
|
|
128
|
+
|
|
129
|
+
@arg points: Other points (each a C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
130
|
+
or C{Vector4Tuple}).
|
|
131
|
+
|
|
132
|
+
@return: L{Circum4Tuple}C{(radius, center, rank, residuals)} with C{center}
|
|
133
|
+
an instance if this (sub-)class.
|
|
134
|
+
|
|
135
|
+
@raise ImportError: Package C{numpy} not found, not installed or
|
|
136
|
+
older than version 1.10.
|
|
137
|
+
|
|
138
|
+
@raise NumPyError: Some C{numpy} issue.
|
|
139
|
+
|
|
140
|
+
@raise PointsError: Too few B{C{points}}.
|
|
141
|
+
|
|
142
|
+
@raise TypeError: One of the B{C{points}} invalid.
|
|
143
|
+
|
|
144
|
+
@see: Function L{pygeodesy.circum4_} and methods L{circum3} and L{meeus2}.
|
|
145
|
+
'''
|
|
146
|
+
return _MODS.vector2d.circum4_(self, *points, useZ=True, Vector=self.classof)
|
|
147
|
+
|
|
148
|
+
def iscolinearWith(self, point1, point2, eps=EPS):
|
|
149
|
+
'''Check whether this and two other (3-D) points are colinear.
|
|
150
|
+
|
|
151
|
+
@arg point1: One point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
152
|
+
or C{Vector4Tuple}).
|
|
153
|
+
@arg point2: An other point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
154
|
+
or C{Vector4Tuple}).
|
|
155
|
+
@kwarg eps: Tolerance (C{scalar}), same units as C{x},
|
|
156
|
+
C{y}, and C{z}.
|
|
157
|
+
|
|
158
|
+
@return: C{True} if this point is colinear with B{C{point1}} and
|
|
159
|
+
B{C{point2}}, C{False} otherwise.
|
|
160
|
+
|
|
161
|
+
@raise TypeError: Invalid B{C{point1}} or B{C{point2}}.
|
|
162
|
+
|
|
163
|
+
@see: Method L{nearestOn}.
|
|
164
|
+
'''
|
|
165
|
+
v = self if self.name else _otherV3d(NN_OK=False, this=self)
|
|
166
|
+
return _MODS.vector2d._iscolinearWith(v, point1, point2, eps=eps)
|
|
167
|
+
|
|
168
|
+
def meeus2(self, point2, point3, circum=False):
|
|
169
|
+
'''Return the radius and I{Meeus}' Type of the smallest circle I{through}
|
|
170
|
+
or I{containing} this and two other (3-D) points.
|
|
171
|
+
|
|
172
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
173
|
+
or C{Vector4Tuple}).
|
|
174
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
175
|
+
or C{Vector4Tuple}).
|
|
176
|
+
@kwarg circum: If C{True} return the C{circumradius} and C{circumcenter}
|
|
177
|
+
always, overriding I{Meeus}' Type II case (C{bool}).
|
|
178
|
+
|
|
179
|
+
@return: L{Meeus2Tuple}C{(radius, Type)}, with C{Type} the C{circumcenter}
|
|
180
|
+
iff C{B{circum}=True}.
|
|
181
|
+
|
|
182
|
+
@raise IntersectionError: Coincident or colinear points, iff C{B{circum}=True}.
|
|
183
|
+
|
|
184
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
185
|
+
|
|
186
|
+
@see: Function L{pygeodesy.meeus2} and methods L{circum3} and L{circum4_}.
|
|
187
|
+
'''
|
|
188
|
+
try:
|
|
189
|
+
return _MODS.vector2d._meeus2(self, point2, point3, circum, clas=self.classof)
|
|
190
|
+
except (TypeError, ValueError) as x:
|
|
191
|
+
raise _xError(x, point=self, point2=point2, point3=point3, circum=circum)
|
|
192
|
+
|
|
193
|
+
def nearestOn(self, point1, point2, within=True):
|
|
194
|
+
'''Locate the point between two points closest to this point.
|
|
195
|
+
|
|
196
|
+
@arg point1: Start point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
197
|
+
C{Vector4Tuple}).
|
|
198
|
+
@arg point2: End point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
199
|
+
C{Vector4Tuple}).
|
|
200
|
+
@kwarg within: If C{True} return the closest point between the given
|
|
201
|
+
points, otherwise the closest point on the extended
|
|
202
|
+
line through both points (C{bool}).
|
|
203
|
+
|
|
204
|
+
@return: Closest point, either B{C{point1}} or B{C{point2}} or an instance
|
|
205
|
+
of this (sub-)class.
|
|
206
|
+
|
|
207
|
+
@raise TypeError: Invalid B{C{point1}} or B{C{point2}}.
|
|
208
|
+
|
|
209
|
+
@see: Method L{sphericalTrigonometry.LatLon.nearestOn3} and U{3-D Point-Line
|
|
210
|
+
Distance<https://MathWorld.Wolfram.com/Point-LineDistance3-Dimensional.html>}.
|
|
211
|
+
'''
|
|
212
|
+
return _nearestOn2(self, point1, point2, within=within).closest
|
|
213
|
+
|
|
214
|
+
def nearestOn6(self, points, closed=False, useZ=True): # eps=EPS
|
|
215
|
+
'''Locate the point on a path or polygon closest to this point.
|
|
216
|
+
|
|
217
|
+
The closest point is either on and within the extent of a polygon
|
|
218
|
+
edge or the nearest of that edge's end points.
|
|
219
|
+
|
|
220
|
+
@arg points: The path or polygon points (C{Cartesian}, L{Vector3d},
|
|
221
|
+
C{Vector3Tuple} or C{Vector4Tuple}[]).
|
|
222
|
+
@kwarg closed: Optionally, close the path or polygon (C{bool}).
|
|
223
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
224
|
+
|
|
225
|
+
@return: A L{NearestOn6Tuple}C{(closest, distance, fi, j, start, end)}
|
|
226
|
+
with the C{closest}, the C{start} and the C{end} point each
|
|
227
|
+
an instance of this point's (sub-)class.
|
|
228
|
+
|
|
229
|
+
@raise PointsError: Insufficient number of B{C{points}}
|
|
230
|
+
|
|
231
|
+
@raise TypeError: Non-cartesian B{C{points}}.
|
|
232
|
+
|
|
233
|
+
@note: Distances measured with method L{Vector3d.equirectangular}.
|
|
234
|
+
|
|
235
|
+
@see: Function L{nearestOn6}.
|
|
236
|
+
'''
|
|
237
|
+
return nearestOn6(self, points, closed=closed, useZ=useZ) # Vector=self.classof
|
|
238
|
+
|
|
239
|
+
def parse(self, str3d, sep=_COMMA_, name=NN):
|
|
240
|
+
'''Parse an C{"x, y, z"} string to a L{Vector3d} instance.
|
|
241
|
+
|
|
242
|
+
@arg str3d: X, y and z string (C{str}), see function L{parse3d}.
|
|
243
|
+
@kwarg sep: Optional separator (C{str}).
|
|
244
|
+
@kwarg name: Optional instance name (C{str}), overriding this name.
|
|
245
|
+
|
|
246
|
+
@return: The instance (L{Vector3d}).
|
|
247
|
+
|
|
248
|
+
@raise VectorError: Invalid B{C{str3d}}.
|
|
249
|
+
'''
|
|
250
|
+
return parse3d(str3d, sep=sep, Vector=self.classof, name=name or self.name)
|
|
251
|
+
|
|
252
|
+
def radii11(self, point2, point3):
|
|
253
|
+
'''Return the radii of the C{Circum-}, C{In-}, I{Soddy} and C{Tangent}
|
|
254
|
+
circles of a (3-D) triangle.
|
|
255
|
+
|
|
256
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
257
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
258
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
259
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
260
|
+
|
|
261
|
+
@return: L{Radii11Tuple}C{(rA, rB, rC, cR, rIn, riS, roS, a, b, c, s)}.
|
|
262
|
+
|
|
263
|
+
@raise TriangleError: Near-coincident or -colinear points.
|
|
264
|
+
|
|
265
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
266
|
+
|
|
267
|
+
@see: Function L{pygeodesy.radii11}, U{Incircle
|
|
268
|
+
<https://MathWorld.Wolfram.com/Incircle.html>}, U{Soddy Circles
|
|
269
|
+
<https://MathWorld.Wolfram.com/SoddyCircles.html>} and U{Tangent
|
|
270
|
+
Circles<https://MathWorld.Wolfram.com/TangentCircles.html>}.
|
|
271
|
+
'''
|
|
272
|
+
try:
|
|
273
|
+
return _MODS.vector2d._radii11ABC(self, point2, point3, useZ=True)[0]
|
|
274
|
+
except (TypeError, ValueError) as x:
|
|
275
|
+
raise _xError(x, point=self, point2=point2, point3=point3)
|
|
276
|
+
|
|
277
|
+
def soddy4(self, point2, point3, eps=EPS4):
|
|
278
|
+
'''Return the radius and center of the C{inner} I{Soddy} circle of a
|
|
279
|
+
(3-D) triangle.
|
|
280
|
+
|
|
281
|
+
@arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
282
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
283
|
+
@arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},
|
|
284
|
+
C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).
|
|
285
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2} if
|
|
286
|
+
C{B{useZ} is True} otherwise L{pygeodesy.trilaterate2d2}.
|
|
287
|
+
|
|
288
|
+
@return: L{Soddy4Tuple}C{(radius, center, deltas, outer)}. The C{center},
|
|
289
|
+
an instance of B{C{point1}}'s (sub-)class, is co-planar with the
|
|
290
|
+
three given points.
|
|
291
|
+
|
|
292
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
293
|
+
than version 1.10.
|
|
294
|
+
|
|
295
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
296
|
+
a trilateration or C{numpy} issue.
|
|
297
|
+
|
|
298
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
299
|
+
|
|
300
|
+
@see: Function L{pygeodesy.soddy4}.
|
|
301
|
+
'''
|
|
302
|
+
return _MODS.vector2d.soddy4(self, point2, point3, eps=eps, useZ=True)
|
|
303
|
+
|
|
304
|
+
def trilaterate2d2(self, radius, center2, radius2, center3, radius3, eps=EPS, z=INT0):
|
|
305
|
+
'''Trilaterate this and two other circles, each given as a (2-D) center
|
|
306
|
+
and a radius.
|
|
307
|
+
|
|
308
|
+
@arg radius: Radius of this circle (same C{units} as this C{x} and C{y}.
|
|
309
|
+
@arg center2: Center of the 2nd circle (C{Cartesian}, L{Vector3d},
|
|
310
|
+
C{Vector2Tuple}, C{Vector3Tuple} or C{Vector4Tuple}).
|
|
311
|
+
@arg radius2: Radius of this circle (same C{units} as this C{x} and C{y}.
|
|
312
|
+
@arg center3: Center of the 3rd circle (C{Cartesian}, L{Vector3d},
|
|
313
|
+
C{Vector2Tuple}, C{Vector3Tuple} or C{Vector4Tuple}).
|
|
314
|
+
@arg radius3: Radius of the 3rd circle (same C{units} as this C{x} and C{y}.
|
|
315
|
+
@kwarg eps: Tolerance to check the trilaterated point I{delta} on all
|
|
316
|
+
3 circles (C{scalar}) or C{None} for no checking.
|
|
317
|
+
@kwarg z: Optional Z component of the trilaterated point (C{scalar}).
|
|
318
|
+
|
|
319
|
+
@return: Trilaterated point, an instance of this (sub-)class with C{z=B{z}}.
|
|
320
|
+
|
|
321
|
+
@raise IntersectionError: No intersection, near-concentric or -colinear
|
|
322
|
+
centers, trilateration failed some other way
|
|
323
|
+
or the trilaterated point is off one circle
|
|
324
|
+
by more than B{C{eps}}.
|
|
325
|
+
|
|
326
|
+
@raise TypeError: Invalid B{C{center2}} or B{C{center3}}.
|
|
327
|
+
|
|
328
|
+
@raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{radius3}}.
|
|
329
|
+
|
|
330
|
+
@see: Function L{pygeodesy.trilaterate2d2}.
|
|
331
|
+
'''
|
|
332
|
+
|
|
333
|
+
def _xyr3(r, **name_v):
|
|
334
|
+
v = _otherV3d(useZ=False, **name_v)
|
|
335
|
+
return v.x, v.y, r
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
return _MODS.vector2d._trilaterate2d2(*(_xyr3(radius, center=self) +
|
|
339
|
+
_xyr3(radius2, center2=center2) +
|
|
340
|
+
_xyr3(radius3, center3=center3)),
|
|
341
|
+
eps=eps, Vector=self.classof, z=z)
|
|
342
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
343
|
+
raise _xError(x, center=self, radius=radius,
|
|
344
|
+
center2=center2, radius2=radius2,
|
|
345
|
+
center3=center3, radius3=radius3)
|
|
346
|
+
|
|
347
|
+
def trilaterate3d2(self, radius, center2, radius2, center3, radius3, eps=EPS):
|
|
348
|
+
'''Trilaterate this and two other spheres, each given as a (3-D) center
|
|
349
|
+
and a radius.
|
|
350
|
+
|
|
351
|
+
@arg radius: Radius of this sphere (same C{units} as this C{x}, C{y}
|
|
352
|
+
and C{z}).
|
|
353
|
+
@arg center2: Center of the 2nd sphere (C{Cartesian}, L{Vector3d},
|
|
354
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
355
|
+
@arg radius2: Radius of this sphere (same C{units} as this C{x}, C{y}
|
|
356
|
+
and C{z}).
|
|
357
|
+
@arg center3: Center of the 3rd sphere (C{Cartesian}, , L{Vector3d},
|
|
358
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
359
|
+
@arg radius3: Radius of the 3rd sphere (same C{units} as this C{x}, C{y}
|
|
360
|
+
and C{z}).
|
|
361
|
+
@kwarg eps: Pertubation tolerance (C{scalar}), same units as C{x}, C{y}
|
|
362
|
+
and C{z} or C{None} for no pertubations.
|
|
363
|
+
|
|
364
|
+
@return: 2-Tuple with two trilaterated points, each an instance of this
|
|
365
|
+
(sub-)class. Both points are the same instance if all three
|
|
366
|
+
spheres intersect or abut in a single point.
|
|
367
|
+
|
|
368
|
+
@raise ImportError: Package C{numpy} not found, not installed or
|
|
369
|
+
older than version 1.10.
|
|
370
|
+
|
|
371
|
+
@raise IntersectionError: Near-concentric, -colinear, too distant or
|
|
372
|
+
non-intersecting spheres or C{numpy} issue.
|
|
373
|
+
|
|
374
|
+
@raise NumPyError: Some C{numpy} issue.
|
|
375
|
+
|
|
376
|
+
@raise TypeError: Invalid B{C{center2}} or B{C{center3}}.
|
|
377
|
+
|
|
378
|
+
@raise UnitError: Invalid B{C{radius}}, B{C{radius2}} or B{C{radius3}}.
|
|
379
|
+
|
|
380
|
+
@note: Package U{numpy<https://PyPI.org/project/numpy>} is required,
|
|
381
|
+
version 1.10 or later.
|
|
382
|
+
|
|
383
|
+
@see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration
|
|
384
|
+
Problem}<https://www.ResearchGate.net/publication/275027725>}
|
|
385
|
+
and U{I{implementation}<https://www.ResearchGate.net/publication/288825016>}.
|
|
386
|
+
'''
|
|
387
|
+
try:
|
|
388
|
+
c1 = _otherV3d(center=self, NN_OK=False)
|
|
389
|
+
return _MODS.vector2d._trilaterate3d2(c1, Radius_(radius, low=eps),
|
|
390
|
+
center2, radius2,
|
|
391
|
+
center3, radius3,
|
|
392
|
+
eps=eps, clas=self.classof)
|
|
393
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
394
|
+
raise _xError(x, center=self, radius=radius,
|
|
395
|
+
center2=center2, radius2=radius2,
|
|
396
|
+
center3=center3, radius3=radius3)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _intersect3d3(start1, end1, start2, end2, eps=EPS, useZ=False): # MCCABE 16 in .formy.intersection2, .rhumbBase
|
|
400
|
+
# (INTERNAL) Intersect two lines, see L{intersection3d3} below,
|
|
401
|
+
# separated to allow callers to embellish any exceptions
|
|
402
|
+
|
|
403
|
+
def _corners2(s1, b1, s2, useZ):
|
|
404
|
+
# Get the C{s1'} and C{e1'} corners of a right-angle
|
|
405
|
+
# triangle with the hypotenuse thru C{s1} at bearing
|
|
406
|
+
# C{b1} and the right angle at C{s2}
|
|
407
|
+
dx, dy, d = s2.minus(s1).xyz
|
|
408
|
+
if useZ and not isnear0(d): # not supported
|
|
409
|
+
raise IntersectionError(useZ=d, bearing=b1)
|
|
410
|
+
s, c = sincos2d(b1)
|
|
411
|
+
if s and c:
|
|
412
|
+
dx *= c / s
|
|
413
|
+
dy *= s / c
|
|
414
|
+
e1 = Vector3d(s2.x, s1.y + dx, s1.z)
|
|
415
|
+
s1 = Vector3d(s1.x + dy, s2.y, s1.z)
|
|
416
|
+
else: # orthogonal
|
|
417
|
+
d = euclid(dx, dy) # hypot?
|
|
418
|
+
e1 = Vector3d(s1.x + s * d, s1.y + c * d, s1.z)
|
|
419
|
+
return s1, e1
|
|
420
|
+
|
|
421
|
+
def _outside(t, d2, o): # -o before start#, +o after end#
|
|
422
|
+
return -o if t < 0 else (o if t > d2 else 0) # XXX d2 + eps?
|
|
423
|
+
|
|
424
|
+
s1 = t = _otherV3d(useZ=useZ, start1=start1)
|
|
425
|
+
s2 = _otherV3d(useZ=useZ, start2=start2)
|
|
426
|
+
b1 = _isDegrees(end1)
|
|
427
|
+
if b1: # bearing, make an e1
|
|
428
|
+
s1, e1 = _corners2(s1, end1, s2, useZ)
|
|
429
|
+
else:
|
|
430
|
+
e1 = _otherV3d(useZ=useZ, end1=end1)
|
|
431
|
+
b2 = _isDegrees(end2)
|
|
432
|
+
if b2: # bearing, make an e2
|
|
433
|
+
s2, e2 = _corners2(s2, end2, t, useZ)
|
|
434
|
+
else:
|
|
435
|
+
e2 = _otherV3d(useZ=useZ, end2=end2)
|
|
436
|
+
|
|
437
|
+
a = e1.minus(s1)
|
|
438
|
+
b = e2.minus(s2)
|
|
439
|
+
c = s2.minus(s1)
|
|
440
|
+
|
|
441
|
+
ab = a.cross(b)
|
|
442
|
+
d = fabs(c.dot(ab))
|
|
443
|
+
e = max(EPS0, eps or _0_0)
|
|
444
|
+
if d > EPS0 and ab.length > e: # PYCHOK no cover
|
|
445
|
+
d = d / ab.length # /= chokes PyChecker
|
|
446
|
+
if d > e: # argonic, skew lines distance
|
|
447
|
+
raise IntersectionError(skew_d=d, txt=_no_(_intersection_))
|
|
448
|
+
|
|
449
|
+
# co-planar, non-skew lines
|
|
450
|
+
ab2 = ab.length2
|
|
451
|
+
if ab2 < e: # colinear, parallel or null line(s)
|
|
452
|
+
x = a.length2 > b.length2
|
|
453
|
+
if x: # make C{a} the shortest
|
|
454
|
+
a, b = b, a
|
|
455
|
+
s1, s2 = s2, s1
|
|
456
|
+
e1, e2 = e2, e1
|
|
457
|
+
b1, b2 = b2, b1
|
|
458
|
+
if b.length2 < e: # PYCHOK no cover
|
|
459
|
+
if c.length < e:
|
|
460
|
+
return s1, 0, 0
|
|
461
|
+
elif e2.minus(e1).length < e:
|
|
462
|
+
return e1, 0, 0
|
|
463
|
+
elif a.length2 < e: # null (s1, e1), non-null (s2, e2)
|
|
464
|
+
# like _nearestOn2(s1, s2, e2, within=False, eps=e)
|
|
465
|
+
t = s1.minus(s2).dot(b)
|
|
466
|
+
v = s2.plus(b.times(t / b.length2))
|
|
467
|
+
if s1.minus(v).length < e:
|
|
468
|
+
o = 0 if b2 else _outside(t, b.length2, 1 if x else 2)
|
|
469
|
+
return (v, o, 0) if x else (v, 0, o)
|
|
470
|
+
raise IntersectionError(length2=ab2, txt=_no_(_intersection_))
|
|
471
|
+
|
|
472
|
+
cb = c.cross(b)
|
|
473
|
+
t = cb.dot(ab)
|
|
474
|
+
o1 = 0 if b1 else _outside(t, ab2, 1)
|
|
475
|
+
v = s1.plus(a.times(t / ab2))
|
|
476
|
+
o2 = 0 if b2 else _outside(v.minus(s2).dot(b), b.length2, 2)
|
|
477
|
+
return v, o1, o2
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def intersection3d3(start1, end1, start2, end2, eps=EPS, useZ=True,
|
|
481
|
+
**Vector_and_kwds):
|
|
482
|
+
'''Compute the intersection point of two (2- or 3-D) lines, each defined
|
|
483
|
+
by two points or by a point and a bearing.
|
|
484
|
+
|
|
485
|
+
@arg start1: Start point of the first line (C{Cartesian}, L{Vector3d},
|
|
486
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
487
|
+
@arg end1: End point of the first line (C{Cartesian}, L{Vector3d},
|
|
488
|
+
C{Vector3Tuple} or C{Vector4Tuple}) or the bearing at
|
|
489
|
+
B{C{start1}} (compass C{degrees}).
|
|
490
|
+
@arg start2: Start point of the second line (C{Cartesian}, L{Vector3d},
|
|
491
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
492
|
+
@arg end2: End point of the second line (C{Cartesian}, L{Vector3d},
|
|
493
|
+
C{Vector3Tuple} or C{Vector4Tuple}) or the bearing at
|
|
494
|
+
B{C{start2}} (Ccompass C{degrees}).
|
|
495
|
+
@kwarg eps: Tolerance for skew line distance and length (C{EPS}).
|
|
496
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
497
|
+
@kwarg Vector_and_kwds: Optional class C{B{Vector}=None} to return the
|
|
498
|
+
intersection points and optional, additional B{C{Vector}}
|
|
499
|
+
keyword arguments, otherwise B{C{start1}}'s (sub-)class.
|
|
500
|
+
|
|
501
|
+
@return: An L{Intersection3Tuple}C{(point, outside1, outside2)} with
|
|
502
|
+
C{point} an instance of B{C{Vector}} or B{C{start1}}'s (sub-)class.
|
|
503
|
+
|
|
504
|
+
@note: The C{outside} values is C{0} for lines specified by point and bearing.
|
|
505
|
+
|
|
506
|
+
@raise IntersectionError: Invalid, skew, non-co-planar or otherwise
|
|
507
|
+
non-intersecting lines.
|
|
508
|
+
|
|
509
|
+
@see: U{Line-line intersection<https://MathWorld.Wolfram.com/Line-LineIntersection.html>}
|
|
510
|
+
and U{line-line distance<https://MathWorld.Wolfram.com/Line-LineDistance.html>},
|
|
511
|
+
U{skew lines<https://MathWorld.Wolfram.com/SkewLines.html>} and U{point-line
|
|
512
|
+
distance<https://MathWorld.Wolfram.com/Point-LineDistance3-Dimensional.html>}.
|
|
513
|
+
'''
|
|
514
|
+
try:
|
|
515
|
+
v, o1, o2 = _intersect3d3(start1, end1, start2, end2, eps=eps, useZ=useZ)
|
|
516
|
+
except (TypeError, ValueError) as x:
|
|
517
|
+
raise _xError(x, start1=start1, end1=end1, start2=start2, end2=end2)
|
|
518
|
+
v = _nVc(v, **_xkwds(Vector_and_kwds, clas=start1.classof,
|
|
519
|
+
name=intersection3d3.__name__))
|
|
520
|
+
return Intersection3Tuple(v, o1, o2)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def intersections2(center1, radius1, center2, radius2, sphere=True, **Vector_and_kwds):
|
|
524
|
+
'''Compute the intersection of two spheres or circles, each defined by a (3-D)
|
|
525
|
+
center point and a radius.
|
|
526
|
+
|
|
527
|
+
@arg center1: Center of the first sphere or circle (C{Cartesian}, L{Vector3d},
|
|
528
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
529
|
+
@arg radius1: Radius of the first sphere or circle (same units as the
|
|
530
|
+
B{C{center1}} coordinates).
|
|
531
|
+
@arg center2: Center of the second sphere or circle (C{Cartesian}, L{Vector3d},
|
|
532
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
533
|
+
@arg radius2: Radius of the second sphere or circle (same units as the
|
|
534
|
+
B{C{center1}} and B{C{center2}} coordinates).
|
|
535
|
+
@kwarg sphere: If C{True} compute the center and radius of the intersection of
|
|
536
|
+
two spheres. If C{False}, ignore the C{z}-component and compute
|
|
537
|
+
the intersection of two circles (C{bool}).
|
|
538
|
+
@kwarg Vector_and_kwds: Optional class C{B{Vector}=None} to return the
|
|
539
|
+
intersection points and optional, additional B{C{Vector}}
|
|
540
|
+
keyword arguments, otherwise B{C{center1}}'s (sub-)class.
|
|
541
|
+
|
|
542
|
+
@return: If B{C{sphere}} is C{True}, a 2-tuple of the C{center} and C{radius}
|
|
543
|
+
of the intersection of the I{spheres}. The C{radius} is C{0.0} for
|
|
544
|
+
abutting spheres (and the C{center} is aka the I{radical center}).
|
|
545
|
+
|
|
546
|
+
If B{C{sphere}} is C{False}, a 2-tuple with the two intersection
|
|
547
|
+
points of the I{circles}. For abutting circles, both points are
|
|
548
|
+
the same instance, aka the I{radical center}.
|
|
549
|
+
|
|
550
|
+
@raise IntersectionError: Concentric, invalid or non-intersecting spheres
|
|
551
|
+
or circles.
|
|
552
|
+
|
|
553
|
+
@raise TypeError: Invalid B{C{center1}} or B{C{center2}}.
|
|
554
|
+
|
|
555
|
+
@raise UnitError: Invalid B{C{radius1}} or B{C{radius2}}.
|
|
556
|
+
|
|
557
|
+
@see: U{Sphere-Sphere<https://MathWorld.Wolfram.com/Sphere-SphereIntersection.html>} and
|
|
558
|
+
U{Circle-Circle<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>}
|
|
559
|
+
Intersection.
|
|
560
|
+
'''
|
|
561
|
+
try:
|
|
562
|
+
return _intersects2(center1, Radius_(radius1=radius1),
|
|
563
|
+
center2, Radius_(radius2=radius2), sphere=sphere,
|
|
564
|
+
clas=center1.classof, **Vector_and_kwds)
|
|
565
|
+
except (TypeError, ValueError) as x:
|
|
566
|
+
raise _xError(x, center1=center1, radius1=radius1, center2=center2, radius2=radius2)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _intersects2(center1, r1, center2, r2, sphere=True, too_d=None, # in CartesianEllipsoidalBase.intersections2,
|
|
570
|
+
**clas_Vector_and_kwds): # .ellipsoidalBaseDI._intersections2, .formy.intersections2
|
|
571
|
+
# (INTERNAL) Intersect two spheres or circles, see L{intersections2}
|
|
572
|
+
# above, separated to allow callers to embellish any exceptions
|
|
573
|
+
|
|
574
|
+
def _nV3(x, y, z):
|
|
575
|
+
v = Vector3d(x, y, z)
|
|
576
|
+
n = intersections2.__name__
|
|
577
|
+
return _nVc(v, **_xkwds(clas_Vector_and_kwds, name=n))
|
|
578
|
+
|
|
579
|
+
def _xV3(c1, u, x, y):
|
|
580
|
+
xy1 = x, y, _1_0 # transform to original space
|
|
581
|
+
return _nV3(fdot(xy1, u.x, -u.y, c1.x),
|
|
582
|
+
fdot(xy1, u.y, u.x, c1.y), _0_0)
|
|
583
|
+
|
|
584
|
+
c1 = _otherV3d(useZ=sphere, center1=center1)
|
|
585
|
+
c2 = _otherV3d(useZ=sphere, center2=center2)
|
|
586
|
+
|
|
587
|
+
if r1 < r2: # r1, r2 == R, r
|
|
588
|
+
c1, c2 = c2, c1
|
|
589
|
+
r1, r2 = r2, r1
|
|
590
|
+
|
|
591
|
+
m = c2.minus(c1)
|
|
592
|
+
d = m.length
|
|
593
|
+
if d < max(r2 - r1, EPS):
|
|
594
|
+
raise IntersectionError(_near_(_concentric_)) # XXX ConcentricError?
|
|
595
|
+
|
|
596
|
+
o = fsum1_(-d, r1, r2) # overlap == -(d - (r1 + r2))
|
|
597
|
+
# compute intersections with c1 at (0, 0) and c2 at (d, 0), like
|
|
598
|
+
# <https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>
|
|
599
|
+
if o > EPS: # overlapping, r1, r2 == R, r
|
|
600
|
+
x = _MODS.formy._radical2(d, r1, r2).xline
|
|
601
|
+
y = _1_0 - (x / r1)**2
|
|
602
|
+
if y > EPS:
|
|
603
|
+
y = r1 * sqrt(y) # y == a / 2
|
|
604
|
+
elif y < 0: # PYCHOK no cover
|
|
605
|
+
raise IntersectionError(_negative_)
|
|
606
|
+
else: # abutting
|
|
607
|
+
y = _0_0
|
|
608
|
+
elif o < 0: # PYCHOK no cover
|
|
609
|
+
if too_d is not None:
|
|
610
|
+
d = too_d
|
|
611
|
+
raise IntersectionError(_too_(Fmt.distant(d)))
|
|
612
|
+
else: # abutting
|
|
613
|
+
x, y = r1, _0_0
|
|
614
|
+
|
|
615
|
+
u = m.unit()
|
|
616
|
+
if sphere: # sphere center and radius
|
|
617
|
+
c = c1 if x < EPS else (
|
|
618
|
+
c2 if x > EPS1 else c1.plus(u.times(x)))
|
|
619
|
+
t = _nV3(c.x, c.y, c.z), Radius(y)
|
|
620
|
+
|
|
621
|
+
elif y > 0: # intersecting circles
|
|
622
|
+
t = _xV3(c1, u, x, y), _xV3(c1, u, x, -y)
|
|
623
|
+
else: # abutting circles
|
|
624
|
+
t = _xV3(c1, u, x, 0)
|
|
625
|
+
t = t, t
|
|
626
|
+
return t
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def iscolinearWith(point, point1, point2, eps=EPS, useZ=True):
|
|
630
|
+
'''Check whether a point is colinear with two other (2- or 3-D) points.
|
|
631
|
+
|
|
632
|
+
@arg point: The point (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}).
|
|
633
|
+
@arg point1: First point (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}).
|
|
634
|
+
@arg point2: Second point (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}).
|
|
635
|
+
@kwarg eps: Tolerance (C{scalar}), same units as C{x}, C{y} and C{z}.
|
|
636
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
637
|
+
|
|
638
|
+
@return: C{True} if B{C{point}} is colinear B{C{point1}} and B{C{point2}},
|
|
639
|
+
C{False} otherwise.
|
|
640
|
+
|
|
641
|
+
@raise TypeError: Invalid B{C{point}}, B{C{point1}} or B{C{point2}}.
|
|
642
|
+
|
|
643
|
+
@see: Function L{nearestOn}.
|
|
644
|
+
'''
|
|
645
|
+
p = _otherV3d(useZ=useZ, point=point)
|
|
646
|
+
return _MODS.vector2d._iscolinearWith(p, point1, point2, eps=eps, useZ=useZ)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def nearestOn(point, point1, point2, within=True, useZ=True, Vector=None, **Vector_kwds):
|
|
650
|
+
'''Locate the point between two points closest to a reference (2- or 3-D).
|
|
651
|
+
|
|
652
|
+
@arg point: Reference point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}
|
|
653
|
+
or C{Vector4Tuple}).
|
|
654
|
+
@arg point1: Start point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
655
|
+
C{Vector4Tuple}).
|
|
656
|
+
@arg point2: End point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
657
|
+
C{Vector4Tuple}).
|
|
658
|
+
@kwarg within: If C{True} return the closest point between both given
|
|
659
|
+
points, otherwise the closest point on the extended line
|
|
660
|
+
through both points (C{bool}).
|
|
661
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
662
|
+
@kwarg Vector: Class to return closest point (C{Cartesian}, L{Vector3d}
|
|
663
|
+
or C{Vector3Tuple}) or C{None}.
|
|
664
|
+
@kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments,
|
|
665
|
+
ignored if C{B{Vector} is None}.
|
|
666
|
+
|
|
667
|
+
@return: Closest point, either B{C{point1}} or B{C{point2}} or an instance
|
|
668
|
+
of the B{C{point}}'s (sub-)class or B{C{Vector}} if not C{None}.
|
|
669
|
+
|
|
670
|
+
@raise TypeError: Invalid B{C{point}}, B{C{point1}} or B{C{point2}}.
|
|
671
|
+
|
|
672
|
+
@see: U{3-D Point-Line Distance<https://MathWorld.Wolfram.com/Point-LineDistance3-Dimensional.html>},
|
|
673
|
+
C{Cartesian} and C{LatLon} methods C{nearestOn}, method L{sphericalTrigonometry.LatLon.nearestOn3}
|
|
674
|
+
and function L{sphericalTrigonometry.nearestOn3}.
|
|
675
|
+
'''
|
|
676
|
+
p0 = _otherV3d(useZ=useZ, point =point)
|
|
677
|
+
p1 = _otherV3d(useZ=useZ, point1=point1)
|
|
678
|
+
p2 = _otherV3d(useZ=useZ, point2=point2)
|
|
679
|
+
|
|
680
|
+
n = nearestOn.__name__
|
|
681
|
+
p, _ = _nearestOn2(p0, p1, p2, within=within)
|
|
682
|
+
if Vector is not None:
|
|
683
|
+
p = Vector(p.x, p.y, **_xkwds(Vector_kwds, z=p.z, name=n))
|
|
684
|
+
elif p is p1:
|
|
685
|
+
p = point1
|
|
686
|
+
elif p is p2:
|
|
687
|
+
p = point2
|
|
688
|
+
else: # ignore Vector_kwds
|
|
689
|
+
p = point.classof(p.x, p.y, _xkwds_get(Vector_kwds, z=p.z), name=n)
|
|
690
|
+
return p
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def _nearestOn2(p0, p1, p2, within=True, eps=EPS):
|
|
694
|
+
# (INTERNAL) Closest point and fraction, see L{nearestOn} above,
|
|
695
|
+
# separated to allow callers to embellish any exceptions
|
|
696
|
+
p21 = p2.minus(p1)
|
|
697
|
+
d2 = p21.length2
|
|
698
|
+
if d2 < eps: # coincident
|
|
699
|
+
p = p1 # ~= p2
|
|
700
|
+
t = 0
|
|
701
|
+
else: # see comments in .points.nearestOn5
|
|
702
|
+
t = p0.minus(p1).dot(p21) / d2
|
|
703
|
+
if within and t < eps:
|
|
704
|
+
p = p1
|
|
705
|
+
t = 0
|
|
706
|
+
elif within and t > (_1_0 - eps):
|
|
707
|
+
p = p2
|
|
708
|
+
t = 1
|
|
709
|
+
else:
|
|
710
|
+
p = p1.plus(p21.times(t))
|
|
711
|
+
return NearestOn2Tuple(p, t)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def nearestOn6(point, points, closed=False, useZ=True, **Vector_and_kwds): # eps=EPS
|
|
715
|
+
'''Locate the point on a path or polygon closest to a reference point.
|
|
716
|
+
|
|
717
|
+
The closest point on each polygon edge is either the nearest of that
|
|
718
|
+
edge's end points or a point in between.
|
|
719
|
+
|
|
720
|
+
@arg point: Reference point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple} or
|
|
721
|
+
C{Vector4Tuple}).
|
|
722
|
+
@arg points: The path or polygon points (C{Cartesian}, L{Vector3d},
|
|
723
|
+
C{Vector3Tuple} or C{Vector4Tuple}[]).
|
|
724
|
+
@kwarg closed: Optionally, close the path or polygon (C{bool}).
|
|
725
|
+
@kwarg useZ: If C{True}, use the Z components, otherwise force C{z=INT0} (C{bool}).
|
|
726
|
+
@kwarg Vector_and_kwds: Optional class C{B{Vector}=None} to return the closest
|
|
727
|
+
point and optional, additional B{C{Vector}} keyword
|
|
728
|
+
arguments, otherwise B{C{point}}'s (sub-)class.
|
|
729
|
+
|
|
730
|
+
@return: A L{NearestOn6Tuple}C{(closest, distance, fi, j, start, end)} with the
|
|
731
|
+
C{closest}, the C{start} and the C{end} point each an instance of the
|
|
732
|
+
B{C{Vector}} keyword argument of if {B{Vector}=None} or not specified,
|
|
733
|
+
an instance of the reference B{C{point}}'s (sub-)class.
|
|
734
|
+
|
|
735
|
+
@raise PointsError: Insufficient number of B{C{points}}
|
|
736
|
+
|
|
737
|
+
@raise TypeError: Non-cartesian B{C{point}} and B{C{points}}.
|
|
738
|
+
|
|
739
|
+
@note: Distances measured with method L{Vector3d.equirectangular}. For
|
|
740
|
+
geodetic distances use function L{nearestOn5} or one of the
|
|
741
|
+
C{LatLon.nearestOn6} methods.
|
|
742
|
+
'''
|
|
743
|
+
r = _otherV3d(useZ=useZ, point=point)
|
|
744
|
+
D2 = r.equirectangular # distance squared
|
|
745
|
+
|
|
746
|
+
Ps = PointsIter(points, loop=1, name=nearestOn6.__name__)
|
|
747
|
+
p1 = c = s = e = _otherV3d(useZ=useZ, i=0, points=Ps[0])
|
|
748
|
+
c2 = D2(c) # == r.minus(c).length2
|
|
749
|
+
|
|
750
|
+
f = i = 0 # p1..p2 == points[i]..[j]
|
|
751
|
+
for j, p2 in Ps.enumerate(closed=closed):
|
|
752
|
+
p2 = _otherV3d(useZ=useZ, i=j, points=p2)
|
|
753
|
+
p, t = _nearestOn2(r, p1, p2) # within=True, eps=EPS
|
|
754
|
+
d2 = D2(p) # == r.minus(p).length2
|
|
755
|
+
if d2 < c2:
|
|
756
|
+
c2, c, s, e, f = d2, p, p1, p2, (i + t)
|
|
757
|
+
p1, i = p2, j
|
|
758
|
+
|
|
759
|
+
f, j = _fi_j2(f, len(Ps)) # like .ellipsoidalBaseDI._nearestOn2_
|
|
760
|
+
|
|
761
|
+
kwds = _xkwds(Vector_and_kwds, clas=point.classof, name=Ps.name)
|
|
762
|
+
v = _nVc(c, **kwds)
|
|
763
|
+
s = _nVc(s, **kwds) if s is not c else v
|
|
764
|
+
e = _nVc(e, **kwds) if e is not c else v
|
|
765
|
+
return NearestOn6Tuple(v, sqrt(c2), f, j, s, e)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _nVc(v, clas=None, name=NN, Vector=None, **Vector_kwds): # in .vector2d
|
|
769
|
+
# return a named C{Vector} or C{clas} instance
|
|
770
|
+
if Vector is not None:
|
|
771
|
+
v = Vector(v.x, v.y, v.z, **Vector_kwds)
|
|
772
|
+
elif clas is not None:
|
|
773
|
+
v = clas(v.x, v.y, v.z) # ignore Vector_kwds
|
|
774
|
+
return _xnamed(v, name) if name else v
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def _otherV3d(useZ=True, NN_OK=True, i=None, **name_v):
|
|
778
|
+
# check named vector instance, return Vector3d
|
|
779
|
+
n, v = _xkwds_item2(name_v)
|
|
780
|
+
if useZ and isinstance(v, Vector3dBase):
|
|
781
|
+
return v if NN_OK or v.name else v.copy(name=Fmt.INDEX(n, i))
|
|
782
|
+
|
|
783
|
+
n = Fmt.INDEX(n, i)
|
|
784
|
+
try:
|
|
785
|
+
return Vector3d(v.x, v.y, (v.z if useZ else INT0), name=n)
|
|
786
|
+
except AttributeError: # no .x, .y or .z attr
|
|
787
|
+
pass
|
|
788
|
+
raise _xotherError(Vector3d(0, 0, 0), v, name=n, up=2)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def parse3d(str3d, sep=_COMMA_, Vector=Vector3d, **Vector_kwds):
|
|
792
|
+
'''Parse an C{"x, y, z"} string.
|
|
793
|
+
|
|
794
|
+
@arg str3d: X, y and z values (C{str}).
|
|
795
|
+
@kwarg sep: Optional separator (C{str}).
|
|
796
|
+
@kwarg Vector: Optional class (L{Vector3d}).
|
|
797
|
+
@kwarg Vector_kwds: Optional B{C{Vector}} keyword arguments,
|
|
798
|
+
ignored if C{B{Vector} is None}.
|
|
799
|
+
|
|
800
|
+
@return: A B{C{Vector}} instance or if B{C{Vector}} is C{None},
|
|
801
|
+
a named L{Vector3Tuple}C{(x, y, z)}.
|
|
802
|
+
|
|
803
|
+
@raise VectorError: Invalid B{C{str3d}}.
|
|
804
|
+
'''
|
|
805
|
+
try:
|
|
806
|
+
v = [float(v.strip()) for v in str3d.split(sep)]
|
|
807
|
+
n = len(v)
|
|
808
|
+
if n != 3:
|
|
809
|
+
raise _ValueError(len=n)
|
|
810
|
+
except (TypeError, ValueError) as x:
|
|
811
|
+
raise VectorError(str3d=str3d, cause=x)
|
|
812
|
+
return _xnamed((Vector3Tuple(v) if Vector is None else # *v
|
|
813
|
+
Vector(*v, **Vector_kwds)), parse3d.__name__)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def sumOf(vectors, Vector=Vector3d, **Vector_kwds):
|
|
817
|
+
'''Compute the I{vectorial} sum of two oe more vectors.
|
|
818
|
+
|
|
819
|
+
@arg vectors: Vectors to be added (L{Vector3d}[]).
|
|
820
|
+
@kwarg Vector: Optional class for the vectorial sum (L{Vector3d}).
|
|
821
|
+
@kwarg Vector_kwds: Optional B{C{Vector}} keyword arguments,
|
|
822
|
+
ignored if C{B{Vector} is None}.
|
|
823
|
+
|
|
824
|
+
@return: Vectorial sum as B{C{Vector}} or if B{C{Vector}} is
|
|
825
|
+
C{None}, a named L{Vector3Tuple}C{(x, y, z)}.
|
|
826
|
+
|
|
827
|
+
@raise VectorError: No B{C{vectors}}.
|
|
828
|
+
'''
|
|
829
|
+
try:
|
|
830
|
+
t = _MODS.nvectorBase._nsumOf(vectors, 0, None, {}) # no H
|
|
831
|
+
except (TypeError, ValueError) as x:
|
|
832
|
+
raise VectorError(vectors=vectors, Vector=Vector, cause=x)
|
|
833
|
+
x, y, z = t[:3]
|
|
834
|
+
n = sumOf.__name__
|
|
835
|
+
return Vector3Tuple(x, y, z, name=n) if Vector is None else \
|
|
836
|
+
Vector(x, y, z, **_xkwds(Vector_kwds, name=n))
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def trilaterate2d2(x1, y1, radius1, x2, y2, radius2, x3, y3, radius3,
|
|
840
|
+
eps=None, **Vector_and_kwds):
|
|
841
|
+
'''Trilaterate three circles, each given as a (2-D) center and a radius.
|
|
842
|
+
|
|
843
|
+
@arg x1: Center C{x} coordinate of the 1st circle (C{scalar}).
|
|
844
|
+
@arg y1: Center C{y} coordinate of the 1st circle (C{scalar}).
|
|
845
|
+
@arg radius1: Radius of the 1st circle (C{scalar}).
|
|
846
|
+
@arg x2: Center C{x} coordinate of the 2nd circle (C{scalar}).
|
|
847
|
+
@arg y2: Center C{y} coordinate of the 2nd circle (C{scalar}).
|
|
848
|
+
@arg radius2: Radius of the 2nd circle (C{scalar}).
|
|
849
|
+
@arg x3: Center C{x} coordinate of the 3rd circle (C{scalar}).
|
|
850
|
+
@arg y3: Center C{y} coordinate of the 3rd circle (C{scalar}).
|
|
851
|
+
@arg radius3: Radius of the 3rd circle (C{scalar}).
|
|
852
|
+
@kwarg eps: Tolerance to check the trilaterated point I{delta} on all
|
|
853
|
+
3 circles (C{scalar}) or C{None} for no checking.
|
|
854
|
+
@kwarg Vector_and_kwds: Optional class C{B{Vector}=None} to return the
|
|
855
|
+
trilateration and optional, additional B{C{Vector}}
|
|
856
|
+
keyword arguments, otherwise (L{Vector3d}).
|
|
857
|
+
|
|
858
|
+
@return: Trilaterated point as C{B{Vector}(x, y, **B{Vector_kwds})}
|
|
859
|
+
or L{Vector2Tuple}C{(x, y)} if C{B{Vector} is None}..
|
|
860
|
+
|
|
861
|
+
@raise IntersectionError: No intersection, near-concentric or -colinear
|
|
862
|
+
centers, trilateration failed some other way
|
|
863
|
+
or the trilaterated point is off one circle
|
|
864
|
+
by more than B{C{eps}}.
|
|
865
|
+
|
|
866
|
+
@raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{radius3}}.
|
|
867
|
+
|
|
868
|
+
@see: U{Issue #49<https://GitHub.com/mrJean1/PyGeodesy/issues/49>},
|
|
869
|
+
U{Find X location using 3 known (X,Y) location using trilateration
|
|
870
|
+
<https://math.StackExchange.com/questions/884807>} and function
|
|
871
|
+
L{pygeodesy.trilaterate3d2}.
|
|
872
|
+
'''
|
|
873
|
+
return _MODS.vector2d._trilaterate2d2(x1, y1, radius1,
|
|
874
|
+
x2, y2, radius2,
|
|
875
|
+
x3, y3, radius3, eps=eps, **Vector_and_kwds)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def trilaterate3d2(center1, radius1, center2, radius2, center3, radius3,
|
|
879
|
+
eps=EPS, **Vector_and_kwds):
|
|
880
|
+
'''Trilaterate three spheres, each given as a (3-D) center and a radius.
|
|
881
|
+
|
|
882
|
+
@arg center1: Center of the 1st sphere (C{Cartesian}, L{Vector3d},
|
|
883
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
884
|
+
@arg radius1: Radius of the 1st sphere (same C{units} as C{x}, C{y}
|
|
885
|
+
and C{z}).
|
|
886
|
+
@arg center2: Center of the 2nd sphere (C{Cartesian}, L{Vector3d},
|
|
887
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
888
|
+
@arg radius2: Radius of this sphere (same C{units} as C{x}, C{y}
|
|
889
|
+
and C{z}).
|
|
890
|
+
@arg center3: Center of the 3rd sphere (C{Cartesian}, L{Vector3d},
|
|
891
|
+
C{Vector3Tuple} or C{Vector4Tuple}).
|
|
892
|
+
@arg radius3: Radius of the 3rd sphere (same C{units} as C{x}, C{y}
|
|
893
|
+
and C{z}).
|
|
894
|
+
@kwarg eps: Pertubation tolerance (C{scalar}), same units as C{x},
|
|
895
|
+
C{y} and C{z} or C{None} for no pertubations.
|
|
896
|
+
@kwarg Vector_and_kwds: Optional class C{B{Vector}=None} to return the
|
|
897
|
+
trilateration and optional, additional B{C{Vector}}
|
|
898
|
+
keyword arguments, otherwise B{C{center1}}'s
|
|
899
|
+
(sub-)class.
|
|
900
|
+
|
|
901
|
+
@return: 2-Tuple with two trilaterated points, each a B{C{Vector}}
|
|
902
|
+
instance. Both points are the same instance if all three
|
|
903
|
+
spheres abut/intersect in a single point.
|
|
904
|
+
|
|
905
|
+
@raise ImportError: Package C{numpy} not found, not installed or
|
|
906
|
+
older than version 1.10.
|
|
907
|
+
|
|
908
|
+
@raise IntersectionError: Near-concentric, -colinear, too distant or
|
|
909
|
+
non-intersecting spheres.
|
|
910
|
+
|
|
911
|
+
@raise NumPyError: Some C{numpy} issue.
|
|
912
|
+
|
|
913
|
+
@raise TypeError: Invalid B{C{center1}}, B{C{center2}} or B{C{center3}}.
|
|
914
|
+
|
|
915
|
+
@raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{radius3}}.
|
|
916
|
+
|
|
917
|
+
@see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration
|
|
918
|
+
Problem}<https://www.ResearchGate.net/publication/275027725>},
|
|
919
|
+
the U{I{implementation}<https://www.ResearchGate.net/publication/
|
|
920
|
+
288825016>} and function L{pygeodesy.trilaterate2d2}.
|
|
921
|
+
'''
|
|
922
|
+
try:
|
|
923
|
+
return _MODS.vector2d._trilaterate3d2(_otherV3d(center1=center1, NN_OK=False),
|
|
924
|
+
Radius_(radius1=radius1, low=eps),
|
|
925
|
+
center2, radius2, center3, radius3, eps=eps,
|
|
926
|
+
clas=center1.classof, **Vector_and_kwds)
|
|
927
|
+
except (AssertionError, TypeError, ValueError) as x:
|
|
928
|
+
raise _xError(x, center1=center1, radius1=radius1,
|
|
929
|
+
center2=center2, radius2=radius2,
|
|
930
|
+
center3=center3, radius3=radius3)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _xyzhdn3(xyz, height, datum, ll): # in .cartesianBase, .nvectorBase
|
|
934
|
+
'''(INTERNAL) Get a C{(h, d, name)} 3-tuple.
|
|
935
|
+
'''
|
|
936
|
+
h = height or _xattr(xyz, height=None) \
|
|
937
|
+
or _xattr(xyz, h=None) \
|
|
938
|
+
or _xattr(ll, height=None)
|
|
939
|
+
|
|
940
|
+
d = datum or _xattr(xyz, datum=None) \
|
|
941
|
+
or _xattr(ll, datum=None)
|
|
942
|
+
|
|
943
|
+
return h, d, _xattr(xyz, name=NN)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
__all__ += _ALL_DOCS(intersections2, sumOf, Vector3dBase)
|
|
947
|
+
|
|
948
|
+
# **) MIT License
|
|
949
|
+
#
|
|
950
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
951
|
+
#
|
|
952
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
953
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
954
|
+
# to deal in the Software without restriction, including without limitation
|
|
955
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
956
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
957
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
958
|
+
#
|
|
959
|
+
# The above copyright notice and this permission notice shall be included
|
|
960
|
+
# in all copies or substantial portions of the Software.
|
|
961
|
+
#
|
|
962
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
963
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
964
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
965
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
966
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
967
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
968
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|