pygeodesy 24.7.7__py2.py3-none-any.whl → 24.8.4__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,21 +1,21 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
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.
4
+ u'''Classes L{Intersectool} and L{Intersector} to find the intersections of two geodesic lines or line segments.
7
5
 
8
- L{Intersectool} and L{Intersector} methods C{All}, C{Closest}, C{Next} and C{Segment} produce
9
-
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}.
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>}.
13
8
 
14
9
  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}.
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"}.
17
14
 
18
- Set env variable C{PYGEODESY_INTERSECTTOOL} to the (fully qualified) path of the C{IntersectTool} executable.
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.
19
19
 
20
20
  For more details, see the C++ U{GeographicLib<https://GeographicLib.SourceForge.io/C++/doc/index.html>}
21
21
  documentation, I{Charles F.F. Karney}'s paper U{Geodesics intersections<https://arxiv.org/abs/2308.00495>}
@@ -32,36 +32,38 @@ from pygeodesy.constants import EPS, INF, INT0, PI, PI2, PI_4, \
32
32
  _90_0, isfinite
33
33
  from pygeodesy.ellipsoids import _EWGS84, Fmt, unstr
34
34
  from pygeodesy.errors import GeodesicError, IntersectionError, _an, \
35
- _xgeodesics, _xkwds_get, _xkwds_kwds
35
+ _xgeodesics, _xkwds_get, _xkwds_kwds, \
36
+ _xkwds_pop2
36
37
  # from pygeodesy.errors import exception_chaining # _MODS
37
38
  from pygeodesy.fmath import euclid, fdot
38
39
  from pygeodesy.fsums import Fsum, fsum1_, _ceil
39
40
  from pygeodesy.interns import NN, _A_, _B_, _c_, _COMMASPACE_, \
40
41
  _HASH_, _M_, _not_, _SPACE_, _too_
41
- from pygeodesy.interns import _m_ # PYCHOK used!
42
42
  from pygeodesy.karney import Caps, _diff182, GDict, _sincos2de
43
- from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, \
43
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, \
44
44
  _getenv, _PYGEODESY_INTERSECTTOOL_
45
45
  from pygeodesy.named import ADict, _NamedBase, _NamedTuple, _Pass
46
+ # from pygeodesy.namedTuples import _LL4Tuple # _MODS
46
47
  from pygeodesy.props import deprecated_method, Property, \
47
- Property_RO, property_RO
48
+ Property_RO, property_RO, property_ROver
48
49
  from pygeodesy.solveBase import _SolveCapsBase, pairs
49
50
  # from pygeodesy.streprs import pairs # from .solveBase
50
51
  # from pygeodesy.streprs import Fmt, unstr # from .ellipsoids
51
- from pygeodesy.units import Degrees, Float, Int, Lat, Lon, \
52
- Meter, Meter_
52
+ from pygeodesy.units import Azimuth as Azi, Degrees, Float, Int, \
53
+ _isDegrees, Lat, Lon, Meter, Meter_
53
54
  from pygeodesy.utily import sincos2, atan2, fabs, radians
54
55
 
55
56
  # from math import atan2, ceil as _ceil, fabs, radians # .fsums, .utily
56
57
 
57
58
  __all__ = _ALL_LAZY.geodesici
58
- __version__ = '24.07.09'
59
+ __version__ = '24.07.25'
59
60
 
60
61
  _0t = 0, # int
61
62
  _1_1t = -1, +1
62
63
  _1_0_1t = -1, 0, +1
64
+ _aAB_ = 'aAB'
63
65
  _c__ = '-c' # PYCHOK used!
64
- _C__ = '-C' # PYCHOK used!
66
+ _cWGS84 = _EWGS84.a * PI2 # outer circumference
65
67
  _EPS3 = EPS * _3_0
66
68
  _EPSr5 = pow(EPS, 0.2) # PYCHOK used! 7.4e-4 or ~3"
67
69
  _i__ = '-i' # PYCHOK used!
@@ -75,12 +77,6 @@ _sX0_ = 'sX0'
75
77
  _TRIPS = 128
76
78
 
77
79
 
78
- class Azi(Degrees):
79
- '''(INTERNAL) Azimuth C{Unit}.
80
- '''
81
- pass
82
-
83
-
84
80
  class XDict(ADict):
85
81
  '''4+Item result from L{Intersectool} and L{Intersector} methods
86
82
  C{All}, C{Closest}, C{Next} and C{Segment} with the intersection
@@ -92,9 +88,14 @@ class XDict(ADict):
92
88
  line C{glA} respectively C{glB}, but C{sX0} is the I{L1-distance}
93
89
  between the intersection and the I{origin} C{X0}.
