pygeodesy 24.6.24__py2.py3-none-any.whl → 24.7.7__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.6.24.dist-info → PyGeodesy-24.7.7.dist-info}/METADATA +2 -2
- {PyGeodesy-24.6.24.dist-info → PyGeodesy-24.7.7.dist-info}/RECORD +23 -23
- pygeodesy/__init__.py +7 -1
- pygeodesy/__main__.py +6 -1
- pygeodesy/basics.py +8 -4
- pygeodesy/deprecated/__init__.py +1 -1
- pygeodesy/deprecated/classes.py +10 -3
- pygeodesy/errors.py +10 -1
- pygeodesy/geodesici.py +1111 -324
- pygeodesy/geodesicw.py +5 -3
- pygeodesy/geodesicx/__init__.py +1 -1
- pygeodesy/geodesicx/gx.py +19 -30
- pygeodesy/geodesicx/gxline.py +83 -72
- pygeodesy/geodsolve.py +36 -57
- pygeodesy/internals.py +40 -13
- pygeodesy/karney.py +63 -16
- pygeodesy/lazily.py +23 -21
- pygeodesy/ltpTuples.py +4 -4
- pygeodesy/rhumb/solve.py +21 -22
- pygeodesy/solveBase.py +177 -123
- pygeodesy/units.py +5 -5
- {PyGeodesy-24.6.24.dist-info → PyGeodesy-24.7.7.dist-info}/WHEEL +0 -0
- {PyGeodesy-24.6.24.dist-info → PyGeodesy-24.7.7.dist-info}/top_level.txt +0 -0
pygeodesy/geodesici.py
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
-
u'''
|
|
5
|
-
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Intersect.html>}
|
|
6
|
-
geodesic lines.
|
|
4
|
+
u'''Classes L{Intersectool} and L{Intersector}, a pure Python version of I{Karney}'s C++ class
|
|
5
|
+
U{Intersect<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Intersect.html>}
|
|
6
|
+
to find the intersections of two geodesic lines or line segments.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
L{Intersector.All}, L{Intersector.Closest} and L{Intersector.Next} producing 4-item L{XDist}s.
|
|
8
|
+
L{Intersectool} and L{Intersector} methods C{All}, C{Closest}, C{Next} and C{Segment} produce
|
|
10
9
|
|
|
11
|
-
Adjacent methods
|
|
12
|
-
|
|
13
|
-
each intersection as
|
|
10
|
+
L{XDict} instances with 4 or more items. Adjacent methods C{All5}, C{Closest5}, C{Next5} and
|
|
11
|
+
C{Segment} return or yield L{Intersectool5Tuple} or L{Intersector5Tuple}s with the lat-, longitude
|
|
12
|
+
azimuth of each intersection as an extended or C{Position}-like L{GDict}.
|
|
13
|
+
|
|
14
|
+
Class L{Intersectool} is a wrapper to invoke I{Karney}'s U{IntersectTool
|
|
15
|
+
<https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>} utility like class L{Intersector},
|
|
16
|
+
but intended I{for testing purposes only}.
|
|
17
|
+
|
|
18
|
+
Set env variable C{PYGEODESY_INTERSECTTOOL} to the (fully qualified) path of the C{IntersectTool} executable.
|
|
14
19
|
|
|
15
20
|
For more details, see the C++ U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/index.html>}
|
|
16
21
|
documentation, I{Charles F.F. Karney}'s paper U{Geodesics intersections<https://arxiv.org/abs/2308.00495>}
|
|
@@ -22,58 +27,78 @@ from __future__ import division as _; del _ # PYCHOK semicolon
|
|
|
22
27
|
|
|
23
28
|
from pygeodesy.basics import _copy, _enumereverse, map1, \
|
|
24
29
|
_xinstanceof, _xor
|
|
25
|
-
from pygeodesy.constants import EPS, INF, INT0, PI, PI2, PI_4,
|
|
26
|
-
_0_5, _1_0, _1_5, _2_0, _3_0,
|
|
27
|
-
|
|
28
|
-
from pygeodesy.
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
from pygeodesy.constants import EPS, INF, INT0, PI, PI2, PI_4, \
|
|
31
|
+
_0_0, _0_5, _1_0, _1_5, _2_0, _3_0, \
|
|
32
|
+
_90_0, isfinite
|
|
33
|
+
from pygeodesy.ellipsoids import _EWGS84, Fmt, unstr
|
|
34
|
+
from pygeodesy.errors import GeodesicError, IntersectionError, _an, \
|
|
35
|
+
_xgeodesics, _xkwds_get, _xkwds_kwds
|
|
36
|
+
# from pygeodesy.errors import exception_chaining # _MODS
|
|
37
|
+
from pygeodesy.fmath import euclid, fdot
|
|
31
38
|
from pygeodesy.fsums import Fsum, fsum1_, _ceil
|
|
32
|
-
from pygeodesy.interns import _A_, _B_, _c_,
|
|
33
|
-
|
|
34
|
-
from pygeodesy.
|
|
35
|
-
from pygeodesy.
|
|
36
|
-
from pygeodesy.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# from
|
|
39
|
+
from pygeodesy.interns import NN, _A_, _B_, _c_, _COMMASPACE_, \
|
|
40
|
+
_HASH_, _M_, _not_, _SPACE_, _too_
|
|
41
|
+
from pygeodesy.interns import _m_ # PYCHOK used!
|
|
42
|
+
from pygeodesy.karney import Caps, _diff182, GDict, _sincos2de
|
|
43
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, \
|
|
44
|
+
_getenv, _PYGEODESY_INTERSECTTOOL_
|
|
45
|
+
from pygeodesy.named import ADict, _NamedBase, _NamedTuple, _Pass
|
|
46
|
+
from pygeodesy.props import deprecated_method, Property, \
|
|
47
|
+
Property_RO, property_RO
|
|
48
|
+
from pygeodesy.solveBase import _SolveCapsBase, pairs
|
|
49
|
+
# from pygeodesy.streprs import pairs # from .solveBase
|
|
50
|
+
# from pygeodesy.streprs import Fmt, unstr # from .ellipsoids
|
|
51
|
+
from pygeodesy.units import Degrees, Float, Int, Lat, Lon, \
|
|
52
|
+
Meter, Meter_
|
|
53
|
+
from pygeodesy.utily import sincos2, atan2, fabs, radians
|
|
54
|
+
|
|
55
|
+
# from math import atan2, ceil as _ceil, fabs, radians # .fsums, .utily
|
|
43
56
|
|
|
44
57
|
__all__ = _ALL_LAZY.geodesici
|
|
45
|
-
__version__ = '24.
|
|
58
|
+
__version__ = '24.07.09'
|
|
46
59
|
|
|
47
60
|
_0t = 0, # int
|
|
48
61
|
_1_1t = -1, +1
|
|
49
62
|
_1_0_1t = -1, 0, +1
|
|
63
|
+
_c__ = '-c' # PYCHOK used!
|
|
64
|
+
_C__ = '-C' # PYCHOK used!
|
|
50
65
|
_EPS3 = EPS * _3_0
|
|
51
|
-
_EPSr5 = pow(EPS, 0.2) # PYCHOK used!
|
|
66
|
+
_EPSr5 = pow(EPS, 0.2) # PYCHOK used! 7.4e-4 or ~3"
|
|
67
|
+
_i__ = '-i' # PYCHOK used!
|
|
68
|
+
_latA_ = 'latA'
|
|
69
|
+
_lonA_ = 'lonA'
|
|
70
|
+
_n__ = '-n' # PYCHOK used!
|
|
71
|
+
_o__ = '-o' # PYCHOK used!
|
|
72
|
+
_R__ = '-R'
|
|
73
|
+
_sAB_ = 'sAB'
|
|
74
|
+
_sX0_ = 'sX0'
|
|
52
75
|
_TRIPS = 128
|
|
53
76
|
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
class Azi(Degrees):
|
|
79
|
+
'''(INTERNAL) Azimuth C{Unit}.
|
|
80
|
+
'''
|
|
81
|
+
pass
|
|
57
82
|
|
|
58
83
|
|
|
59
|
-
class
|
|
60
|
-
'''4
|
|
61
|
-
|
|
62
|
-
C{
|
|
63
|
-
+1 for parallel, -1 for anti-parallel
|
|
64
|
-
|
|
65
|
-
_Delta = EPS # default margin, see C{Intersector._Delto}
|
|
84
|
+
class XDict(ADict):
|
|
85
|
+
'''4+Item result from L{Intersectool} and L{Intersector} methods
|
|
86
|
+
C{All}, C{Closest}, C{Next} and C{Segment} with the intersection
|
|
87
|
+
offsets C{sA}, C{sB} and C{sX0} in C{meter} and the coincidence
|
|
88
|
+
indicator C{c}, an C{int}, +1 for parallel, -1 for anti-parallel
|
|
89
|
+
or 0 otherwise.
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
Offsets C{sA} and C{sB} are distances measured I{along} geodesic
|
|
92
|
+
line C{glA} respectively C{glB}, but C{sX0} is the I{L1-distance}
|
|
93
|
+
between the intersection and the I{origin} C{X0}.
|
|
69
94
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
Segment indicators C{kA} and C{kB} are C{0} if the segments
|
|
96
|
+
intersect or C{-1} or C{+1} if the intersection is I{before}
|
|
97
|
+
the start, respectively I{after} the end of the segment, like
|
|
98
|
+
L{Intersection3Tuple<Intersection3Tuple>}. Segment indicator
|
|
99
|
+
C{k} is I{Karney}'s C{segmode}, equal C{kA * 3 + kB}.
|
|
100
|
+
'''
|
|
101
|
+
_Delta = EPS # default margin, see C{Intersector._Delto}
|
|
77
102
|
|
|
78
103
|
def __add__(self, other):
|
|
79
104
|
X = _copy(self)
|
|
@@ -87,7 +112,7 @@ class XDist(ADict):
|
|
|
87
112
|
if isinstance(other, tuple): # and len(other) == 2:
|
|
88
113
|
a, b = other
|
|
89
114
|
else:
|
|
90
|
-
# _xinstanceof(
|
|
115
|
+
# _xinstanceof(XDict, other=other)
|
|
91
116
|
a = other.sA
|
|
92
117
|
b = other.sB
|
|
93
118
|
if other.c:
|
|
@@ -97,33 +122,79 @@ class XDist(ADict):
|
|
|
97
122
|
return self
|
|
98
123
|
|
|
99
124
|
def __le__(self, other):
|
|
100
|
-
# _xinstanceof(
|
|
125
|
+
# _xinstanceof(XDict, other=other)
|
|
101
126
|
return self == other or self < other
|
|
102
127
|
|
|
103
128
|
def __lt__(self, other):
|
|
104
|
-
# _xinstanceof(
|
|
129
|
+
# _xinstanceof(XDict, other=other)
|
|
105
130
|
return (self.sA < other.sA or (self.sA == other.sA and # PYCHOK sA
|
|
106
131
|
self.sB < other.sB) and self != other) # PYCHOK sB
|
|
107
132
|
|
|
108
133
|
def __ne__(self, other):
|
|
109
|
-
# _xinstanceof(
|
|
134
|
+
# _xinstanceof(XDict, other=other)
|
|
110
135
|
return self is not other and self.L1(other) > self._Delta
|
|
111
136
|
|
|
112
|
-
def
|
|
137
|
+
def _corners(self, sA, sB, T2):
|
|
138
|
+
# yield all corners further than C{T2}
|
|
139
|
+
a, b = self.sA, self.sB # PYCHOK sA, sB
|
|
140
|
+
for x in (0, sA):
|
|
141
|
+
for y in (0, sB):
|
|
142
|
+
if _L1(x - a, y - b) >= T2:
|
|
143
|
+
yield XDict_(x, y)
|
|
144
|
+
|
|
145
|
+
def _fixCoincident(self, X, c0=0):
|
|
113
146
|
# return the mid-point if C{X} is anti-/parallel
|
|
114
|
-
c = c0
|
|
147
|
+
c = c0 or X.c
|
|
115
148
|
if c:
|
|
116
149
|
s = (self.sA - X.sA + # PYCHOK sA
|
|
117
150
|
(self.sB - X.sB) * c) * _0_5 # PYCHOK sB
|
|
118
151
|
X = X + (s, s * c) # NOT +=
|
|
119
152
|
return X
|
|
120
153
|
|
|
154
|
+
def _fixSegment(self, sA, sB): # PYCHOK no cover
|
|
155
|
+
# modify this anti-/parallel C{XDict}
|
|
156
|
+
a, b, c = self.sA, self.sB, self.c # PYCHOK sA, sB, c
|
|
157
|
+
|
|
158
|
+
def _g(): # intersection in smallest gap
|
|
159
|
+
if c > 0: # distance to [A, B] is |(a - b) - (A - B)|
|
|
160
|
+
t = a - b # consider corners [0, sB] and [sA, 0]
|
|
161
|
+
t = fabs(t + sB) < fabs(t - sA)
|
|
162
|
+
s = a + b
|
|
163
|
+
else: # distance to [A, B] is |(a + b) - (A + B)|
|
|
164
|
+
t = a + b # consider corner [0, 0] and [sA, sB]
|
|
165
|
+
t = fabs(t) < fabs(t - (sA + sB))
|
|
166
|
+
s = sB + (a - b)
|
|
167
|
+
return (sB if t else sA) - s
|
|
168
|
+
|
|
169
|
+
ta = -a
|
|
170
|
+
tb = sA - a
|
|
171
|
+
tc = -c * b
|
|
172
|
+
td = -c * (b - sB)
|
|
173
|
+
|
|
174
|
+
ga = 0 <= (b + c * ta) <= sB
|
|
175
|
+
gb = 0 <= (b + c * tb) <= sB
|
|
176
|
+
gc = 0 <= (a + tc) <= sA
|
|
177
|
+
gd = 0 <= (a + td) <= sA
|
|
178
|
+
|
|
179
|
+
# test opposite rectangle sides first
|
|
180
|
+
s = ((ta + tb) if ga and gb else (
|
|
181
|
+
(tc + td) if gc and gd else (
|
|
182
|
+
(ta + tc) if ga and gc else (
|
|
183
|
+
(ta + td) if ga and gd else (
|
|
184
|
+
(tb + tc) if gb and gc else (
|
|
185
|
+
(tb + td) if gb and gd else _g())))))) * _0_5
|
|
186
|
+
self += s, s * c
|
|
187
|
+
|
|
188
|
+
@property_RO
|
|
189
|
+
def _is00(self):
|
|
190
|
+
return not (self.sA or self.sB) # PYCHOK sA, sB
|
|
191
|
+
|
|
121
192
|
def L1(self, other=None):
|
|
122
193
|
'''Return the C{L1} distance.
|
|
123
194
|
'''
|
|
124
195
|
a, b = self.sA, self.sB # PYCHOK sA, sB
|
|
125
196
|
if other is not None:
|
|
126
|
-
# _xinstanceof(
|
|
197
|
+
# _xinstanceof(XDict, other=other)
|
|
127
198
|
a -= other.sA
|
|
128
199
|
b -= other.sB
|
|
129
200
|
return _L1(a, b)
|
|
@@ -143,25 +214,473 @@ class XDist(ADict):
|
|
|
143
214
|
(-1, 1, -1, 1, 0, 2, 0, -2)):
|
|
144
215
|
yield self + (D_[a], D_[b])
|
|
145
216
|
|
|
146
|
-
def _nmD3(self, n, m, D3):
|
|
217
|
+
def _nmD3(self, n, m, D3): # d3 / 2
|
|
147
218
|
# yield the C{All} starts
|
|
219
|
+
yield self
|
|
148
220
|
for i in range(n, m, 2):
|
|
149
221
|
for j in range(n, m, 2):
|
|
150
|
-
if i or j:
|
|
222
|
+
if i or j: # skip self
|
|
151
223
|
yield self + ((i + j) * D3,
|
|
152
224
|
(i - j) * D3)
|
|
153
225
|
|
|
226
|
+
def _outSide(self, sA, sB):
|
|
227
|
+
# is this C{Xdist} outside one or both segments?
|
|
228
|
+
a, b = self.sA, self.sB # PYCHOK sA, sB
|
|
229
|
+
kA = -1 if a < 0 else (+1 if a > sA else INT0)
|
|
230
|
+
kB = -1 if b < 0 else (+1 if b > sB else INT0)
|
|
231
|
+
self.set_(kA=kA, kB=kB, k=(kA * 3 + kB) or INT0)
|
|
232
|
+
return bool(kA or kB)
|
|
233
|
+
|
|
154
234
|
def _skip(self, S_, T1_Delta):
|
|
155
|
-
# remove starts from C{S_} near this C{
|
|
235
|
+
# remove starts from list C{S_} near this C{XDict}
|
|
156
236
|
for j, S in _enumereverse(S_):
|
|
157
237
|
if S.L1(self) < T1_Delta:
|
|
158
238
|
S_.pop(j)
|
|
159
239
|
|
|
160
|
-
_X000 = XDist() # PYCHOK origin
|
|
161
|
-
_XINF = XDist(INF)
|
|
162
240
|
|
|
241
|
+
def XDict_(sA=_0_0, sB=_0_0, c=INT0, sX0=_0_0):
|
|
242
|
+
'''(INTERNAL) New L{XDict} from positionals.
|
|
243
|
+
'''
|
|
244
|
+
return XDict(sA=sA, sB=sB, c=c, sX0=sX0)
|
|
245
|
+
|
|
246
|
+
_X000 = XDict_() # PYCHOK origin
|
|
247
|
+
_XINF = XDict_(INF)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class _IntersectBase(_NamedBase):
|
|
251
|
+
'''(INTERNAL) Base class for L{Intersectool} and L{Intersector}.
|
|
252
|
+
'''
|
|
253
|
+
# _g = None
|
|
254
|
+
|
|
255
|
+
def __init__(self, geodesic, **name):
|
|
256
|
+
_xinstanceof(*_EWGS84._Geodesics, geodesic=geodesic)
|
|
257
|
+
self._g = geodesic
|
|
258
|
+
if name:
|
|
259
|
+
self.name = name
|
|
260
|
+
|
|
261
|
+
@Property_RO
|
|
262
|
+
def a(self):
|
|
263
|
+
'''Get the I{equatorial} radius, semi-axis (C{meter}).
|
|
264
|
+
'''
|
|
265
|
+
return self.ellipsoid.a
|
|
266
|
+
|
|
267
|
+
equatoradius = a # = Requatorial
|
|
268
|
+
|
|
269
|
+
@Property_RO
|
|
270
|
+
def _cHalf(self): # normalizer, semi-circumference
|
|
271
|
+
return self.R * PI # ~20K Km WGS84
|
|
272
|
+
|
|
273
|
+
@Property_RO
|
|
274
|
+
def _cMax(self): # outer circumference
|
|
275
|
+
return max(self.a, self.ellipsoid.b, self.R) * PI2
|
|
276
|
+
|
|
277
|
+
@property_RO
|
|
278
|
+
def datum(self):
|
|
279
|
+
'''Get the geodesic's datum (C{Datum}).
|
|
280
|
+
'''
|
|
281
|
+
return self.geodesic.datum
|
|
282
|
+
|
|
283
|
+
@Property_RO
|
|
284
|
+
def ellipsoid(self):
|
|
285
|
+
'''Get the C{geodesic}'s ellipsoid (C{Ellipsoid}).
|
|
286
|
+
'''
|
|
287
|
+
return self.geodesic.datum.ellipsoid
|
|
288
|
+
|
|
289
|
+
@Property_RO
|
|
290
|
+
def f(self):
|
|
291
|
+
'''Get the I{flattening} (C{scalar}), C{0} for spherical, negative for prolate.
|
|
292
|
+
'''
|
|
293
|
+
return self.ellipsoid.f
|
|
294
|
+
|
|
295
|
+
flattening = f
|
|
296
|
+
|
|
297
|
+
@property_RO
|
|
298
|
+
def geodesic(self):
|
|
299
|
+
'''Get the C{geodesic} (C{Geodesic...}).
|
|
300
|
+
'''
|
|
301
|
+
return self._g
|
|
302
|
+
|
|
303
|
+
def _Inversa12(self, A, B=None):
|
|
304
|
+
lls = (0, 0, A, 0) if B is None else (A.lat2, A.lon2,
|
|
305
|
+
B.lat2, B.lon2)
|
|
306
|
+
r = self._g.Inverse(*lls, outmask=Caps.DISTANCE)
|
|
307
|
+
return r.s12, r.a12 # .a12 always in r
|
|
308
|
+
|
|
309
|
+
def _ll3z4ll(self, lat1, lon1, azi1_lat2, *lon2):
|
|
310
|
+
t = Lat(lat1=lat1), Lon(lon1=lon1)
|
|
311
|
+
if lon2: # get azis for All, keep lat-/lons
|
|
312
|
+
t += Lat(lat2=azi1_lat2), Lon(lon2=lon2[0])
|
|
313
|
+
else:
|
|
314
|
+
t += Azi(azi1=azi1_lat2),
|
|
315
|
+
return t
|
|
316
|
+
|
|
317
|
+
@deprecated_method
|
|
318
|
+
def Next5s(self, glA, glB, X0=_X000, aMax=1801, sMax=0, **unused):
|
|
319
|
+
'''DEPRECATED on 2024.07.02, use method L{All5}.'''
|
|
320
|
+
return self.All5(glA, glB, X0=X0, aMaX0=aMax, sMaX0=sMax) # PYCHOK attr
|
|
321
|
+
|
|
322
|
+
@Property_RO
|
|
323
|
+
def R(self):
|
|
324
|
+
'''Get the I{authalic} earth radius (C{meter}).
|
|
325
|
+
'''
|
|
326
|
+
return self.ellipsoid.R2
|
|
327
|
+
|
|
328
|
+
def _sMaX0_C2(self, aMaX0, **sMaX0_C):
|
|
329
|
+
_g = _xkwds_get
|
|
330
|
+
s = _g(sMaX0_C, sMaX0=self._cMax)
|
|
331
|
+
s = _g(sMaX0_C, sMax=s) # for backward ...
|
|
332
|
+
a = _g(sMaX0_C, aMax=aMaX0) # ... compatibility
|
|
333
|
+
if a: # degrees to meter, approx.
|
|
334
|
+
s = max(s, self.R * radians(a)) # ellipsoid.degrees2m(a)
|
|
335
|
+
s = _g(sMaX0_C, _R=s)
|
|
336
|
+
if s < _EPS3:
|
|
337
|
+
s = _EPS3 # raise GeodesicError(sMaX0=s)
|
|
338
|
+
return s, _xkwds_kwds(sMaX0_C, _C=False)
|
|
339
|
+
|
|
340
|
+
def _xNext(self, glA, glB, eps1, **eps_C): # PYCHOK no cover
|
|
341
|
+
eps1 = _xkwds_get(eps_C, eps=eps1) # eps for backward compatibility
|
|
342
|
+
if eps1 is not None:
|
|
343
|
+
a = glA.lat1 - glB.lat1
|
|
344
|
+
b = glA.lon1 - glB.lon1
|
|
345
|
+
if euclid(a, b) > eps1:
|
|
346
|
+
raise GeodesicError(lat=a, lon=b, eps1=eps1)
|
|
347
|
+
return _xkwds_kwds(eps_C, _C=False)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class Intersectool(_IntersectBase, _SolveCapsBase):
|
|
351
|
+
'''Wrapper to invoke I{Karney}'s utility U{IntersectTool
|
|
352
|
+
<https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>}
|
|
353
|
+
similar to class L{Intersector<geodesici.Intersector>}.
|
|
354
|
+
|
|
355
|
+
@note: Use property C{IntersectTool} or env variable C{PYGEODESY_INTERSECTTOOL}
|
|
356
|
+
to specify the (fully qualified) path to the C{IntersectTool} executable.
|
|
357
|
+
|
|
358
|
+
@note: This C{Intersectool} is intended I{for testing purposes only}, it invokes
|
|
359
|
+
the C{IntersectTool} executable for I{every} method call.
|
|
360
|
+
'''
|
|
361
|
+
_Error = GeodesicError
|
|
362
|
+
_linelimit = 1200 # line printer width X 10
|
|
363
|
+
_Names_ABs = _latA_, _lonA_, 'latB', 'lonB', _sAB_ # -C to stderr
|
|
364
|
+
_Names_XDict = 'sA', 'sB', _c_ # plus 'k' from -i or 'sX0' from -R
|
|
365
|
+
_Xable_name = 'IntersectTool'
|
|
366
|
+
_Xable_path = _getenv(_PYGEODESY_INTERSECTTOOL_, _PYGEODESY_INTERSECTTOOL_)
|
|
367
|
+
|
|
368
|
+
def __init__(self, a_geodesic=None, f=None, **name):
|
|
369
|
+
'''New L{IntersectTool}.
|
|
370
|
+
|
|
371
|
+
@arg a_geodesic: Earth' equatorial axis (C{meter}) or a geodesic
|
|
372
|
+
(L{GeodesicExact<pygeodesy.geodesicx.GeodesicExact>},
|
|
373
|
+
wrapped L{Geodesic<pygeodesy.geodesicw.Geodesic>} or
|
|
374
|
+
L{GeodesicSolve<pygeodesy.geodsolve.GeodesicSolve>}).
|
|
375
|
+
@kwarg f: Earth' flattening (C{scalar}), required if B{C{a_geodesic}}
|
|
376
|
+
is in C{meter}, ignored otherwise.
|
|
377
|
+
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
378
|
+
|
|
379
|
+
@raise GeodesicError: The eccentricity of the B{C{geodesic}}'s ellipsoid is too
|
|
380
|
+
large or no initial convergence.
|
|
381
|
+
|
|
382
|
+
@see: The B{Note} at I{Karney}'s C++ U{Intersect<https://GeographicLib.sourceforge.io/
|
|
383
|
+
C++/doc/classGeographicLib_1_1Intersect.html#ae41f54c9a44836f6c8f140f6994930cf>}.
|
|
384
|
+
'''
|
|
385
|
+
g = self._GeodesicExact() if a_geodesic is None else (a_geodesic if f is None else
|
|
386
|
+
self._GeodesicExact(a_geodesic, f))
|
|
387
|
+
_IntersectBase.__init__(self, g, **name)
|
|
388
|
+
|
|
389
|
+
def All(self, glA, glB, X0=_X000, eps1=_0_0, aMaX0=0, **sMaX0_C):
|
|
390
|
+
'''Yield all intersection of two geodesic lines up to a limit.
|
|
391
|
+
|
|
392
|
+
@kwarg eps1: Optional margin for the L{euclid<pygeodesy.euclid>}ean distance
|
|
393
|
+
(C{degrees}) between the C{(lat1, lon1)} points of both lines for
|
|
394
|
+
using the L{IntersectTool<Intersectool.IntersectTool>}'s C{"-n"}
|
|
395
|
+
option, unless C{B{eps1}=None}.
|
|
396
|
+
|
|
397
|
+
@return: An L{XDict} for each intersection.
|
|
398
|
+
'''
|
|
399
|
+
def _xz2(**gl):
|
|
400
|
+
try:
|
|
401
|
+
n, gl = gl.popitem() # _xkwds_item2(gl)
|
|
402
|
+
try:
|
|
403
|
+
return self._c_alt, (gl.azi1,)
|
|
404
|
+
except (AttributeError, KeyError):
|
|
405
|
+
return self._i_alt, (gl.lat2, gl.lon2)
|
|
406
|
+
except Exception as x:
|
|
407
|
+
raise GeodesicError(n, gl, cause=x)
|
|
408
|
+
|
|
409
|
+
_t, a = _xz2(glA=glA)
|
|
410
|
+
_x, b = _xz2(glB=glB)
|
|
411
|
+
if _x is not _t:
|
|
412
|
+
raise GeodesicError(glA=glA, glB=glB)
|
|
413
|
+
|
|
414
|
+
A = glA.lat1, glA.lon1
|
|
415
|
+
B = glB.lat1, glB.lon1
|
|
416
|
+
if _x is self._c_alt:
|
|
417
|
+
if X0 is _X000 or X0._is00:
|
|
418
|
+
if eps1 is not None and \
|
|
419
|
+
euclid(glA.lat1 - glB.lat1,
|
|
420
|
+
glA.lon1 - glB.lon1) <= eps1:
|
|
421
|
+
_x, B = self._n_alt, ()
|
|
422
|
+
else: # non-zero offset
|
|
423
|
+
_x = self._o_alt
|
|
424
|
+
b += X0.sA, X0.sB
|
|
425
|
+
|
|
426
|
+
sMaX0, _C = self._sMaX0_C2(aMaX0, **sMaX0_C)
|
|
427
|
+
for X in self._XDictInvoke(_x, _sX0_, (A + a + B + b),
|
|
428
|
+
_R=sMaX0, **_C):
|
|
429
|
+
yield X.set_(c=int(X.c))
|
|
430
|
+
|
|
431
|
+
def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0):
|
|
432
|
+
'''Yield all intersection of two geodesic lines up to a limit.
|
|
433
|
+
|
|
434
|
+
@return: An L{Intersectool5Tuple} for each intersection.
|
|
435
|
+
'''
|
|
436
|
+
for X in self.All(glA, glB, X0=X0, _C=True, **aMaX0_sMaX0):
|
|
437
|
+
yield self._In5T(glA, glB, X, X)
|
|
438
|
+
|
|
439
|
+
@Property_RO
|
|
440
|
+
def _C_option(self):
|
|
441
|
+
return (_C__,)
|
|
442
|
+
|
|
443
|
+
@Property_RO
|
|
444
|
+
def _cmdBasic(self):
|
|
445
|
+
'''(INTERNAL) Get the basic C{IntersectTool} cmd (C{tuple}).
|
|
446
|
+
'''
|
|
447
|
+
return (self.IntersectTool,) + (self._e_option +
|
|
448
|
+
self._E_option +
|
|
449
|
+
self._p_option)
|
|
450
|
+
|
|
451
|
+
@Property_RO
|
|
452
|
+
def _c_alt(self):
|
|
453
|
+
'''(INTERNAL) Get the C{Closest} format (C{tuple}).
|
|
454
|
+
'''
|
|
455
|
+
return _c__, # latA lonA aziA latB lonB aziB
|
|
456
|
+
|
|
457
|
+
def Closest(self, glA, glB, X0=_X000, _C=False):
|
|
458
|
+
'''Find the closest intersection of two geodesic lines.
|
|
459
|
+
|
|
460
|
+
@kwarg _C: Use C{B{_C}=True} to include the C{"-C"} results (C{bool}).
|
|
461
|
+
|
|
462
|
+
@return: An L{XDict}.
|
|
463
|
+
'''
|
|
464
|
+
args = glA.lat1, glA.lon1, glA.azi1, \
|
|
465
|
+
glB.lat1, glB.lon1, glB.azi1
|
|
466
|
+
if X0 is _X000 or X0._is000:
|
|
467
|
+
_x = self._c_alt
|
|
468
|
+
else:
|
|
469
|
+
_x = self._o_alt
|
|
470
|
+
args += X0.sA, X0.sB
|
|
471
|
+
return self._XDictInvoke(_x, NN, args, _C=_C) # _R=None)
|
|
472
|
+
|
|
473
|
+
def Closest5(self, glA, glB, **unused):
|
|
474
|
+
'''Find the closest intersection of two geodesic lines.
|
|
475
|
+
|
|
476
|
+
@return: An L{Intersectool5Tuple}.
|
|
477
|
+
'''
|
|
478
|
+
X = self.Closest(glA, glB, _C=True)
|
|
479
|
+
return self._In5T(glA, glB, X, X)
|
|
480
|
+
|
|
481
|
+
@property_RO
|
|
482
|
+
def _GeodesicExact(self):
|
|
483
|
+
'''Get the I{class} L{GeodesicExact}, I{once}.
|
|
484
|
+
'''
|
|
485
|
+
Intersectool._GeodesicExact = G = _MODS.geodesicx.GeodesicExact # overwrite property_RO
|
|
486
|
+
return G
|
|
487
|
+
|
|
488
|
+
@Property_RO
|
|
489
|
+
def _i_alt(self):
|
|
490
|
+
'''(INTERNAL) Get the C{Segment} format (C{tuple}).
|
|
491
|
+
'''
|
|
492
|
+
return _i__, # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
|
|
493
|
+
|
|
494
|
+
def _In5T(self, glA, glB, S, X, k2=False, **_2X):
|
|
495
|
+
A = GDict(glA).set_(lat2=X.latA, lon2=X.lonA, s12=S.sA)
|
|
496
|
+
B = GDict(glB).set_(lat2=X.latB, lon2=X.lonB, s12=S.sB)
|
|
497
|
+
if k2:
|
|
498
|
+
A.set_(k2=X.kA)
|
|
499
|
+
B.set_(k2=X.kB)
|
|
500
|
+
s, a = self._Inversa12(A, B)
|
|
501
|
+
sAB = _xkwds_get(X, sAB=s)
|
|
502
|
+
if a and s and s != sAB:
|
|
503
|
+
a *= sAB / s # adjust a
|
|
504
|
+
return Intersectool5Tuple(A._2X(glA, **_2X),
|
|
505
|
+
B._2X(glB, **_2X), sAB, a, X.c)
|
|
506
|
+
|
|
507
|
+
@Property
|
|
508
|
+
def IntersectTool(self):
|
|
509
|
+
'''Get the U{IntersectTool<https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>}
|
|
510
|
+
executable (C{filename}).
|
|
511
|
+
'''
|
|
512
|
+
return self._Xable_path
|
|
513
|
+
|
|
514
|
+
@IntersectTool.setter # PYCHOK setter!
|
|
515
|
+
def IntersectTool(self, path):
|
|
516
|
+
'''Set the U{IntersectTool<https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>}
|
|
517
|
+
executable (C{filename}), the (fully qualified) path to the C{IntersectTool} executable.
|
|
518
|
+
|
|
519
|
+
@raise GeodesicError: Invalid B{C{path}}, B{C{path}} doesn't exist or isn't the
|
|
520
|
+
C{IntersectTool} executable.
|
|
521
|
+
'''
|
|
522
|
+
self._setXable(path)
|
|
523
|
+
|
|
524
|
+
def Line(self, lat1, lon1, azi1_lat2, *lon2, **name):
|
|
525
|
+
'''Return a geodesic line from this C{Intersector}'s geodesic, specified by
|
|
526
|
+
two (goedetic) points or a (goedetic) point and an (initial) azimuth.
|
|
527
|
+
|
|
528
|
+
@return: A 3- or 6-item, named L{GDict}.
|
|
529
|
+
'''
|
|
530
|
+
args = self._ll3z4ll(lat1, lon1, azi1_lat2, *lon2)
|
|
531
|
+
gl = GDict((u.name, u) for u in args)
|
|
532
|
+
# if lon2: # get azis for All, use lat-/lons as given
|
|
533
|
+
# r = self._g.Inverse(outmask=Caps.AZIMUTH, *args)
|
|
534
|
+
# gl.set_(azi1=Azi(azi1=r.azi1), azi2=Azi(azi2=r.azi2))
|
|
535
|
+
if name:
|
|
536
|
+
gl.name= name
|
|
537
|
+
return gl
|
|
538
|
+
|
|
539
|
+
def Middle(self, glA, glB, **_C):
|
|
540
|
+
'''Get the mid-points on two geodesic line segments.
|
|
541
|
+
|
|
542
|
+
@kwarg _C: Use C{B{_C}=True} to include the C{"-C"} results (C{bool}).
|
|
543
|
+
|
|
544
|
+
@return: An L{XDict}.
|
|
545
|
+
'''
|
|
546
|
+
X, _, _, _, _ = self._middle5(glA, glB, **_C)
|
|
547
|
+
return X
|
|
548
|
+
|
|
549
|
+
def _middle5(self, glA, glB, _C=False, **unused):
|
|
550
|
+
# return intersections C{A} and C{B} and the
|
|
551
|
+
# center C{X0} of rectangle [sA, sB]
|
|
552
|
+
|
|
553
|
+
def _smi4(**gl):
|
|
554
|
+
try:
|
|
555
|
+
n, gl = gl.popitem()
|
|
556
|
+
il = self._g.InverseLine(gl.lat1, gl.lon1, gl.lat2, gl.lon2)
|
|
557
|
+
except Exception as x:
|
|
558
|
+
raise GeodesicError(n, gl, cause=x)
|
|
559
|
+
s = il.s13
|
|
560
|
+
m = s * _0_5
|
|
561
|
+
return s, m, il, (il.Position(m, outmask=Caps._STD_LINE) if _C else None)
|
|
562
|
+
|
|
563
|
+
sA, mA, iA, A = _smi4(glA=glA)
|
|
564
|
+
sB, mB, iB, B = _smi4(glB=glB)
|
|
565
|
+
X = XDict_(mA, mB) # center
|
|
566
|
+
_ = X._outSide(sA, sB)
|
|
567
|
+
if _C: # _Names_ABs
|
|
568
|
+
s, a = self._Inversa12(A, B)
|
|
569
|
+
X.set_(latA=A.lat2, lonA=A.lon2, aMM=a, # assert sA == A.s12
|
|
570
|
+
latB=B.lat2, lonB=B.lon2, sMM=s) # assert sB == B.s12
|
|
571
|
+
return X, A, iA, B, iB
|
|
572
|
+
|
|
573
|
+
def Middle5(self, glA, glB, **unused):
|
|
574
|
+
'''Get the mid-points on two geodesic line segments and their distance.
|
|
575
|
+
|
|
576
|
+
@return: A L{Middle5Tuple}.
|
|
577
|
+
'''
|
|
578
|
+
X, A, iA, B, iB = self._middle5(glA, glB, _C=True)
|
|
579
|
+
A, B, s, a, c = self._In5T(A, B, X, X, _2X=_M_)
|
|
580
|
+
return Middle5Tuple(_llz2G(A, glA, iA), _llz2G(B, glB, iB), s, a, c)
|
|
581
|
+
|
|
582
|
+
@Property_RO
|
|
583
|
+
def _n_alt(self):
|
|
584
|
+
'''(INTERNAL) Get the C{Next} format (C{tuple}).
|
|
585
|
+
'''
|
|
586
|
+
return _n__, # latA lonA aziA aziB
|
|
587
|
+
|
|
588
|
+
def Next(self, glA, glB, eps1=None, **_C): # PYCHOK no cover
|
|
589
|
+
'''Find the next intersection of two I{intersecting} geodesic lines.
|
|
590
|
+
|
|
591
|
+
@kwarg _C: Use C{B{_C}=True} to include the option C{"-C"} results (C{bool}).
|
|
592
|
+
|
|
593
|
+
@return: An L{XDict}.
|
|
594
|
+
'''
|
|
595
|
+
if eps1 or _C:
|
|
596
|
+
_C = self._xNext(glA, glB, eps1, **_C)
|
|
597
|
+
return self._XDictInvoke(self._n_alt, NN,
|
|
598
|
+
(glA.lat1, glA.lon1, glA.azi1, glB.azi1),
|
|
599
|
+
**_C) # _R=None
|
|
600
|
+
|
|
601
|
+
def Next5(self, glA, glB, **eps1): # PYCHOK no cover
|
|
602
|
+
'''Find the next intersection of two I{intersecting} geodesic lines.
|
|
603
|
+
|
|
604
|
+
@return: An L{Intersectool5Tuple}.
|
|
605
|
+
'''
|
|
606
|
+
X = self.Next(glA, glB, _C=True, **eps1)
|
|
607
|
+
return self._In5T(glA, glB, X, X)
|
|
608
|
+
|
|
609
|
+
@Property_RO
|
|
610
|
+
def _o_alt(self):
|
|
611
|
+
'''(INTERNAL) Get the C{Offset} format (C{tuple}).
|
|
612
|
+
'''
|
|
613
|
+
return _o__, # latA lonA aziA latB lonB aziB x0 y0
|
|
614
|
+
|
|
615
|
+
def _R_option(self, _R=None):
|
|
616
|
+
'''(INTERNAL) Get the C{-R maxdist} option.
|
|
617
|
+
'''
|
|
618
|
+
return () if _R is None else (_R__, str(_R)) # -R maxdist
|
|
619
|
+
|
|
620
|
+
def Segment(self, glA, glB, **_C_unused):
|
|
621
|
+
'''Find the intersection between two geodesic line segments.
|
|
163
622
|
|
|
164
|
-
|
|
623
|
+
@kwarg _C: Use C{B{_C}=True} to include the option C{"-C"} results (C{bool}).
|
|
624
|
+
|
|
625
|
+
@return: An L{XDict}.
|
|
626
|
+
'''
|
|
627
|
+
X = self._XDictInvoke(self._i_alt, 'k',
|
|
628
|
+
(glA.lat1, glA.lon1, glA.lat2, glA.lon2,
|
|
629
|
+
glB.lat1, glB.lon1, glB.lat2, glB.lon2),
|
|
630
|
+
_C=_xkwds_get(_C_unused, _C=False)) # _R=None
|
|
631
|
+
k = int(X.k)
|
|
632
|
+
for kA in range(-1, 2):
|
|
633
|
+
for kB in range(-1, 2):
|
|
634
|
+
if (kA * 3 + kB) == k:
|
|
635
|
+
return X.set_(k=k, kA=kA, kB=kB)
|
|
636
|
+
raise GeodesicError(k=k, glA=glA, glB=glB, txt=repr(X))
|
|
637
|
+
|
|
638
|
+
def Segment5(self, glA, glB, **unused):
|
|
639
|
+
'''Find the next intersection of two I{intersecting} geodesic lines.
|
|
640
|
+
|
|
641
|
+
@return: An L{Intersectool5Tuple}.
|
|
642
|
+
'''
|
|
643
|
+
X = self.Segment(glA, glB, _C=True)
|
|
644
|
+
return self._In5T(glA, glB, X, X, k2=True)
|
|
645
|
+
|
|
646
|
+
def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
|
|
647
|
+
'''Return this C{Intersectool} as string.
|
|
648
|
+
|
|
649
|
+
@kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=", "}
|
|
650
|
+
for the C{float} C{prec}ision, number of decimal digits
|
|
651
|
+
(0..9) and the C{sep}arator string to join. Trailing
|
|
652
|
+
zero decimals are stripped for B{C{prec}} values of 1
|
|
653
|
+
and above, but kept for negative B{C{prec}} values.
|
|
654
|
+
|
|
655
|
+
@return: Intersectool items (C{str}).
|
|
656
|
+
'''
|
|
657
|
+
d = dict(geodesic=self.geodesic, invokation=self.invokation,
|
|
658
|
+
status=self.status,
|
|
659
|
+
IntersectTool=self.IntersectTool)
|
|
660
|
+
return sep.join(pairs(d, prec=prec))
|
|
661
|
+
|
|
662
|
+
def _XDictInvoke(self, alt, _k_sX0, args, _C=False, **_R):
|
|
663
|
+
'''(INTERNAL) Invoke C{IntersectTool}, return results as C{XDict} or
|
|
664
|
+
a C{generator} if keyword argument C{B{_R}=sMaX0} is specified.
|
|
665
|
+
'''
|
|
666
|
+
# assert len(args) == {self._c_alt: 6,
|
|
667
|
+
# self._i_alt: 8,
|
|
668
|
+
# self._n_alt: 4,
|
|
669
|
+
# self._o_alt: 8}.get(alt, len(args))
|
|
670
|
+
cmd = self._cmdBasic
|
|
671
|
+
Names = self._Names_XDict # has _c_ always
|
|
672
|
+
if _k_sX0:
|
|
673
|
+
Names += _k_sX0,
|
|
674
|
+
if _C:
|
|
675
|
+
cmd += self._C_option
|
|
676
|
+
Names += self._Names_ABs
|
|
677
|
+
if _R:
|
|
678
|
+
cmd += self._R_option(**_R)
|
|
679
|
+
X, _R = self._DictInvoke2(cmd + alt, Names, XDict, args, **_R)
|
|
680
|
+
return X if _R else X.set_(c=int(X.c)) # generator or XDict
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
class Intersector(_IntersectBase):
|
|
165
684
|
'''Finder of intersections between two goedesic lines, each an instance
|
|
166
685
|
of L{GeodesicLineExact<pygeodesy.geodesicx.GeodesicLineExact>},
|
|
167
686
|
wrapped L{GeodesicLine<pygeodesy.geodesicw.GeodesicLine>} or
|
|
@@ -172,7 +691,6 @@ class Intersector(_NamedBase):
|
|
|
172
691
|
'''
|
|
173
692
|
# _D1 = 0
|
|
174
693
|
# _D2 = 0
|
|
175
|
-
# _g = None
|
|
176
694
|
# _T1 = 0
|
|
177
695
|
# _T5 = 0
|
|
178
696
|
|
|
@@ -190,53 +708,46 @@ class Intersector(_NamedBase):
|
|
|
190
708
|
@see: The B{Note} at I{Karney}'s C++ U{Intersect<https://GeographicLib.sourceforge.io/
|
|
191
709
|
C++/doc/classGeographicLib_1_1Intersect.html#ae41f54c9a44836f6c8f140f6994930cf>}.
|
|
192
710
|
'''
|
|
193
|
-
|
|
194
|
-
self.
|
|
195
|
-
if name:
|
|
196
|
-
self.name = name
|
|
197
|
-
E = self.ellipsoid
|
|
198
|
-
|
|
711
|
+
_IntersectBase.__init__(self, geodesic, **name)
|
|
712
|
+
E = self.ellipsoid
|
|
199
713
|
t1 = E.b * PI # min distance between intersects
|
|
200
|
-
t2 = self._polarDist2(_90_0)[0] * _2_0 # furthest closest intersect
|
|
201
|
-
t5 = self._Inversa12(_90_0)[0] * _2_0 # longest shortest geodesic
|
|
714
|
+
t2 = self._polarDist2(_90_0)[0] * _2_0 # furthest, closest intersect
|
|
715
|
+
t5 = self._Inversa12( _90_0)[0] * _2_0 # longest, shortest geodesic
|
|
202
716
|
if self.f > 0:
|
|
203
717
|
t3 = self._obliqDist4()[0]
|
|
204
718
|
t4 = t1
|
|
205
719
|
else: # PYCHOK no cover
|
|
206
720
|
t1, t2, t3 = t2, t1, t5
|
|
207
|
-
t4 = self._polarB3()
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
721
|
+
t4, _, _ = self._polarB3()
|
|
722
|
+
|
|
723
|
+
self._D1 = d1 = t2 * _0_5 # ~E.L tile spacing for Closest
|
|
724
|
+
self._D2 = d2 = t3 / _1_5 # tile spacing for Next
|
|
725
|
+
self._D3 = d3 = t4 - self.Delta # tile spacing for All
|
|
726
|
+
self._T1 = t1 # min distance between intersects
|
|
727
|
+
self._T2 = t2 = t1 * _2_0
|
|
728
|
+
# self._T5 = t5 # not used
|
|
212
729
|
if not (d1 < d3 and d2 < d3 and d2 < t2):
|
|
213
730
|
t = Fmt.PARENSPACED(_too_('eccentric'), E.e)
|
|
214
731
|
raise GeodesicError(ellipsoid=E.toStr(terse=2), txt=t)
|
|
215
|
-
self._D1 = d1 # tile spacing for Closest
|
|
216
|
-
self._D2 = d2 # tile spacing for Next
|
|
217
|
-
self._D3 = d3 # tile spacing for All
|
|
218
|
-
self._T1 = t1 # min distance between intersects
|
|
219
|
-
self._T2 = t2
|
|
220
|
-
# self._T5 = t5
|
|
221
732
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
'''Get the I{equatorial} radius, semi-axis (C{meter}).
|
|
225
|
-
'''
|
|
226
|
-
return self.ellipsoid.a
|
|
227
|
-
|
|
228
|
-
equatoradius = a # = Requatorial
|
|
229
|
-
|
|
230
|
-
def All(self, glA, glB, X0=_X000, **sMax):
|
|
231
|
-
'''Find all intersection of two geodesic lines up to a limit.
|
|
733
|
+
def All(self, glA, glB, X0=None, aMaX0=0, **sMaX0_C): # MCCABE 13
|
|
734
|
+
'''Yield all intersection of two geodesic lines up to a limit.
|
|
232
735
|
|
|
233
736
|
@arg glA: A geodesic line (L{Line<Intersector.Line>}).
|
|
234
737
|
@arg glB: An other geodesic line (L{Line<Intersector.Line>}).
|
|
235
|
-
@kwarg X0: Optional
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
@
|
|
738
|
+
@kwarg X0: Optional I{origin} for I{L1-distances} (L{XDict}) or
|
|
739
|
+
C{None} for the L{Middle<Intersector.Middle>} of both
|
|
740
|
+
lines if both are a 4-C{args} L{Line<Intersector.Line>}
|
|
741
|
+
or C{InverseLine}, otherwise C{XDiff_(0, 0)}.
|
|
742
|
+
@kwarg aMaX0: Upper limit for the I{angular L1-distance}
|
|
743
|
+
(C{degrees}) or C{None} or C{0} for unlimited.
|
|
744
|
+
@kwarg sMaX0_C: Optional, upper limit C{B{sMaX0}=2*PI*R} for the
|
|
745
|
+
I{L1-distance} to B{C{X0}} (C{meter}) and option
|
|
746
|
+
C{B{_C}=False} to include the intersection lat-/
|
|
747
|
+
longitudes C{latA}, C{lonA}, C{latB}, C{lonB} and
|
|
748
|
+
distances C{sAB} and C{aSB}.
|
|
749
|
+
|
|
750
|
+
@return: Yield an L{XDict} for each intersection found.
|
|
240
751
|
|
|
241
752
|
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}}
|
|
242
753
|
invalid, incompatible or ill-configured.
|
|
@@ -244,35 +755,37 @@ class Intersector(_NamedBase):
|
|
|
244
755
|
@raise IntersectionError: No convergence.
|
|
245
756
|
'''
|
|
246
757
|
self._xLines(glA, glB)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
758
|
+
if X0 is None:
|
|
759
|
+
try: # determine X0
|
|
760
|
+
X0, _, _ = self._middle3(glA, glB, True)
|
|
761
|
+
except GeodesicError: # no .Distance
|
|
762
|
+
X0 = _X000
|
|
763
|
+
sMaX0, _C = self._sMaX0_C2(aMaX0, **sMaX0_C)
|
|
764
|
+
|
|
765
|
+
D, _D = self.Delta, self._cHalf # C++ _d
|
|
766
|
+
xMaX0 = sMaX0 + D
|
|
767
|
+
m = int(_ceil(xMaX0 / self._D3)) # m x m tiles
|
|
768
|
+
d3 = xMaX0 / m
|
|
255
769
|
T2d3D = self._T2d3Delta(d3)
|
|
256
|
-
_X0fx = X0._fixCoincident
|
|
257
770
|
|
|
258
|
-
c0 = 0
|
|
259
771
|
C_ = _List(D) # closest coincident
|
|
260
772
|
X_ = _List(D) # intersections found
|
|
773
|
+
c0 = 0
|
|
261
774
|
S_ = list(X0._nmD3(1 - m, m, d3 * _0_5))
|
|
262
|
-
# assert len(
|
|
775
|
+
# assert len(S_) == m * m + (m - 1) % 2
|
|
263
776
|
while S_:
|
|
264
777
|
Q, i = self._Basic2(glA, glB, S_.pop(0))
|
|
265
778
|
if Q in X_:
|
|
266
779
|
continue
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Q
|
|
271
|
-
if Q in C_:
|
|
780
|
+
if Q.c: # coincident intersection # PYCHOK no cover
|
|
781
|
+
_X0fx = X0._fixCoincident
|
|
782
|
+
Q = _X0fx(Q) # Q = Q'
|
|
783
|
+
if c0 and Q in C_:
|
|
272
784
|
continue
|
|
273
785
|
C_.addend(Q)
|
|
274
786
|
# elimate all existing intersections
|
|
275
787
|
# on this line (which didn't set c0)
|
|
788
|
+
c0 = Q.c
|
|
276
789
|
for j, X in _enumereverse(X_):
|
|
277
790
|
if _X0fx(X, c0).L1(Q) <= D: # X' == Q
|
|
278
791
|
X_.pop(j)
|
|
@@ -284,38 +797,33 @@ class Intersector(_NamedBase):
|
|
|
284
797
|
s += s0
|
|
285
798
|
sa = 0
|
|
286
799
|
while True:
|
|
800
|
+
i += 1
|
|
287
801
|
sa = _cjD(glA, s + sa, *args) - s0
|
|
288
802
|
X = Q + (sa, sa * c0)
|
|
289
|
-
i
|
|
290
|
-
if X_.addend(X, X0.L1(X), i) > xMax:
|
|
803
|
+
if X_.addend(X, X0.L1(X), i) > xMaX0:
|
|
291
804
|
break
|
|
292
805
|
|
|
806
|
+
elif c0 and Q in C_: # Q.c == 0
|
|
807
|
+
continue
|
|
808
|
+
else:
|
|
809
|
+
a = len(X_)
|
|
810
|
+
|
|
293
811
|
X_.addend(Q, X0.L1(Q), i + 1)
|
|
294
812
|
for X in X_[a:]: # addended Xs
|
|
295
813
|
X._skip(S_, T2d3D)
|
|
296
814
|
|
|
297
|
-
return X_.
|
|
298
|
-
|
|
299
|
-
def All5(self, glA, glB, X0=_X000, aMax=0, **sMax):
|
|
300
|
-
'''Find all intersection of two geodesic lines up to a limit.
|
|
815
|
+
return X_.sorter(sMaX0, self._C, glA, glB, **_C) # generator
|
|
301
816
|
|
|
302
|
-
|
|
303
|
-
|
|
817
|
+
def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0_C):
|
|
818
|
+
'''Yield all intersection of two geodesic lines up to a limit.
|
|
304
819
|
|
|
305
820
|
@return: Yield an L{Intersector5Tuple}C{(A, B, sAB, aAB, c)}
|
|
306
821
|
for each intersection found.
|
|
307
822
|
|
|
308
823
|
@see: Methods L{All} for further details.
|
|
309
824
|
'''
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
r = self._In5T(glA, glB, X, X)
|
|
313
|
-
yield r
|
|
314
|
-
if aMax:
|
|
315
|
-
aA += r.A.a12
|
|
316
|
-
aB += r.B.a12
|
|
317
|
-
if fabs(aA) > aMax or fabs(aB) > aMax:
|
|
318
|
-
break
|
|
825
|
+
for X in self.All(glA, glB, X0=X0, **aMaX0_sMaX0_C):
|
|
826
|
+
yield self._In5T(glA, glB, X, X)
|
|
319
827
|
|
|
320
828
|
def _Basic2(self, glA, glB, S, i=0):
|
|
321
829
|
'''(INTERNAL) Get a basic solution.
|
|
@@ -330,18 +838,31 @@ class Intersector(_NamedBase):
|
|
|
330
838
|
|
|
331
839
|
raise IntersectionError(Fmt.no_convergence(S.L1(), self._Tol))
|
|
332
840
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
841
|
+
def _C(self, X, glA, glB, _C=False, _AB=True):
|
|
842
|
+
# add the C{_C} items to C{X}, if requested.
|
|
843
|
+
if _C:
|
|
844
|
+
A = self._Position(glA, X.sA)
|
|
845
|
+
B = self._Position(glB, X.sB)
|
|
846
|
+
s, a = self._Inversa12(A, B)
|
|
847
|
+
X.set_(latA=A.lat2, lonA=A.lon2,
|
|
848
|
+
latB=B.lat2, lonB=B.lon2)
|
|
849
|
+
if _AB:
|
|
850
|
+
X.set_(sAB=s, aAB=a)
|
|
851
|
+
else:
|
|
852
|
+
X.set_(sMM=s, aMM=a)
|
|
853
|
+
return X
|
|
336
854
|
|
|
337
|
-
def Closest(self, glA, glB, X0=_X000):
|
|
855
|
+
def Closest(self, glA, glB, X0=_X000, **_C):
|
|
338
856
|
'''Find the closest intersection of two geodesic lines.
|
|
339
857
|
|
|
340
858
|
@arg glA: A geodesic line (L{Line<Intersector.Line>}).
|
|
341
859
|
@arg glB: An other geodesic line (L{Line<Intersector.Line>}).
|
|
342
|
-
@kwarg X0: Optional
|
|
860
|
+
@kwarg X0: Optional I{origin} for I{L1-closeness} (L{XDict}).
|
|
861
|
+
@kwarg _C: If C{True}, include the lat-/longitudes C{latA},
|
|
862
|
+
C{lonA}, C{latB}, C{lonB} oon and distances C{sAB}
|
|
863
|
+
and C{aSB} between the intersections.
|
|
343
864
|
|
|
344
|
-
@return: The intersection (L{
|
|
865
|
+
@return: The intersection (L{XDict}) or C{None} if none found.
|
|
345
866
|
|
|
346
867
|
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}}
|
|
347
868
|
invalid, incompatible or ill-configured.
|
|
@@ -362,7 +883,7 @@ class Intersector(_NamedBase):
|
|
|
362
883
|
Q, d, q = X, d0, i
|
|
363
884
|
X._skip(S_, self._T2D1Delta)
|
|
364
885
|
|
|
365
|
-
return None if Q is X0 else Q.set_(sX0=d, iteration=q)
|
|
886
|
+
return None if Q is X0 else self._C(Q, glA, glB, **_C).set_(sX0=d, iteration=q)
|
|
366
887
|
|
|
367
888
|
def Closest5(self, glA, glB, X0=_X000):
|
|
368
889
|
'''Find the closest intersection of two geodesic lines.
|
|
@@ -387,7 +908,7 @@ class Intersector(_NamedBase):
|
|
|
387
908
|
# see "Algorithms for geodesics", eqs. 31, 32, 33.
|
|
388
909
|
m23 = m13 * M12
|
|
389
910
|
M32 = M31 * M12
|
|
390
|
-
if m12:
|
|
911
|
+
if m12: # PYCHOK no cover
|
|
391
912
|
m23 -= m12 * M13
|
|
392
913
|
if m13:
|
|
393
914
|
M32 += (_1 - M13 * M31) * m12 / m13
|
|
@@ -408,7 +929,7 @@ class Intersector(_NamedBase):
|
|
|
408
929
|
|
|
409
930
|
@Property
|
|
410
931
|
def _conjDist3s(self):
|
|
411
|
-
gl, self._gl3, _D = self._gl3, None, self.
|
|
932
|
+
gl, self._gl3, _D = self._gl3, None, self._cHalf
|
|
412
933
|
return tuple(self._conjDist(gl, s) for s in (-_D, 0, _D))
|
|
413
934
|
|
|
414
935
|
@_conjDist3s.setter # PYCHOK setter!
|
|
@@ -418,93 +939,70 @@ class Intersector(_NamedBase):
|
|
|
418
939
|
|
|
419
940
|
def _conjDist3Tt_(self, c, X0=_X000):
|
|
420
941
|
for s in self._conjDist3s:
|
|
421
|
-
T =
|
|
942
|
+
T = XDict_(s, s * c, c)
|
|
422
943
|
yield self._Delto(T), T.L1(X0)
|
|
423
944
|
|
|
424
945
|
def _conjDist5(self, azi):
|
|
425
946
|
gl = self._Line(azi1=azi)
|
|
426
|
-
s = self._conjDist(gl, self.
|
|
427
|
-
X, _ = self._Basic2(gl, gl,
|
|
947
|
+
s = self._conjDist(gl, self._cHalf)
|
|
948
|
+
X, _ = self._Basic2(gl, gl, XDict_(s * _0_5, -s * _1_5))
|
|
428
949
|
return s, (X.L1() - s * _2_0), azi, X.sA, X.sB
|
|
429
950
|
|
|
430
951
|
@Property_RO
|
|
431
952
|
def Delta(self):
|
|
432
953
|
'''Get the equality and tiling margin (C{meter}).
|
|
433
954
|
'''
|
|
434
|
-
return self.
|
|
955
|
+
return self._cHalf * _EPSr5 # ~15 Km WGS84
|
|
435
956
|
|
|
436
957
|
def _Delto(self, X):
|
|
437
958
|
# copy Delta into X, overriding X's default
|
|
438
959
|
X._Delta = self.Delta # NOT X.set_(self.Delta)
|
|
439
960
|
return X
|
|
440
961
|
|
|
441
|
-
@Property_RO
|
|
442
|
-
def ellipsoid(self):
|
|
443
|
-
'''Get the C{geodesic}'s ellipsoid (C{Ellipsoid}).
|
|
444
|
-
'''
|
|
445
|
-
return self.geodesic.datum.ellipsoid
|
|
446
|
-
|
|
447
962
|
@Property_RO
|
|
448
963
|
def _EPS3R(self):
|
|
449
964
|
return _EPS3 * self.R
|
|
450
965
|
|
|
451
|
-
@Property_RO
|
|
452
|
-
def f(self):
|
|
453
|
-
'''Get the I{flattening} (C{scalar}), C{0} for spherical, negative for prolate.
|
|
454
|
-
'''
|
|
455
|
-
return self.ellipsoid.f
|
|
456
|
-
|
|
457
|
-
flattening = f
|
|
458
|
-
|
|
459
966
|
@Property_RO
|
|
460
967
|
def _faPI_4(self):
|
|
461
968
|
return (self.f + _2_0) * self.a * PI_4
|
|
462
969
|
|
|
463
|
-
@property_RO
|
|
464
|
-
def geodesic(self):
|
|
465
|
-
'''Get the C{geodesic} (C{Geodesic...}).
|
|
466
|
-
'''
|
|
467
|
-
return self._g
|
|
468
|
-
|
|
469
970
|
@Property_RO
|
|
470
971
|
def _GeodesicLines(self):
|
|
471
972
|
'''(INTERNAL) Get the C{Geodesic...Line} class(es).
|
|
472
973
|
'''
|
|
473
974
|
return type(self._Line()),
|
|
474
975
|
|
|
475
|
-
def _In5T(self, glA, glB, S, X):
|
|
976
|
+
def _In5T(self, glA, glB, S, X, k2=False, **_2X):
|
|
476
977
|
# Return an intersection as C{Intersector5Tuple}.
|
|
477
|
-
A = self._Position(glA, S.sA
|
|
478
|
-
B = self._Position(glB, S.sB
|
|
978
|
+
A = self._Position(glA, S.sA)
|
|
979
|
+
B = self._Position(glB, S.sB)
|
|
980
|
+
if k2:
|
|
981
|
+
A.set_(k2=X.kA)
|
|
982
|
+
B.set_(k2=X.kB)
|
|
479
983
|
s, a = self._Inversa12(A, B)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
def _Inversa12(self, A, B=None):
|
|
484
|
-
lls = (0, 0, A, 0) if B is None else (A.lat2, A.lon2,
|
|
485
|
-
B.lat2, B.lon2)
|
|
486
|
-
r = self._g.Inverse(*lls, outmask=Caps.DISTANCE)
|
|
487
|
-
return r.s12, r.a12 # .a12 always in r
|
|
984
|
+
return Intersector5Tuple(A._2X(glA, **_2X),
|
|
985
|
+
B._2X(glB, **_2X), s, a, X.c, iteration=X.iteration)
|
|
488
986
|
|
|
489
987
|
def _Inverse(self, A, B): # caps=Caps.STANDARD
|
|
490
988
|
return self._g.Inverse(A.lat2, A.lon2, B.lat2, B.lon2)
|
|
491
989
|
|
|
492
|
-
def Line(self, lat1, lon1,
|
|
990
|
+
def Line(self, lat1, lon1, azi1_lat2, *lon2, **name):
|
|
493
991
|
'''Return a geodesic line from this C{Intersector}'s geodesic, specified by
|
|
494
992
|
two (goedetic) points or a (goedetic) point and an (initial) azimuth.
|
|
495
993
|
|
|
496
994
|
@arg lat1: Latitude of the first point (C{degrees}).
|
|
497
995
|
@arg lon1: Longitude of the first point (C{degrees}).
|
|
498
|
-
@arg
|
|
996
|
+
@arg azi1_lat2: Azimuth at the first point (compass C{degrees}) if no
|
|
499
997
|
B{C{lon2}} argument is given, otherwise the latitude of
|
|
500
998
|
the second point (C{degrees}).
|
|
501
999
|
@arg lon2: If given, the longitude of the second point (C{degrees}).
|
|
502
1000
|
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
503
1001
|
|
|
504
1002
|
@return: A line (from L{geodesic<Intersector.geodesic>}C{.Line} or
|
|
505
|
-
C{
|
|
1003
|
+
C{.InverseLine} method) with C{LINE_CAPS}.
|
|
506
1004
|
'''
|
|
507
|
-
args = (lat1, lon1,
|
|
1005
|
+
args = self._ll3z4ll(lat1, lon1, azi1_lat2, *lon2)
|
|
508
1006
|
gl = self._g.InverseLine(*args, caps=Caps.LINE_CAPS) if lon2 else \
|
|
509
1007
|
self._g.Line( *args, caps=Caps.LINE_CAPS)
|
|
510
1008
|
if name:
|
|
@@ -514,89 +1012,80 @@ class Intersector(_NamedBase):
|
|
|
514
1012
|
def _Line(self, lat1=0, lon1=0, azi1=0):
|
|
515
1013
|
return self._g.Line(lat1, lon1, azi1, caps=Caps.LINE_CAPS)
|
|
516
1014
|
|
|
1015
|
+
def Middle(self, glA, glB, raiser=True, **_C):
|
|
1016
|
+
'''Get the mid-points on two geodesic line segments.
|
|
1017
|
+
|
|
1018
|
+
@arg glA: A geodesic line (L{Line<Intersector.Line>}, 4-C{args}).
|
|
1019
|
+
@arg glB: An other geodesic line (L{Line<Intersector.Line>}, 4-C{args}).
|
|
1020
|
+
@kwarg raiser: If C{True}, check that B{C{glA}} and B{C{glB}} are a
|
|
1021
|
+
4-C{args} L{Line<Intersector.Line>} or C{InverseLine}
|
|
1022
|
+
(C{bool}).
|
|
1023
|
+
@kwarg _C: If C{True}, include the lat-/longitudes C{latA}, C{lonA},
|
|
1024
|
+
C{latB}, C{lonB} of the mid-points and half-lengths C{sA}
|
|
1025
|
+
and C{sB} in C{meter} of the respective line segments.
|
|
1026
|
+
|
|
1027
|
+
@return: The mid-point and half-length of each segment (L{XDict}),
|
|
1028
|
+
B{C{_C}} above.
|
|
1029
|
+
|
|
1030
|
+
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}} invalid,
|
|
1031
|
+
incompatible, ill-configured or not a 4-C{args
|
|
1032
|
+
Line} or other C{InverseLine}.
|
|
1033
|
+
'''
|
|
1034
|
+
M, _, _ = self._middle3(glA, glB, raiser)
|
|
1035
|
+
# _ = X._outSide(sA, sB)
|
|
1036
|
+
return self._C(M, glA, glB, _AB=False, **_C) if _C else M
|
|
1037
|
+
|
|
1038
|
+
def _middle3(self, glA, glB, raiser): # in .All
|
|
1039
|
+
# return segment length C{sA} and C{sB} and the
|
|
1040
|
+
# center C{X0} of rectangle [sA, sB]
|
|
1041
|
+
self._xLines(glA, glB, s13=raiser) # need .Distance
|
|
1042
|
+
sA = glA.Distance()
|
|
1043
|
+
sB = glB.Distance()
|
|
1044
|
+
X = XDict_(sA * _0_5, sB * _0_5) # center
|
|
1045
|
+
return self._Delto(X), sA, sB
|
|
1046
|
+
|
|
1047
|
+
def Middle5(self, glA, glB, **raiser):
|
|
1048
|
+
'''Get the mid-points of two geodesic line segments and distances.
|
|
1049
|
+
|
|
1050
|
+
@return: A L{Middle5Tuple}C{(A, B, sMM, aMM, c)}.
|
|
1051
|
+
|
|
1052
|
+
@see: Method L{Middle} for further details.
|
|
1053
|
+
'''
|
|
1054
|
+
M = self.Middle(glA, glB, _C=True, **raiser)
|
|
1055
|
+
A, B, s, a, c = self._In5T(glA, glB, M, M, _2X=_M_)
|
|
1056
|
+
return Middle5Tuple(_llz2G(A, glA, glA), _llz2G(B, glB, glB), s, a, c)
|
|
1057
|
+
|
|
517
1058
|
def _m12_M12_M21(self, gl, s):
|
|
518
1059
|
P = gl.Position(s, outmask=Caps._REDUCEDLENGTH_GEODESICSCALE)
|
|
519
1060
|
return P.m12, P.M12, P.M21
|
|
520
1061
|
|
|
521
|
-
def Next(self, glA, glB, **
|
|
1062
|
+
def Next(self, glA, glB, eps1=None, **_C): # PYCHOK no cover
|
|
522
1063
|
'''Yield the next intersection of two I{intersecting} geodesic lines.
|
|
523
1064
|
|
|
524
1065
|
@arg glA: A geodesic line (L{Line<Intersector.Line>}).
|
|
525
1066
|
@arg glB: An other geodesic line (L{Line<Intersector.Line>}).
|
|
526
|
-
@kwarg
|
|
1067
|
+
@kwarg eps1: Optional margin for the L{euclid<pygeodesy.euclid>}ean
|
|
1068
|
+
distance (C{degrees}) between the C{(lat1, lon1)} points
|
|
1069
|
+
of both lines or C{None} for unchecked.
|
|
1070
|
+
@kwarg _C: If C{True}, include the lat-/longitudes C{latA}, C{lonA},
|
|
1071
|
+
C{latB}, C{lonB} of and distances C{sAB} and C{aSB}
|
|
1072
|
+
between the intersections.
|
|
527
1073
|
|
|
528
|
-
@return: The intersection (L{
|
|
1074
|
+
@return: The intersection (L{XDict}) or C{None} if none found.
|
|
529
1075
|
|
|
530
|
-
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}}
|
|
531
|
-
|
|
532
|
-
|
|
1076
|
+
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}} invalid,
|
|
1077
|
+
incompatible, ill-configured or C{(lat1, lon1)}
|
|
1078
|
+
not B{C{eps1}}-equal.
|
|
533
1079
|
|
|
534
1080
|
@raise IntersectionError: No convergence.
|
|
535
1081
|
|
|
536
1082
|
@note: Offset C{X0} is implicit, zeros.
|
|
537
1083
|
'''
|
|
538
1084
|
self._xLines(glA, glB)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
b = glA.lon1 - glB.lon1
|
|
542
|
-
if fabs(a) > e or fabs(b) > e:
|
|
543
|
-
raise GeodesicError(lat1=a, lon1=b, eps=e)
|
|
544
|
-
return self._Next(glA, glB)
|
|
545
|
-
|
|
546
|
-
def Next5(self, glA, glB, eps=_EPS3):
|
|
547
|
-
'''Yield the next intersection of two I{intersecting} geodesic lines.
|
|
1085
|
+
if eps1 or _C: # eps
|
|
1086
|
+
_C = self._xNext(glA, glB, eps1, **_C)
|
|
548
1087
|
|
|
549
|
-
|
|
550
|
-
if none found.
|
|
551
|
-
|
|
552
|
-
@see: Method L{Next} for further details.
|
|
553
|
-
'''
|
|
554
|
-
X = self.Next(glA, glB, eps=eps)
|
|
555
|
-
return X if X is None else self._In5T(glA, glB, X, X)
|
|
556
|
-
|
|
557
|
-
def Next5s(self, glA, glB, X0=_X000, aMax=1801, sMax=0, avg=False, **Delta):
|
|
558
|
-
'''Yield C{Next} intersections up to a maximal (angular) distance.
|
|
559
|
-
|
|
560
|
-
@kwarg aMax: Upper limit for the angular distance (C{degrees}) or
|
|
561
|
-
C{None} or C{0} for unlimited.
|
|
562
|
-
@kwarg sMax: Upper limit for the distance (C{meter}) or C{None} or
|
|
563
|
-
C{0} for unlimited.
|
|
564
|
-
@kwarg avg: If C{True}, set the next intersection lat- and longitude
|
|
565
|
-
to the mid-point of the previous ones (C{bool}).
|
|
566
|
-
@kwarg Delta: Optional, margin overrding this margin (C{meter}), see
|
|
567
|
-
prpoerty L{Delta<Intersector.Delta>}.
|
|
568
|
-
|
|
569
|
-
@return: Yield an L{Intersector5Tuple}C{(A, B, sAB, aAB, c)} for
|
|
570
|
-
every intersection found.
|
|
571
|
-
|
|
572
|
-
@see: Methods L{Next5} for further details.
|
|
573
|
-
'''
|
|
574
|
-
X = self.Closest(glA, glB, X0=X0)
|
|
575
|
-
if X is not None:
|
|
576
|
-
D = _xkwds_get(Delta, Delta=self.Delta)
|
|
577
|
-
S, _L, _abs = X, self._Line, fabs
|
|
578
|
-
while True:
|
|
579
|
-
A, B, _, _, _ = r = self._In5T(glA, glB, S, X)
|
|
580
|
-
yield r
|
|
581
|
-
if (aMax and (_abs(A.a12) > aMax or _abs(B.a12) > aMax)) or \
|
|
582
|
-
(sMax and (_abs(A.s12) > sMax or _abs(B.s12) > sMax)):
|
|
583
|
-
break
|
|
584
|
-
latA, lonA = A.lat2, A.lon2
|
|
585
|
-
latB, lonB = B.lat2, B.lon2
|
|
586
|
-
if avg:
|
|
587
|
-
latA = latB = favg(latA, latB)
|
|
588
|
-
lonA = lonB = favg(lonA, lonB)
|
|
589
|
-
X = self._Next(_L(latA, lonA, A.azi2),
|
|
590
|
-
_L(latB, lonB, B.azi2))
|
|
591
|
-
if X is None or X.L1() < D:
|
|
592
|
-
break
|
|
593
|
-
S += X.sA, X.sB
|
|
594
|
-
S.set_(sX0=X.sX0 + S.sX0)
|
|
595
|
-
|
|
596
|
-
def _Next(self, glA, glB):
|
|
597
|
-
'''(INTERNAL) Find the next intersection.
|
|
598
|
-
'''
|
|
599
|
-
X0, self._conjDist3s = _X000, glA
|
|
1088
|
+
X0, self._conjDist3s = _X000, glA # reset Property
|
|
600
1089
|
Q, d, S_, i = _XINF, INF, list(X0._nD2(self._D2)), 0
|
|
601
1090
|
while S_:
|
|
602
1091
|
X, i = self._Basic2(glA, glB, S_.pop(0), i)
|
|
@@ -623,7 +1112,18 @@ class Intersector(_NamedBase):
|
|
|
623
1112
|
T = X + (s, s * c) # NOT +=
|
|
624
1113
|
T._skip(S_, self._T2D2Delta)
|
|
625
1114
|
|
|
626
|
-
return None if Q is _XINF else Q.set_(sX0=d, iteration=q)
|
|
1115
|
+
return None if Q is _XINF else self._C(Q, glA, glB, **_C).set_(sX0=d, iteration=q)
|
|
1116
|
+
|
|
1117
|
+
def Next5(self, glA, glB, **eps1): # PYCHOK no cover
|
|
1118
|
+
'''Yield the next intersection of two I{intersecting} geodesic lines.
|
|
1119
|
+
|
|
1120
|
+
@return: An L{Intersector5Tuple}C{(A, B, sAB, aAB, c)} or C{None}
|
|
1121
|
+
if none found.
|
|
1122
|
+
|
|
1123
|
+
@see: Method L{Next} for further details.
|
|
1124
|
+
'''
|
|
1125
|
+
X = self.Next(glA, glB, **eps1)
|
|
1126
|
+
return X if X is None else self._In5T(glA, glB, X, X)
|
|
627
1127
|
|
|
628
1128
|
def _obliqDist4(self):
|
|
629
1129
|
zx = 45.0
|
|
@@ -645,7 +1145,7 @@ class Intersector(_NamedBase):
|
|
|
645
1145
|
if not dsx:
|
|
646
1146
|
break
|
|
647
1147
|
else:
|
|
648
|
-
sx, sAx, sBx = self.
|
|
1148
|
+
sx, sAx, sBx = self._cHalf, _0_5, -_1_5
|
|
649
1149
|
return sx, zx, sAx, sBx
|
|
650
1150
|
|
|
651
1151
|
def _polarB3(self, lats=False): # PYCHOK no cover
|
|
@@ -677,7 +1177,7 @@ class Intersector(_NamedBase):
|
|
|
677
1177
|
_, lat = _pD2(latx, lat2=True)
|
|
678
1178
|
sx += sx
|
|
679
1179
|
else:
|
|
680
|
-
sx = self.
|
|
1180
|
+
sx = self._cHalf
|
|
681
1181
|
return sx, latx, lat
|
|
682
1182
|
|
|
683
1183
|
def _polarDist2(self, lat1, lat2=False):
|
|
@@ -687,19 +1187,71 @@ class Intersector(_NamedBase):
|
|
|
687
1187
|
lat1 = gl.Position(s, outmask=Caps.LATITUDE).lat2
|
|
688
1188
|
return s, lat1
|
|
689
1189
|
|
|
690
|
-
def _Position(self, gl, s
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1190
|
+
def _Position(self, gl, s):
|
|
1191
|
+
return gl.Position(s, outmask=Caps._STD_LINE)
|
|
1192
|
+
|
|
1193
|
+
def Segment(self, glA, glB, proven=None, raiser=True, **_C):
|
|
1194
|
+
'''Find the intersection between two geodesic line segments.
|
|
1195
|
+
|
|
1196
|
+
@kwarg proven: Conjecture is that whenever two geodesic line
|
|
1197
|
+
segments intersect, the intersection is the
|
|
1198
|
+
one closest to the mid-points of segments.
|
|
1199
|
+
If so, use C{B{proven}=True}, otherwise find
|
|
1200
|
+
intersections on the segments and specify
|
|
1201
|
+
C{B{proven}=None} to return the first or
|
|
1202
|
+
C{B{proven}=False} the closest (C{bool}).
|
|
1203
|
+
@kwarg raiser: If C{True}, check that B{C{glA}} and B{C{glB}}
|
|
1204
|
+
are an 4-C{args} L{Line<Intersector.Line>} or
|
|
1205
|
+
C{InverseLine} (C{bool}).
|
|
1206
|
+
@kwarg _C: If C{True}, include the lat-/longitudes C{latA},
|
|
1207
|
+
C{lonA}, C{latB}, C{lonB} of and distances C{sAB}
|
|
1208
|
+
and C{aSB} between the intersections.
|
|
1209
|
+
|
|
1210
|
+
@return: The intersection of the segments (L{XDict}) with
|
|
1211
|
+
indicators C{kA}, C{kB} and C{k} set or if no
|
|
1212
|
+
intersection is found, C{None}.
|
|
697
1213
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1214
|
+
@raise GeodesicError: Geodesic line B{C{glA}} or B{C{glB}}
|
|
1215
|
+
invalid, incompatible, ill-configured or
|
|
1216
|
+
not an C{InverseLine} or 4-C{args Line}.
|
|
1217
|
+
|
|
1218
|
+
@raise IntersectionError: No convergence.
|
|
1219
|
+
|
|
1220
|
+
@see: Method L{Middle<Intersector.Middle>} for further details.
|
|
701
1221
|
'''
|
|
702
|
-
|
|
1222
|
+
X0, sA, sB = self._middle3(glA, glB, raiser)
|
|
1223
|
+
Q = self.Closest(glA, glB, X0) # to X0
|
|
1224
|
+
if Q is not None:
|
|
1225
|
+
if Q.c: # anti-/parallel
|
|
1226
|
+
Q._fixSegment(sA, sB)
|
|
1227
|
+
# are rectangle [sA, sB] corners further from X0 than Q?
|
|
1228
|
+
d0 = X0.L1(Q)
|
|
1229
|
+
if Q._outSide(sA, sB) and d0 <= X0.L1() and not proven:
|
|
1230
|
+
i = Q.iteration
|
|
1231
|
+
for T in X0._corners(sA, sB, self._T2):
|
|
1232
|
+
X, i = self._Basic2(glA, glB, T, i)
|
|
1233
|
+
X = T._fixCoincident(X)
|
|
1234
|
+
if not X._outSide(sA, sB):
|
|
1235
|
+
d = X0.L1(X)
|
|
1236
|
+
if d < d0 or proven is None:
|
|
1237
|
+
Q, d0 = X, d
|
|
1238
|
+
if proven is None:
|
|
1239
|
+
break
|
|
1240
|
+
Q.set_(iteration=i)
|
|
1241
|
+
|
|
1242
|
+
Q = self._C(Q, glA, glB, **_C).set_(sX0=d0)
|
|
1243
|
+
return Q
|
|
1244
|
+
|
|
1245
|
+
def Segment5(self, glA, glB, **proven_raiser):
|
|
1246
|
+
'''Find the intersection between two geodesic line segments.
|
|
1247
|
+
|
|
1248
|
+
@return: An L{Intersector5Tuple}C{(A, B, sAB, aAB, c)}
|
|
1249
|
+
or C{None} if none found.
|
|
1250
|
+
|
|
1251
|
+
@see: Method L{Segment} for further details.
|
|
1252
|
+
'''
|
|
1253
|
+
X = self.Segment(glA, glB, **proven_raiser)
|
|
1254
|
+
return X if X is None else self._In5T(glA, glB, X, X, k2=True)
|
|
703
1255
|
|
|
704
1256
|
def _Spherical(self, glA, glB, S):
|
|
705
1257
|
'''(INTERNAL) Get solution based from a spherical triangle.
|
|
@@ -735,10 +1287,10 @@ class Intersector(_NamedBase):
|
|
|
735
1287
|
# in {sa, sb} * sz; this is probably not necessary].
|
|
736
1288
|
# Definitely need to treat sz < 0 (z > PI*R) correctly in
|
|
737
1289
|
# order to avoid some convergence failures in _Basic2.
|
|
738
|
-
sA = atan2(sb * sz, sb * ca * cz -
|
|
739
|
-
sB = atan2(sa * sz, -sa * cb * cz +
|
|
1290
|
+
sA = atan2(sb * sz, sb * ca * cz - sa * cb) * self.R
|
|
1291
|
+
sB = atan2(sa * sz, -sa * cb * cz + sb * ca) * self.R
|
|
740
1292
|
c = 0
|
|
741
|
-
return
|
|
1293
|
+
return XDict_(sA, sB, c) # no ._Delto
|
|
742
1294
|
|
|
743
1295
|
@Property_RO
|
|
744
1296
|
def _T2D1Delta(self):
|
|
@@ -753,7 +1305,7 @@ class Intersector(_NamedBase):
|
|
|
753
1305
|
|
|
754
1306
|
@Property_RO
|
|
755
1307
|
def _Tol(self): # convergence tolerance
|
|
756
|
-
return self.
|
|
1308
|
+
return self._cHalf * pow(EPS, 0.75) # _0_75
|
|
757
1309
|
|
|
758
1310
|
def toStr(self, **prec_sep_name): # PYCHOK signature
|
|
759
1311
|
'''Return this C{Intersector} as string.
|
|
@@ -765,13 +1317,16 @@ class Intersector(_NamedBase):
|
|
|
765
1317
|
'''
|
|
766
1318
|
return self._instr(props=(Intersector.geodesic,), **prec_sep_name)
|
|
767
1319
|
|
|
768
|
-
def _xLines(self, glA, glB):
|
|
1320
|
+
def _xLines(self, glA, glB, s13=False):
|
|
769
1321
|
# check two geodesic lines vs this geodesic
|
|
770
1322
|
C, gls = Caps.LINE_CAPS, dict(glA=glA, glB=glB)
|
|
771
1323
|
_xinstanceof(*self._GeodesicLines, **gls)
|
|
772
1324
|
for n, gl in gls.items():
|
|
773
1325
|
try:
|
|
774
1326
|
_xgeodesics(gl.geodesic, self.geodesic)
|
|
1327
|
+
if s13 and not isfinite(gl.s13): # or not gl.caps & Caps.DISTANCE_IN
|
|
1328
|
+
t = gl.geodesic.InverseLine.__name__
|
|
1329
|
+
raise TypeError(_not_(_an(t)))
|
|
775
1330
|
c = gl.caps & C
|
|
776
1331
|
if c != C: # not gl.caps_(C)
|
|
777
1332
|
c, C, x = map1(bin, c, C, _xor(c, C))
|
|
@@ -781,21 +1336,47 @@ class Intersector(_NamedBase):
|
|
|
781
1336
|
raise GeodesicError(n, gl, cause=x)
|
|
782
1337
|
|
|
783
1338
|
|
|
784
|
-
class
|
|
1339
|
+
class Intersectool5Tuple(_NamedTuple):
|
|
1340
|
+
'''5-Tuple C{(A, B, sAB, aAB, c)} with C{A} and C{B} the C{Position}
|
|
1341
|
+
of the intersection on each geodesic line, the distance C{sAB}
|
|
1342
|
+
between C{A} and C{B} in C{meter}, the angular distance C{aAB} in
|
|
1343
|
+
C{degrees} and coincidence indicator C{c} (C{int}), see L{XDict}.
|
|
1344
|
+
|
|
1345
|
+
@note: C{A} and C{B} are each a C{GDict} with C{lat1}, C{lon1} and
|
|
1346
|
+
C{azi1} or C{lat2}, C{lon2} from the geodesic line C{glA}
|
|
1347
|
+
respectively C{glB} and the intersection location in C{latX},
|
|
1348
|
+
C{lonX}, distance C{s1X} in C{meter} and angular distance
|
|
1349
|
+
C{a1M} in C{degrees} and the segment indicator C{kX}. See
|
|
1350
|
+
L{XDict} for more details.
|
|
1351
|
+
'''
|
|
1352
|
+
_Names_ = (_A_, _B_, _sAB_, 'aAB', _c_)
|
|
1353
|
+
_Units_ = (_Pass, _Pass, Meter, Degrees, Int)
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
class Intersector5Tuple(Intersectool5Tuple):
|
|
785
1357
|
'''5-Tuple C{(A, B, sAB, aAB, c)} with C{A} and C{B} the C{Position}
|
|
786
1358
|
of the intersection on each geodesic line, the distance C{sAB}
|
|
787
1359
|
between C{A} and C{B} in C{meter}, angular distance C{aAB} in
|
|
788
|
-
C{degrees} and coincidence indicator C{c} (C{int}), see L{
|
|
1360
|
+
C{degrees} and coincidence indicator C{c} (C{int}), see L{XDict}.
|
|
789
1361
|
|
|
790
1362
|
@note: C{A} and C{B} are each a C{GeodesicLine...Position} for
|
|
791
1363
|
C{outmask=Caps.STANDARD} with the intersection location in
|
|
792
|
-
C{
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
C{azi0}, C{s10} and C{a10}.
|
|
1364
|
+
C{latX}, C{lonX}, azimuth in C{aziX}, distance C{s1X} in
|
|
1365
|
+
C{meter} and angular distance C{a1X} in C{degrees} and the
|
|
1366
|
+
segment indicator C{kX}. See L{XDict} for more details.
|
|
796
1367
|
'''
|
|
797
|
-
_Names_ =
|
|
798
|
-
|
|
1368
|
+
_Names_ = Intersectool5Tuple._Names_
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
class Middle5Tuple(Intersectool5Tuple):
|
|
1372
|
+
'''5-Tuple C{(A, B, sMM, aMM, c)} with C{A} and C{B} the I{line segments}
|
|
1373
|
+
including the mid-point location in C{latM}, C{lonM}, distance C{s1M}
|
|
1374
|
+
in C{meter} and angular distance C{a1M} in C{degrees}, the distance
|
|
1375
|
+
between both mid-points C{sMM} in C{meter} and angular distance C{aMM}
|
|
1376
|
+
in C{degrees} and coincidence indicator C{c} (C{int}). See L{XDict}
|
|
1377
|
+
for more details.
|
|
1378
|
+
'''
|
|
1379
|
+
_Names_ = (_A_, _B_, 'sMM', 'aMM', _c_)
|
|
799
1380
|
|
|
800
1381
|
|
|
801
1382
|
class _List(list):
|
|
@@ -823,68 +1404,274 @@ class _List(list):
|
|
|
823
1404
|
self.append(X)
|
|
824
1405
|
return X.sX0
|
|
825
1406
|
|
|
826
|
-
def
|
|
1407
|
+
def sorter(self, sMaX0, dot_C, glA, glB, **_C):
|
|
827
1408
|
# trim and sort the X items
|
|
828
1409
|
|
|
829
|
-
def _key(
|
|
830
|
-
|
|
831
|
-
return k # rank of X
|
|
1410
|
+
def _key(X):
|
|
1411
|
+
return X.sX0 # rank of X
|
|
832
1412
|
|
|
833
|
-
for X
|
|
834
|
-
|
|
1413
|
+
t = (X for X in self if X.sX0 <= sMaX0)
|
|
1414
|
+
for X in sorted(t, key=_key):
|
|
1415
|
+
yield dot_C(X, glA, glB, **_C) if _C else X
|
|
835
1416
|
|
|
836
|
-
def trim(self, X0, sMax):
|
|
837
|
-
# trim and yield 2-tuple (X, rank)
|
|
838
|
-
a, b, _eu = X0.sA, X0.sB, euclid
|
|
839
1417
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1418
|
+
def _L1(a, b):
|
|
1419
|
+
'''(INTERNAL) Return the I{L1} distance.
|
|
1420
|
+
'''
|
|
1421
|
+
return fabs(a) + fabs(b)
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
def _llz2G(G, gl, il):
|
|
1425
|
+
'''(INTERNAL) Set C{InverseLine} attrs in C{G}, a C{GDict}.
|
|
1426
|
+
'''
|
|
1427
|
+
return G.set_(lat1=gl.lat1, lon1=gl.lon1, azi1=il.azi1, a12=il.a13, # .Arc()
|
|
1428
|
+
lat2=gl.lat2, lon2=gl.lon2, azi2=il.azi2, s12=il.s13) # .Distance()
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
if __name__ == '__main__': # MCCABE 14
|
|
1432
|
+
|
|
1433
|
+
from pygeodesy import printf
|
|
1434
|
+
|
|
1435
|
+
def _main(args):
|
|
1436
|
+
|
|
1437
|
+
from pygeodesy import GeodesicExact
|
|
1438
|
+
from pygeodesy.internals import _plural, _usage
|
|
1439
|
+
from pygeodesy.interns import _COLONSPACE_, _DASH_, _DOT_, \
|
|
1440
|
+
_EQUAL_, _i_, _n_, _version_, _X_
|
|
1441
|
+
import re
|
|
1442
|
+
|
|
1443
|
+
class XY0(Float):
|
|
1444
|
+
pass
|
|
1445
|
+
|
|
1446
|
+
def _opts(_h): # for _usage()
|
|
1447
|
+
ll4 = ' latA1 lonA1'
|
|
1448
|
+
ll4 += ll4.replace('1', '2')
|
|
1449
|
+
ll4 += ll4.replace(_A_, _B_)
|
|
1450
|
+
llz = _SPACE_(NN, _latA_, _lonA_, 'aziA')
|
|
1451
|
+
llz2 = llz + llz.replace(_A_, _B_)
|
|
1452
|
+
return dict(opts='-Verbose|V--version|v--help|h--Tool|T--Check|C-R meter-',
|
|
1453
|
+
alts=((_c_ + llz2),
|
|
1454
|
+
(_i_ + ll4),
|
|
1455
|
+
(_m_ + ll4),
|
|
1456
|
+
(_M_ + ll4),
|
|
1457
|
+
(_n_ + llz + ' aziB'),
|
|
1458
|
+
('o' + llz2 + ' x0 y0')),
|
|
1459
|
+
help=_h if isinstance(_h, str) else NN)
|
|
1460
|
+
|
|
1461
|
+
def _starts(opt, arg, n=0):
|
|
1462
|
+
return opt.startswith(arg) and len(arg.lstrip(_DASH_)) > n
|
|
1463
|
+
|
|
1464
|
+
_isopt = re.compile('^[-]+[a-z]*$', flags=re.IGNORECASE).match
|
|
1465
|
+
|
|
1466
|
+
I = Intersector(GeodesicExact()) # PYCHOK I
|
|
1467
|
+
M = m = _R = None
|
|
1468
|
+
_T = _V = _h = _C = False
|
|
1469
|
+
|
|
1470
|
+
while args and _isopt(args[0]):
|
|
1471
|
+
arg = args.pop(0)
|
|
1472
|
+
if arg == _C__ or _starts('--Check', arg):
|
|
1473
|
+
_C = True
|
|
1474
|
+
elif arg == _c__:
|
|
1475
|
+
M, m = I.Closest, 6 # latA lonA aziA latB lonB aziB
|
|
1476
|
+
elif arg == '-h' or _starts('--help', arg):
|
|
1477
|
+
_h = args[0] if args and _isopt(args[0]) else True
|
|
1478
|
+
elif arg == _i__:
|
|
1479
|
+
M, m = I.Segment, 8 # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
|
|
1480
|
+
elif arg == '-m':
|
|
1481
|
+
M, m = I.Middle, 8 # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
|
|
1482
|
+
_R = None
|
|
1483
|
+
elif arg == '-M':
|
|
1484
|
+
M, m = I.Middle5, 8 # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
|
|
1485
|
+
_R = _C = None
|
|
1486
|
+
elif arg == _n__:
|
|
1487
|
+
M, m = I.Next, 4 # latA lonA aziA aziB
|
|
1488
|
+
elif arg == _o__:
|
|
1489
|
+
M, m = I.Closest, 8 # latA lonA aziA latB lonB aziB x0 y0
|
|
1490
|
+
elif arg == _R__ and args:
|
|
1491
|
+
_R = args.pop(0)
|
|
1492
|
+
elif arg == '-T' or _starts('--Tool', arg):
|
|
1493
|
+
I = Intersectool() # PYCHOK I
|
|
1494
|
+
if _V:
|
|
1495
|
+
I.verbose = True
|
|
1496
|
+
if I.IntersectTool in (_PYGEODESY_INTERSECTTOOL_, None): # not set
|
|
1497
|
+
I.IntersectTool = '/opt/local/bin/IntersectTool' # '/opt/local/Cellar/geographiclib/2.3/bin/IntersectTool' # HomeBrew
|
|
1498
|
+
M, _T = None, True
|
|
1499
|
+
elif arg == '-V' or _starts('--Verbose', arg):
|
|
1500
|
+
_V = True
|
|
1501
|
+
if _T:
|
|
1502
|
+
I.verbose = True
|
|
1503
|
+
elif arg == '-v' or _starts('--version', arg):
|
|
1504
|
+
printf(_COLONSPACE_(*((_version_, I.version) if _T else
|
|
1505
|
+
(__version__, repr(I)))))
|
|
1506
|
+
else:
|
|
1507
|
+
raise ValueError('invalid option %r' % (arg,))
|
|
1508
|
+
|
|
1509
|
+
if _h or M is None:
|
|
1510
|
+
printf(_usage(__file__, **_opts(_h)), nl=1)
|
|
1511
|
+
# exit(0)
|
|
1512
|
+
else:
|
|
1513
|
+
n = len(args)
|
|
1514
|
+
if n < m:
|
|
1515
|
+
n = _plural('only %s arg' % (n,), n) if n else 'no args'
|
|
1516
|
+
raise ValueError('%s, need %s' % (n, m))
|
|
1517
|
+
args[:] = args[:m]
|
|
1518
|
+
|
|
1519
|
+
kwds = dict(_C=True) if _C else {}
|
|
1520
|
+
if M == I.Next: # -n
|
|
1521
|
+
# get latA lonA aziA latA lonA aziB
|
|
1522
|
+
args[3:] = args[:2] + args[3:4]
|
|
1523
|
+
elif M == I.Closest and m > 6: # -o
|
|
1524
|
+
y0 = Meter(y0=args.pop())
|
|
1525
|
+
x0 = Meter(x0=args.pop())
|
|
1526
|
+
kwds.update(X0=XDict_(x0, y0))
|
|
1527
|
+
if _R:
|
|
1528
|
+
m = Meter_(_R, name=_R__, low=0)
|
|
1529
|
+
kwds.update(sMaX0=m)
|
|
1530
|
+
M = I.All
|
|
1531
|
+
|
|
1532
|
+
n = len(args) // 2
|
|
1533
|
+
glA = I.Line(*args[:n])
|
|
1534
|
+
glB = I.Line(*args[n:])
|
|
1535
|
+
|
|
1536
|
+
m = _DOT_(I.__class__.__name__, M.__name__)
|
|
1537
|
+
if _V:
|
|
1538
|
+
X = _SPACE_(_X_, _EQUAL_, m)
|
|
1539
|
+
printf(unstr(X, glA, glB, **kwds))
|
|
1540
|
+
|
|
1541
|
+
X = M(glA, glB, **kwds)
|
|
1542
|
+
if X is None or isinstance(X, (XDict, tuple)):
|
|
1543
|
+
printf(_COLONSPACE_(m, repr(X)))
|
|
1544
|
+
else:
|
|
1545
|
+
for i, X in enumerate(X):
|
|
1546
|
+
printf(_COLONSPACE_(Fmt.INDEX(m, i), repr(X)))
|
|
1547
|
+
|
|
1548
|
+
from sys import argv, stderr
|
|
1549
|
+
try:
|
|
1550
|
+
if len(argv) == 2 and argv[1] == _HASH_:
|
|
1551
|
+
from pygeodesy.internals import _usage_argv
|
|
1552
|
+
|
|
1553
|
+
s = _SPACE_(*_usage_argv(__file__))
|
|
1554
|
+
for t in ('-h', '-h -n',
|
|
1555
|
+
'-c 0 0 45 40 10 135',
|
|
1556
|
+
'-C -c 0 0 45 40 10 135',
|
|
1557
|
+
'-T -R 2.6e7 -c 0 0 45 40 10 135',
|
|
1558
|
+
'-c 50 -4 -147.7 0 0 90',
|
|
1559
|
+
'-C -c 50 -4 -147.7 0 0 90',
|
|
1560
|
+
'# % echo 0 0 10 10 50 -4 50S 4W | IntersectTool -i -p 0 -C',
|
|
1561
|
+
'# -631414 5988887 0 -3',
|
|
1562
|
+
'# -4.05187 -4.00000 -4.05187 -4.00000 0',
|
|
1563
|
+
'-C -m 0 0 10 10 50 -4 50S 4W',
|
|
1564
|
+
'-M 0 0 10 10 50 -4 50S 4W',
|
|
1565
|
+
'-i 0 0 10 10 50 -4 50S 4W',
|
|
1566
|
+
'-T -i 0 0 10 10 50 -4 50S 4W',
|
|
1567
|
+
'-C -i 0 0 10 10 50 -4 50S 4W',
|
|
1568
|
+
'-T -C -i 0 0 10 10 50 -4 50S 4W',
|
|
1569
|
+
'-V -T -i 0 0 10 10 50 -4 -50 -4',
|
|
1570
|
+
'-C -R 4e7 -c 50 -4 -147.7 0 0 90',
|
|
1571
|
+
'-T -C -R 4e7 -c 50 -4 -147.7 0 0 90',
|
|
1572
|
+
'-R 4e7 -i 0 0 10 10 50 -4 -50 -4',
|
|
1573
|
+
'-T -R 4e7 -i 0 0 10 10 50 -4 -50 -4'):
|
|
1574
|
+
if t.startswith(_HASH_):
|
|
1575
|
+
printf(t, nl=int(t[2] == '%'))
|
|
1576
|
+
else:
|
|
1577
|
+
printf(_SPACE_(_HASH_, s, t), nl=1)
|
|
1578
|
+
argv[1:] = t = t.split()
|
|
1579
|
+
_main(t)
|
|
1580
|
+
else:
|
|
1581
|
+
_main(argv[1:])
|
|
1582
|
+
|
|
1583
|
+
except Exception as x:
|
|
1584
|
+
x = _SPACE_(x, NN, _HASH_, *argv)
|
|
1585
|
+
printf(x, file=stderr, nl=1)
|
|
1586
|
+
if '-V' in x or _MODS.errors.exception_chaining():
|
|
1587
|
+
raise
|
|
1588
|
+
exit(1)
|
|
1589
|
+
|
|
1590
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m pygeodesy.geodesici -h
|
|
1591
|
+
#
|
|
1592
|
+
# usage: python3 -m ....pygeodesy.geodesici [--Verbose | -V] [--version | -v] [--help | -h] [--Tool | -T] [--Check | -C] [-R meter]
|
|
1593
|
+
# [-c latA lonA aziA latB lonB aziB |
|
|
1594
|
+
# -i latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
|
|
1595
|
+
# -m latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
|
|
1596
|
+
# -M latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
|
|
1597
|
+
# -n latA lonA aziA aziB |
|
|
1598
|
+
# -o latA lonA aziA latB lonB aziB x0 y0]
|
|
1599
|
+
|
|
1600
|
+
# % python3 -m ....pygeodesy.geodesici -h -n
|
|
1601
|
+
#
|
|
1602
|
+
# usage: python3 -m ....pygeodesy.geodesici -n latA lonA aziA aziB
|
|
1603
|
+
|
|
1604
|
+
# % python3 -m ....pygeodesy.geodesici -c 0 0 45 40 10 135
|
|
1605
|
+
# Intersector.Closest: XDict(c=0, sA=3862290.547855, sB=2339969.547699, sX0=6202260.095554)
|
|
1606
|
+
|
|
1607
|
+
# % python3 -m ....pygeodesy.geodesici -C -c 0 0 45 40 10 135
|
|
1608
|
+
# Intersector.Closest: XDict(aAB=0.0, c=0, latA=23.875306, latB=23.875306, lonA=26.094096, lonB=26.094096, sA=3862290.547855, sAB=0.0, sB=2339969.547699, sX0=6202260.095554)
|
|
1609
|
+
|
|
1610
|
+
# % env PYGEODESY_INTERSECTTOOL=...python3 -m ....pygeodesy.geodesici -T -R 2.6e7 -c 0 0 45 40 10 135
|
|
1611
|
+
# Intersectool.All[0]: XDict(c=0, sA=3862290.547855, sB=2339969.547699, sX0=6202260.095554)
|
|
1612
|
+
|
|
1613
|
+
# % python3 -m ....pygeodesy.geodesici -c 50 -4 -147.7 0 0 90
|
|
1614
|
+
# Intersector.Closest: XDict(c=0, sA=6058048.653081, sB=-3311252.995823, sX0=9369301.648903)
|
|
1615
|
+
|
|
1616
|
+
# % python3 -m ....pygeodesy.geodesici -C -c 50 -4 -147.7 0 0 90
|
|
1617
|
+
# Intersector.Closest: XDict(aAB=0.0, c=0, latA=0.0, latB=-0.0, lonA=-29.745492, lonB=-29.745492, sA=6058048.653081, sAB=0.0, sB=-3311252.995823, sX0=9369301.648903)
|
|
1618
|
+
|
|
1619
|
+
# % echo 0 0 10 10 50 -4 50S 4W | IntersectTool -i -p 0 -C
|
|
1620
|
+
# -631414 5988887 0 -3
|
|
1621
|
+
# -4.05187 -4.00000 -4.05187 -4.00000 0
|
|
1622
|
+
|
|
1623
|
+
# % python3 -m ....pygeodesy.geodesici -C -m 0 0 10 10 50 -4 50S 4W
|
|
1624
|
+
# Intersector.Middle: XDict(aMM=10.262308, c=0, latA=5.019509, latB=0.036282, lonA=4.961883, lonB=-4.0, sA=782554.549609, sB=5536835.161499, sMM=1138574.546746, sX0=0.0)
|
|
1625
|
+
|
|
1626
|
+
# % python3 -m ....pygeodesy.geodesici -M 0 0 10 10 50 -4 50S 4W
|
|
1627
|
+
# Intersector.Middle5: Middle5Tuple(A=GDict(a12=14.106434, a1M=7.053396, azi1=44.75191, azi2=45.629037, aziM=44.969535, lat1=0.0, lat2=10.0, latM=5.019509, lon1=0.0, lon2=10.0, lonM=4.961883, s12=1565109.099218, s1M=782554.549609), B=GDict(a12=99.810444, a1M=49.869061, azi1=180.0, azi2=180.0, aziM=180.0, lat1=50.0, lat2=-50.0, latM=0.036282, lon1=-4.0, lon2=-4.0, lonM=-4.0, s12=11073670.322999, s1M=5536835.161499), sMM=1138574.546746, aMM=10.262308, c=0)
|
|
1628
|
+
|
|
1629
|
+
# % python3 -m ....pygeodesy.geodesici -i 0 0 10 10 50 -4 50S 4W
|
|
1630
|
+
# Intersector.Segment: XDict(c=0, k=-3, kA=-1, kB=0, sA=-631414.26877, sB=5988887.278435, sX0=1866020.935315)
|
|
1631
|
+
|
|
1632
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m ....pygeodesy.geodesici -T -i 0 0 10 10 50 -4 50S 4W
|
|
1633
|
+
# Intersectool.Segment: XDict(c=0, k=-3, kA=-1, kB=0, sA=-631414.26877, sB=5988887.278435)
|
|
1634
|
+
|
|
1635
|
+
# % python3 -m ....pygeodesy.geodesici -C -i 0 0 10 10 50 -4 50S 4W
|
|
1636
|
+
# Intersector.Segment: XDict(aAB=0.0, c=0, k=-3, kA=-1, kB=0, latA=-4.051871, latB=-4.051871, lonA=-4.0, lonB=-4.0, sA=-631414.26877, sAB=0.0, sB=5988887.278435, sX0=1866020.935315)
|
|
1637
|
+
|
|
1638
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m ....pygeodesy.geodesici -T -C -i 0 0 10 10 50 -4 50S 4W
|
|
1639
|
+
# Intersectool.Segment: XDict(c=0, k=-3, kA=-1, kB=0, latA=-4.051871, latB=-4.051871, lonA=-4.0, lonB=-4.0, sA=-631414.26877, sAB=0.0, sB=5988887.278435)
|
|
1640
|
+
|
|
1641
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m ....pygeodesy.geodesici -V -T -i 0 0 10 10 50 -4 -50 -4
|
|
1642
|
+
# Intersectool@1: /opt/local/bin/IntersectTool --version (invoke)
|
|
1643
|
+
# Intersectool@1: '/opt/local/bin/IntersectTool: GeographicLib version 2.3' (0)
|
|
1644
|
+
# Intersectool@1: /opt/local/bin/IntersectTool: GeographicLib version 2.3 (0)
|
|
1645
|
+
# X = Intersectool.Segment(GDict(lat1=0.0, lat2=10.0, lon1=0.0, lon2=10.0), GDict(lat1=50.0, lat2=-50.0, lon1=-4.0, lon2=-4.0))
|
|
1646
|
+
# Intersectool@2: /opt/local/bin/IntersectTool -E -p 10 -i \ 0.0 0.0 10.0 10.0 50.0 -4.0 -50.0 -4.0 (Segment)
|
|
1647
|
+
# Intersectool@2: '-631414.2687702414 5988887.2784352796 0 -3' (0)
|
|
1648
|
+
# Intersectool@2: sA=-631414.2687702414, sB=5988887.2784352796, c=0, k=-3 (0)
|
|
1649
|
+
# Intersectool.Segment: XDict(c=0, k=-3, kA=-1, kB=0, sA=-631414.26877, sB=5988887.278435)
|
|
1650
|
+
|
|
1651
|
+
# % python3 -m ....pygeodesy.geodesici -C -R 4e7 -c 50 -4 -147.7 0 0 90
|
|
1652
|
+
# Intersector.All[0]: XDict(aAB=0.0, c=0, latA=0.0, latB=-0.0, lonA=-29.745492, lonB=-29.745492, sA=6058048.653081, sAB=0.0, sB=-3311252.995823, sX0=9369301.648903)
|
|
1653
|
+
# Intersector.All[1]: XDict(aAB=0.0, c=0, latA=0.0, latB=0.0, lonA=150.046964, lonB=150.046964, sA=-13941907.021445, sAB=0.0, sB=16703151.659744, sX0=30645058.681189)
|
|
1654
|
+
# Intersector.All[2]: XDict(aAB=0.0, c=0, latA=-0.0, latB=-0.0, lonA=-30.16058, lonB=-30.16058, sA=-33941862.69597, sAB=0.0, sB=-3357460.370268, sX0=37299323.066238)
|
|
1655
|
+
# Intersector.All[3]: XDict(aAB=0.0, c=0, latA=-0.0, latB=0.0, lonA=150.046964, lonB=150.046964, sA=-13941907.021445, sAB=0.0, sB=-23371865.025835, sX0=37313772.047279)
|
|
1656
|
+
|
|
1657
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m ....pygeodesy.geodesici -T -C -R 4e7 -c 50 -4 -147.7 0 0 90
|
|
1658
|
+
# Intersectool.All[0]: XDict(c=0, latA=-0.0, latB=-0.0, lonA=-29.745492, lonB=-29.745492, sA=6058048.653081, sAB=0.0, sB=-3311252.995823, sX0=9369301.648903)
|
|
1659
|
+
# Intersectool.All[1]: XDict(c=0, latA=0.0, latB=0.0, lonA=150.046964, lonB=150.046964, sA=-13941907.021445, sAB=0.0, sB=16703151.659744, sX0=30645058.681189)
|
|
1660
|
+
# Intersectool.All[2]: XDict(c=0, latA=-0.0, latB=-0.0, lonA=-30.16058, lonB=-30.16058, sA=-33941862.69597, sAB=0.0, sB=-3357460.370268, sX0=37299323.066238)
|
|
1661
|
+
# Intersectool.All[3]: XDict(c=0, latA=-0.0, latB=0.0, lonA=150.046964, lonB=150.046964, sA=-13941907.021445, sAB=0.0, sB=-23371865.025835, sX0=37313772.047279)
|
|
1662
|
+
|
|
1663
|
+
# % python3 -m ....pygeodesy.geodesici -R 4e7 -i 0 0 10 10 50 -4 -50 -4
|
|
1664
|
+
# Intersector.All[0]: XDict(c=0, sA=-631414.26877, sB=5988887.278435, sX0=1866020.935315)
|
|
1665
|
+
# Intersector.All[1]: XDict(c=0, sA=19422725.117572, sB=-14062417.105648, sX0=38239422.83511)
|
|
1666
|
+
# Intersector.All[2]: XDict(c=0, sA=19422725.117572, sB=25945445.811603, sX0=39048781.218067)
|
|
1667
|
+
# Intersector.All[3]: XDict(c=0, sA=39476927.464575, sB=5894074.699478, sX0=39051612.452944)
|
|
1668
|
+
|
|
1669
|
+
# % env PYGEODESY_INTERSECTTOOL=... python3 -m ....pygeodesy.geodesici -T -R 4e7 -i 0 0 10 10 50 -4 -50 -4
|
|
1670
|
+
# Intersectool.All[0]: XDict(c=0, sA=-631414.26877, sB=5988887.278435, sX0=1862009.05513)
|
|
1671
|
+
# Intersectool.All[1]: XDict(c=0, sA=19422725.117572, sB=-14062417.105648, sX0=38243434.715295)
|
|
1672
|
+
# Intersectool.All[2]: XDict(c=0, sA=19422725.117572, sB=25945445.811603, sX0=39044769.337882)
|
|
1673
|
+
# Intersectool.All[3]: XDict(c=0, sA=39476927.464575, sB=5894074.699478, sX0=39047600.57276)
|
|
1674
|
+
|
|
888
1675
|
|
|
889
1676
|
# **) MIT License
|
|
890
1677
|
#
|