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