94
90
 
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
91
+ If present, distance C{sAB} and angular distance C{aAB} represent
92
+ the difference between the intersection point on geodesic lines
93
+ C{glA} and C{glB} in C{meter} respectively C{degrees}, typically
94
+ below C{5e-9 meter} or C{5 nm} and C{5e-14 degrees} or C{1 n"}.
95
+
96
+ For segments, indicators C{kA} and C{kB} are C{0} if the segments
97
+ intersect or C{-1} or C{+1} if the intersection is I{before} the
98
+ start, respectively I{after} the end of the segment, similar to
98
99
  L{Intersection3Tuple<Intersection3Tuple>}. Segment indicator
99
100
  C{k} is I{Karney}'s C{segmode}, equal C{kA * 3 + kB}.
100
101
  '''
@@ -266,6 +267,10 @@ class _IntersectBase(_NamedBase):
266
267
 
267
268
  equatoradius = a # = Requatorial
268
269
 
270
+ def All(self, glA, glB, **kwds): # PYCHOK no cover
271
+ '''(INTERNAL) I{Must be overloaded}.'''
272
+ self._notOverloaded(glA, glB, **kwds)
273
+
269
274
  @Property_RO
270
275
  def _cHalf(self): # normalizer, semi-circumference
271
276
  return self.R * PI # ~20K Km WGS84
@@ -300,12 +305,109 @@ class _IntersectBase(_NamedBase):
300
305
  '''
301
306
  return self._g
302
307
 
308
+ def _illz2G(self, G, il):
309
+ '''(INTERNAL) Set C{InverseLine} 1-/2-attrs into C{G}, a C{GDict}.
310
+ '''
311
+ try:
312
+ G.set_(lat1=il.lat1, lon1=il.lon1, azi1=il.azi1, a12=il.a13, # .Arc()
313
+ lat2=il.lat2, lon2=il.lon2, azi2=il.azi2, s12=il.s13) # .Distance()
314
+ except AttributeError:
315
+ r = il.Position(il.s13, outmask=Caps._STD_LINE) # isfinite(il.s13)
316
+ G.set_(**r)
317
+ # for n, v in r.items():
318
+ # if not hasattr(il, n):
319
+ # setattr(il, n, v)
320
+ return G
321
+
322
+ def intersect7(self, start1, end1, start2, end2, X0=_X000, aMaX0=0, sMaX0=_cWGS84,
323
+ **LatLon_and_kwds):
324
+ '''Yield the intersection points of two lines, each defined by two (ellipsoidal)
325
+ points or by an (ellipsoidal) start point and an azimuth from North.
326
+
327
+ @arg start1: Start point of the first line (C{LatLon}).
328
+ @arg end1: End point of the first line (C{LatLon}) or the azimuth at the
329
+ B{C{start1}} point (compass C{degrees360}).
330
+ @arg start2: Start point of the second line (C{LatLon}).
331
+ @arg end2: End point of the second line (C{LatLon}) or the azimuth at the
332
+ B{C{start2}} point (compass C{degrees360}).
333
+ @kwarg X0: Optional I{origin} for I{L1-distances} (L{XDict}) or C{None} for
334
+ the L{Middle<Intersector.Middle>}, otherwise C{XDiff_(0, 0)}.
335
+ @kwarg aMaX0: Upper limit for the I{angular L1-distance}
336
+ (C{degrees}) or C{None} or C{0} for unlimited.
337
+ @kwarg sMaX0_C: Optional, upper limit C{B{sMaX0}=2*PI*R} for the
338
+ I{L1-distance} to B{C{X0}} (C{meter}).
339
+ @kwarg LatLon_and_kwds: Optional class C{B{LatLon}=None} to return intersection
340
+ points and optional, additional B{C{LatLon}} keyword arguments.
341
+
342
+ @note: The C{lat} and C{lon} attr of B{C{start1}}, B{C{end1}}, B{C{start2}} and
343
+ B{C{end2}} are used I{verbatim}, ignoring C{datum} or C{ellipsoid}.
344
+
345
+ @return: Yield an L{Intersect7Tuple}C{(A, B, sAB, aAB, c, kA, kB)} for every
346
+ intersection found, with C{A} and C{B} each a B{C{LatLon}} or if
347
+ C{B{LatLon} is None} or not specified, a L{LatLon4Tuple}C{(lat, lon,
348
+ height, datum)} with C{height 0} and this C{datum}.
349
+
350
+ @raise GeodesicError: Invalid B{C{start1}}, B{C{end1}}, B{C{start2}} or
351
+ B{C{end2}} or B{C{end1}} and B{C{end2}} differ in type.
352
+
353
+ @raise IntersectionError: No convergence.
354
+ '''
355
+
356
+ def _args(s, e):
357
+ t = (e,) if _isDegrees(e) else (e.lat, e.lon)
358
+ return (s.lat, s.lon) + t
359
+
360
+ try:
361
+ glA = self.Line(*_args(start1, end1))
362
+ glB = self.Line(*_args(start2, end2))
363
+ except Exception as x:
364
+ raise GeodesicError(start1=start1, end1=end1, start2=start2, end2=end2, cause=x)
365
+
366
+ LL, kwds = _xkwds_pop2(LatLon_and_kwds, LatLon=None)
367
+ d, kwds = _xkwds_pop2(kwds, datum=self.datum)
368
+ h, kwds = _xkwds_pop2(kwds, height=0)
369
+
370
+ _LL4T = _MODS.namedTuples._LL4Tuple
371
+ for X in self.All(glA, glB, X0=X0, aMaX0=aMaX0, sMaX0=sMaX0, _C=True):
372
+ A = B = _LL4T(X.latA, X.lonA, h, d, LL, kwds, iteration=X.iteration)
373
+ if X.sAB or X.latA != X.latB or X.lonA != X.lonB:
374
+ B = _LL4T(X.latB, X.lonB, h, d, LL, kwds, iteration=X.iteration)
375
+ yield Intersect7Tuple(A, B, X.sAB, X.aAB, X.c, _xkwds_get(X, kA=0),
376
+ _xkwds_get(X, kB=0))
377
+
303
378
  def _Inversa12(self, A, B=None):
304
379
  lls = (0, 0, A, 0) if B is None else (A.lat2, A.lon2,
305
380
  B.lat2, B.lon2)
306
381
  r = self._g.Inverse(*lls, outmask=Caps.DISTANCE)
307
382
  return r.s12, r.a12 # .a12 always in r
308
383
 
384
+ def k2kAkB(self, k):
385
+ '''Unravel C{k} into C{kA} and C{kB}.
386
+
387
+ @arg k: Segment indicator C{kA * 3 + kB} (C{int}).
388
+
389
+ @return: An C{ADict(k=k, kA=kA, kB=kB)}.
390
+
391
+ @raise GeodesicError: Invalid B{C{k}}.
392
+ '''
393
+ for kA in range(-1, 2):
394
+ for kB in range(-1, 2):
395
+ if (kA * 3 + kB) == k:
396
+ return ADict(k=k, kA=kA, kB=kB)
397
+ raise GeodesicError(k=k)
398
+
399
+ # def k2kAkB(self, k):
400
+ # # unravel C{k} into C{kA} and C{kB}.
401
+ # kA, kB = divmod(k, 3)
402
+ # if kB > 1:
403
+ # kA += 1
404
+ # kB -= 3
405
+ # return kA, kB
406
+
407
+ def Line(self, lat1, lon1, azi1_lat2, *lon2, **name): # PYCHOK no cover
408
+ '''(INTERNAL) I{Must be overloaded}.'''
409
+ self._notOverloaded(lat1, lon1, azi1_lat2, *lon2, **name)
410
+
309
411
  def _ll3z4ll(self, lat1, lon1, azi1_lat2, *lon2):
310
412
  t = Lat(lat1=lat1), Lon(lon1=lon1)
311
413
  if lon2: # get azis for All, keep lat-/lons
@@ -315,8 +417,8 @@ class _IntersectBase(_NamedBase):
315
417
  return t
316
418
 
317
419
  @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}.'''
420
+ def Next5s(self, glA, glB, X0=_X000, aMax=1801, sMax=0, **unused): # PYCHOK no cover
421
+ '''DEPRECATED on 2024.07.02, use method C{All5}.'''
320
422
  return self.All5(glA, glB, X0=X0, aMaX0=aMax, sMaX0=sMax) # PYCHOK attr
321
423
 
322
424
  @Property_RO
@@ -325,17 +427,17 @@ class _IntersectBase(_NamedBase):
325
427
  '''
326
428
  return self.ellipsoid.R2
327
429
 
328
- def _sMaX0_C2(self, aMaX0, **sMaX0_C):
430
+ def _sMaX0_C2(self, aMaX0=0, **sMaX0_C):
329
431
  _g = _xkwds_get
330
432
  s = _g(sMaX0_C, sMaX0=self._cMax)
331
433
  s = _g(sMaX0_C, sMax=s) # for backward ...
332
434
  a = _g(sMaX0_C, aMax=aMaX0) # ... compatibility
333
435
  if a: # degrees to meter, approx.
334
- s = max(s, self.R * radians(a)) # ellipsoid.degrees2m(a)
436
+ s = min(s, self.R * radians(a)) # ellipsoid.degrees2m(a)
335
437
  s = _g(sMaX0_C, _R=s)
336
438
  if s < _EPS3:
337
439
  s = _EPS3 # raise GeodesicError(sMaX0=s)
338
- return s, _xkwds_kwds(sMaX0_C, _C=False)
440
+ return s, _g(sMaX0_C, _C=False)
339
441
 
340
442
  def _xNext(self, glA, glB, eps1, **eps_C): # PYCHOK no cover
341
443
  eps1 = _xkwds_get(eps_C, eps=eps1) # eps for backward compatibility
@@ -343,7 +445,7 @@ class _IntersectBase(_NamedBase):
343
445
  a = glA.lat1 - glB.lat1
344
446
  b = glA.lon1 - glB.lon1
345
447
  if euclid(a, b) > eps1:
346
- raise GeodesicError(lat=a, lon=b, eps1=eps1)
448
+ raise GeodesicError(lat_=a, lon_=b, eps1=eps1)
347
449
  return _xkwds_kwds(eps_C, _C=False)
348
450
 
349
451
 
@@ -358,10 +460,15 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
358
460
  @note: This C{Intersectool} is intended I{for testing purposes only}, it invokes
359
461
  the C{IntersectTool} executable for I{every} method call.
360
462
  '''
463
+ _c_alt = _c__, # Closest latA lonA aziA latB lonB aziB
464
+ _C_option = '-C',
361
465
  _Error = GeodesicError
466
+ _i_alt = _i__, # Segment latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
362
467
  _linelimit = 1200 # line printer width X 10
468
+ _n_alt = _n__, # Next latA lonA aziA aziB
363
469
  _Names_ABs = _latA_, _lonA_, 'latB', 'lonB', _sAB_ # -C to stderr
364
470
  _Names_XDict = 'sA', 'sB', _c_ # plus 'k' from -i or 'sX0' from -R
471
+ _o_alt = _o__, # Offset latA lonA aziA latB lonB aziB x0 y0
365
472
  _Xable_name = 'IntersectTool'
366
473
  _Xable_path = _getenv(_PYGEODESY_INTERSECTTOOL_, _PYGEODESY_INTERSECTTOOL_)
367
474
 
@@ -386,7 +493,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
386
493
  self._GeodesicExact(a_geodesic, f))
387
494
  _IntersectBase.__init__(self, g, **name)
388
495
 
389
- def All(self, glA, glB, X0=_X000, eps1=_0_0, aMaX0=0, **sMaX0_C):
496
+ def All(self, glA, glB, X0=_X000, eps1=_0_0, aMaX0=0, **sMaX0_C): # PYCHOK signature
390
497
  '''Yield all intersection of two geodesic lines up to a limit.
391
498
 
392
499
  @kwarg eps1: Optional margin for the L{euclid<pygeodesy.euclid>}ean distance
@@ -396,6 +503,12 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
396
503
 
397
504
  @return: An L{XDict} for each intersection.
398
505
  '''
506
+ for X, _ in self._All2(glA, glB, X0, eps1, aMaX0=aMaX0, **sMaX0_C):
507
+ yield X
508
+
509
+ def _All2(self, glA, glB, X0, eps1, **aMaX0_sMaX0_C): # MCCABE 13
510
+ '''(INTERNAL) Helper for methods C{.All} and C{.All5}.
511
+ '''
399
512
  def _xz2(**gl):
400
513
  try:
401
514
  n, gl = gl.popitem() # _xkwds_item2(gl)
@@ -423,22 +536,24 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
423
536
  _x = self._o_alt
424
537
  b += X0.sA, X0.sB
425
538
 
426
- sMaX0, _C = self._sMaX0_C2(aMaX0, **sMaX0_C)
539
+ sMaX0, _C = self._sMaX0_C2(**aMaX0_sMaX0_C)
427
540
  for X in self._XDictInvoke(_x, _sX0_, (A + a + B + b),
428
- _R=sMaX0, **_C):
429
- yield X.set_(c=int(X.c))
541
+ _C=_C, _R=sMaX0):
542
+ if _C:
543
+ T = self._In5T(glA, glB, X, X)
544
+ if _aAB_ not in X:
545
+ X.set_(sAB=T.sAB, aAB=T.aAB)
546
+ else:
547
+ T = None
548
+ yield X.set_(c=int(X.c)), T
430
549
 
431
550
  def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0):
432
551
  '''Yield all intersection of two geodesic lines up to a limit.
433
552
 
434
553
  @return: An L{Intersectool5Tuple} for each intersection.
435
554
  '''
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__,)
555
+ for _, T in self._All2(glA, glB, X0, _0_0, _C=True, **aMaX0_sMaX0):
556
+ yield T
442
557
 
443
558
  @Property_RO
444
559
  def _cmdBasic(self):
@@ -448,12 +563,6 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
448
563
  self._E_option +
449
564
  self._p_option)
450
565
 
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
566
  def Closest(self, glA, glB, X0=_X000, _C=False):
458
567
  '''Find the closest intersection of two geodesic lines.
459
568
 
@@ -478,18 +587,11 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
478
587
  X = self.Closest(glA, glB, _C=True)
479
588
  return self._In5T(glA, glB, X, X)
480
589
 
481
- @property_RO
590
+ @property_ROver
482
591
  def _GeodesicExact(self):
483
592
  '''Get the I{class} L{GeodesicExact}, I{once}.
484
593
  '''
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
594
+ return _MODS.geodesicx.GeodesicExact # overwrite property_ROver
493
595
 
494
596
  def _In5T(self, glA, glB, S, X, k2=False, **_2X):
495
597
  A = GDict(glA).set_(lat2=X.latA, lon2=X.lonA, s12=S.sA)
@@ -523,7 +625,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
523
625
 
524
626
  def Line(self, lat1, lon1, azi1_lat2, *lon2, **name):
525
627
  '''Return a geodesic line from this C{Intersector}'s geodesic, specified by
526
- two (goedetic) points or a (goedetic) point and an (initial) azimuth.
628
+ two (goedetic) points or a (goedetic) point and an (forward) azimuth.
527
629
 
528
630
  @return: A 3- or 6-item, named L{GDict}.
529
631
  '''
@@ -562,7 +664,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
562
664
 
563
665
  sA, mA, iA, A = _smi4(glA=glA)
564
666
  sB, mB, iB, B = _smi4(glB=glB)
565
- X = XDict_(mA, mB) # center
667
+ X = XDict_(mA, mB) # centers
566
668
  _ = X._outSide(sA, sB)
567
669
  if _C: # _Names_ABs
568
670
  s, a = self._Inversa12(A, B)
@@ -577,13 +679,8 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
577
679
  '''
578
680
  X, A, iA, B, iB = self._middle5(glA, glB, _C=True)
579
681
  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
682
+ return Middle5Tuple(self._illz2G(A, iA),
683
+ self._illz2G(B, iB), s, a, c)
587
684
 
588
685
  def Next(self, glA, glB, eps1=None, **_C): # PYCHOK no cover
589
686
  '''Find the next intersection of two I{intersecting} geodesic lines.
@@ -606,12 +703,6 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
606
703
  X = self.Next(glA, glB, _C=True, **eps1)
607
704
  return self._In5T(glA, glB, X, X)
608
705
 
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
706
  def _R_option(self, _R=None):
616
707
  '''(INTERNAL) Get the C{-R maxdist} option.
617
708
  '''
@@ -624,16 +715,15 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
624
715
 
625
716
  @return: An L{XDict}.
626
717
  '''
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))
718
+ X = self._XDictInvoke(self._i_alt, 'k',
719
+ (glA.lat1, glA.lon1, glA.lat2, glA.lon2,
720
+ glB.lat1, glB.lon1, glB.lat2, glB.lon2),
721
+ _C=_xkwds_get(_C_unused, _C=False)) # _R=None
722
+ try:
723
+ ks = self.k2kAkB(int(X.k))
724
+ except Exception as x:
725
+ raise GeodesicError(glA=glA, glB=glB, X=str(X), cause=x)
726
+ return X.set_(**ks)
637
727
 
638
728
  def Segment5(self, glA, glB, **unused):
639
729
  '''Find the next intersection of two I{intersecting} geodesic lines.
@@ -676,7 +766,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
676
766
  Names += self._Names_ABs
677
767
  if _R:
678
768
  cmd += self._R_option(**_R)
679
- X, _R = self._DictInvoke2(cmd + alt, Names, XDict, args, **_R)
769
+ X, _R = self._DictInvoke2(cmd + alt, args, Names, XDict, **_R)
680
770
  return X if _R else X.set_(c=int(X.c)) # generator or XDict
681
771
 
682
772
 
@@ -689,10 +779,6 @@ class Intersector(_IntersectBase):
689
779
  @see: I{Karney}'s C++ class U{Intersect<https://GeographicLib.sourceforge.io/
690
780
  C++/doc/classGeographicLib_1_1Intersect.html#details>} for more details.
691
781
  '''
692
- # _D1 = 0
693
- # _D2 = 0
694
- # _T1 = 0
695
- # _T5 = 0
696
782
 
697
783
  def __init__(self, geodesic, **name):
698
784
  '''New L{Intersector}.
@@ -812,7 +898,7 @@ class Intersector(_IntersectBase):
812
898
  for X in X_[a:]: # addended Xs
813
899
  X._skip(S_, T2d3D)
814
900
 
815
- return X_.sorter(sMaX0, self._C, glA, glB, **_C) # generator
901
+ return X_.sorter(sMaX0, self._C, glA, glB, _C=_C) # generator
816
902
 
817
903
  def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0_C):
818
904
  '''Yield all intersection of two geodesic lines up to a limit.
@@ -838,7 +924,7 @@ class Intersector(_IntersectBase):
838
924
 
839
925
  raise IntersectionError(Fmt.no_convergence(S.L1(), self._Tol))
840
926
 
841
- def _C(self, X, glA, glB, _C=False, _AB=True):
927
+ def _C(self, X, glA, glB, _C=False, _MM=False):
842
928
  # add the C{_C} items to C{X}, if requested.
843
929
  if _C:
844
930
  A = self._Position(glA, X.sA)
@@ -846,10 +932,10 @@ class Intersector(_IntersectBase):
846
932
  s, a = self._Inversa12(A, B)
847
933
  X.set_(latA=A.lat2, lonA=A.lon2,
848
934
  latB=B.lat2, lonB=B.lon2)
849
- if _AB:
850
- X.set_(sAB=s, aAB=a)
851
- else:
935
+ if _MM: # in .Middle5
852
936
  X.set_(sMM=s, aMM=a)
937
+ else:
938
+ X.set_(sAB=s, aAB=a)
853
939
  return X
854
940
 
855
941
  def Closest(self, glA, glB, X0=_X000, **_C):
@@ -1032,28 +1118,30 @@ class Intersector(_IntersectBase):
1032
1118
  Line} or other C{InverseLine}.
1033
1119
  '''
1034
1120
  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
1121
+ return self._C(M, glA, glB, **_C) if _C else M
1037
1122
 
1038
- def _middle3(self, glA, glB, raiser): # in .All
1123
+ def _middle3(self, glA, glB, raiser): # in .All, .Segment
1039
1124
  # return segment length C{sA} and C{sB} and the
1040
1125
  # center C{X0} of rectangle [sA, sB]
1041
- self._xLines(glA, glB, s13=raiser) # need .Distance
1126
+ self._xLines(glA, glB, s13=raiser) # need .Arc, .Distance
1042
1127
  sA = glA.Distance()
1043
1128
  sB = glB.Distance()
1044
- X = XDict_(sA * _0_5, sB * _0_5) # center
1129
+ X = XDict_(sA * _0_5, sB * _0_5)
1130
+ # _ = X._outSide(sA, sB)
1045
1131
  return self._Delto(X), sA, sB
1046
1132
 
1047
- def Middle5(self, glA, glB, **raiser):
1133
+ def Middle5(self, glA, glB, raiser=True):
1048
1134
  '''Get the mid-points of two geodesic line segments and distances.
1049
1135
 
1050
1136
  @return: A L{Middle5Tuple}C{(A, B, sMM, aMM, c)}.
1051
1137
 
1052
1138
  @see: Method L{Middle} for further details.
1053
1139
  '''
1054
- M = self.Middle(glA, glB, _C=True, **raiser)
1140
+ M, _, _ = self._middle3(glA, glB, raiser)
1141
+ M = self._C(M, glA, glB, _C=True, _MM=True)
1055
1142
  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)
1143
+ return Middle5Tuple(self._illz2G(A, glA),
1144
+ self._illz2G(B, glB), s, a, c)
1057
1145
 
1058
1146
  def _m12_M12_M21(self, gl, s):
1059
1147
  P = gl.Position(s, outmask=Caps._REDUCEDLENGTH_GEODESICSCALE)
@@ -1201,7 +1289,7 @@ class Intersector(_IntersectBase):
1201
1289
  C{B{proven}=None} to return the first or
1202
1290
  C{B{proven}=False} the closest (C{bool}).
1203
1291
  @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
1292
+ are a 4-C{args} L{Line<Intersector.Line>} or
1205
1293
  C{InverseLine} (C{bool}).
1206
1294
  @kwarg _C: If C{True}, include the lat-/longitudes C{latA},
1207
1295
  C{lonA}, C{latB}, C{lonB} of and distances C{sAB}
@@ -1228,7 +1316,7 @@ class Intersector(_IntersectBase):
1228
1316
  d0 = X0.L1(Q)
1229
1317
  if Q._outSide(sA, sB) and d0 <= X0.L1() and not proven:
1230
1318
  i = Q.iteration
1231
- for T in X0._corners(sA, sB, self._T2):
1319
+ for T in Q._corners(sA, sB, self._T2):
1232
1320
  X, i = self._Basic2(glA, glB, T, i)
1233
1321
  X = T._fixCoincident(X)
1234
1322
  if not X._outSide(sA, sB):
@@ -1336,6 +1424,19 @@ class Intersector(_IntersectBase):
1336
1424
  raise GeodesicError(n, gl, cause=x)
1337
1425
 
1338
1426
 
1427
+ class Intersect7Tuple(_NamedTuple):
1428
+ '''7-Tuple C{(A, B, sAB, aAB, c, kA, kB)} with C{A} and C{B} each
1429
+ a C{LatLon} or L{LatLon4Tuple}C{(lat, lon, height, datum)} of
1430
+ the intersection on each geodesic line, the distance C{sAB} in
1431
+ in C{meter} and angular distance C{aAB} in C{degrees} between
1432
+ C{A} and C{B}, coincidence indicator C{c} and segment indicators
1433
+ C{kA} and C{kB} all C{int}, see L{XDict} and method U{intersect7
1434
+ <_IntersectBase.intersect7>}.
1435
+ '''
1436
+ _Names_ = (_A_, _B_, _sAB_, _aAB_, _c_, 'kA', 'kB')
1437
+ _Units_ = (_Pass, _Pass, Meter, Degrees, Int, Int, Int)
1438
+
1439
+
1339
1440
  class Intersectool5Tuple(_NamedTuple):
1340
1441
  '''5-Tuple C{(A, B, sAB, aAB, c)} with C{A} and C{B} the C{Position}
1341
1442
  of the intersection on each geodesic line, the distance C{sAB}
@@ -1349,8 +1450,8 @@ class Intersectool5Tuple(_NamedTuple):
1349
1450
  C{a1M} in C{degrees} and the segment indicator C{kX}. See
1350
1451
  L{XDict} for more details.
1351
1452
  '''
1352
- _Names_ = (_A_, _B_, _sAB_, 'aAB', _c_)
1353
- _Units_ = (_Pass, _Pass, Meter, Degrees, Int)
1453
+ _Names_ = Intersect7Tuple._Names_[:5]
1454
+ _Units_ = Intersect7Tuple._Units_[:5]
1354
1455
 
1355
1456
 
1356
1457
  class Intersector5Tuple(Intersectool5Tuple):
@@ -1421,23 +1522,19 @@ def _L1(a, b):
1421
1522
  return fabs(a) + fabs(b)
1422
1523
 
1423
1524
 
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
-
1525
+ __all__ += _ALL_DOCS(_IntersectBase)
1430
1526
 
1431
1527
  if __name__ == '__main__': # MCCABE 14
1432
1528
 
1433
1529
  from pygeodesy import printf
1530
+ __help_ = '--help'
1434
1531
 
1435
1532
  def _main(args):
1436
1533
 
1437
1534
  from pygeodesy import GeodesicExact
1438
1535
  from pygeodesy.internals import _plural, _usage
1439
- from pygeodesy.interns import _COLONSPACE_, _DASH_, _DOT_, \
1440
- _EQUAL_, _i_, _n_, _version_, _X_
1536
+ from pygeodesy.interns import _COLONSPACE_, _DOT_, _EQUAL_, \
1537
+ _i_, _m_, _n_, _version_, _X_
1441
1538
  import re
1442
1539
 
1443
1540
  class XY0(Float):
@@ -1453,13 +1550,12 @@ if __name__ == '__main__': # MCCABE 14
1453
1550
  alts=((_c_ + llz2),
1454
1551
  (_i_ + ll4),
1455
1552
  (_m_ + ll4),
1456
- (_M_ + ll4),
1457
1553
  (_n_ + llz + ' aziB'),
1458
1554
  ('o' + llz2 + ' x0 y0')),
1459
1555
  help=_h if isinstance(_h, str) else NN)
1460
1556
 
1461
- def _starts(opt, arg, n=0):
1462
- return opt.startswith(arg) and len(arg.lstrip(_DASH_)) > n
1557
+ def _starts(Opt, arg):
1558
+ return arg == Opt[1:3] or (len(arg) > 2 and Opt.startswith(arg))
1463
1559
 
1464
1560
  _isopt = re.compile('^[-]+[a-z]*$', flags=re.IGNORECASE).match
1465
1561
 
@@ -1469,38 +1565,37 @@ if __name__ == '__main__': # MCCABE 14
1469
1565
 
1470
1566
  while args and _isopt(args[0]):
1471
1567
  arg = args.pop(0)
1472
- if arg == _C__ or _starts('--Check', arg):
1473
- _C = True
1474
- elif arg == _c__:
1568
+ if arg == _c__:
1475
1569
  M, m = I.Closest, 6 # latA lonA aziA latB lonB aziB
1476
- elif arg == '-h' or _starts('--help', arg):
1570
+ elif _starts('--Check', arg):
1571
+ _C = True
1572
+ elif _starts(__help_, arg):
1477
1573
  _h = args[0] if args and _isopt(args[0]) else True
1478
1574
  elif arg == _i__:
1479
1575
  M, m = I.Segment, 8 # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
1480
1576
  elif arg == '-m':
1481
1577
  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
1578
+ _R = None # zap -R
1486
1579
  elif arg == _n__:
1487
1580
  M, m = I.Next, 4 # latA lonA aziA aziB
1488
1581
  elif arg == _o__:
1489
1582
  M, m = I.Closest, 8 # latA lonA aziA latB lonB aziB x0 y0
1490
1583
  elif arg == _R__ and args:
1491
1584
  _R = args.pop(0)
1492
- elif arg == '-T' or _starts('--Tool', arg):
1585
+ elif _starts('--Tool', arg):
1493
1586
  I = Intersectool() # PYCHOK I
1494
1587
  if _V:
1495
1588
  I.verbose = True
1496
1589
  if I.IntersectTool in (_PYGEODESY_INTERSECTTOOL_, None): # not set
1497
1590
  I.IntersectTool = '/opt/local/bin/IntersectTool' # '/opt/local/Cellar/geographiclib/2.3/bin/IntersectTool' # HomeBrew
1591
+ elif _V:
1592
+ _ = I.version
1498
1593
  M, _T = None, True
1499
- elif arg == '-V' or _starts('--Verbose', arg):
1594
+ elif _starts('--Verbose', arg):
1500
1595
  _V = True
1501
1596
  if _T:
1502
1597
  I.verbose = True
1503
- elif arg == '-v' or _starts('--version', arg):
1598
+ elif _starts('--version', arg):
1504
1599
  printf(_COLONSPACE_(*((_version_, I.version) if _T else
1505
1600
  (__version__, repr(I)))))
1506
1601
  else:
@@ -1508,7 +1603,6 @@ if __name__ == '__main__': # MCCABE 14
1508
1603
 
1509
1604
  if _h or M is None:
1510
1605
  printf(_usage(__file__, **_opts(_h)), nl=1)
1511
- # exit(0)
1512
1606
  else:
1513
1607
  n = len(args)
1514
1608
  if n < m:
@@ -1547,7 +1641,7 @@ if __name__ == '__main__': # MCCABE 14
1547
1641
 
1548
1642
  from sys import argv, stderr
1549
1643
  try:
1550
- if len(argv) == 2 and argv[1] == _HASH_:
1644
+ if len(argv) == 2 and argv[1] == __help_:
1551
1645
  from pygeodesy.internals import _usage_argv
1552
1646
 
1553
1647
  s = _SPACE_(*_usage_argv(__file__))
@@ -1560,8 +1654,8 @@ if __name__ == '__main__': # MCCABE 14
1560
1654
  '# % echo 0 0 10 10 50 -4 50S 4W | IntersectTool -i -p 0 -C',
1561
1655
  '# -631414 5988887 0 -3',
1562
1656
  '# -4.05187 -4.00000 -4.05187 -4.00000 0',
1657
+ '-m 0 0 10 10 50 -4 50S 4W',
1563
1658
  '-C -m 0 0 10 10 50 -4 50S 4W',
1564
- '-M 0 0 10 10 50 -4 50S 4W',
1565
1659
  '-i 0 0 10 10 50 -4 50S 4W',
1566
1660
  '-T -i 0 0 10 10 50 -4 50S 4W',
1567
1661
  '-C -i 0 0 10 10 50 -4 50S 4W',
@@ -1587,13 +1681,14 @@ if __name__ == '__main__': # MCCABE 14
1587
1681
  raise
1588
1682
  exit(1)
1589
1683
 
1590
- # % env PYGEODESY_INTERSECTTOOL=... python3 -m pygeodesy.geodesici -h
1684
+ # % env PYGEODESY_INTERSECTTOOL=... python3 -m pygeodesy.geodesici --help
1685
+
1686
+ # % python3 -m pygeodesy.geodesici -h
1591
1687
  #
1592
1688
  # usage: python3 -m ....pygeodesy.geodesici [--Verbose | -V] [--version | -v] [--help | -h] [--Tool | -T] [--Check | -C] [-R meter]
1593
1689
  # [-c latA lonA aziA latB lonB aziB |
1594
1690
  # -i latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1595
1691
  # -m latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1596
- # -M latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1597
1692
  # -n latA lonA aziA aziB |
1598
1693
  # -o latA lonA aziA latB lonB aziB x0 y0]
1599
1694
 
@@ -1620,11 +1715,11 @@ if __name__ == '__main__': # MCCABE 14
1620
1715
  # -631414 5988887 0 -3
1621
1716
  # -4.05187 -4.00000 -4.05187 -4.00000 0
1622
1717
 
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)
1718
+ # % python3 -m ....pygeodesy.geodesici -m 0 0 10 10 50 -4 50S 4W
1719
+ # Intersector.Middle: XDict(c=0, sA=782554.549609, sB=5536835.161499, sX0=0.0)
1625
1720
 
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)
1721
+ # % python3 -m ....pygeodesy.geodesici -C -m 0 0 10 10 50 -4 50S 4W
1722
+ # 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)
1628
1723
 
1629
1724
  # % python3 -m ....pygeodesy.geodesici -i 0 0 10 10 50 -4 50S 4W
1630
1725
  # Intersector.Segment: XDict(c=0, k=-3, kA=-1, kB=0, sA=-631414.26877, sB=5988887.278435, sX0=1866020.935315)