pygeodesy 24.7.7__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,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 Degrees, Float, Int, _isDegrees, \
53
+ 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.22'
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!
@@ -92,9 +94,14 @@ class XDict(ADict):
92
94
  line C{glA} respectively C{glB}, but C{sX0} is the I{L1-distance}
93
95
  between the intersection and the I{origin} C{X0}.
94
96
 
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
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
98
105
  L{Intersection3Tuple<Intersection3Tuple>}. Segment indicator
99
106
  C{k} is I{Karney}'s C{segmode}, equal C{kA * 3 + kB}.
100
107
  '''
@@ -266,6 +273,10 @@ class _IntersectBase(_NamedBase):
266
273
 
267
274
  equatoradius = a # = Requatorial
268
275
 
276
+ def All(self, glA, glB, **kwds): # PYCHOK no cover
277
+ '''(INTERNAL) I{Must be overloaded}.'''
278
+ self._notOverloaded(glA, glB, **kwds)
279
+
269
280
  @Property_RO
270
281
  def _cHalf(self): # normalizer, semi-circumference
271
282
  return self.R * PI # ~20K Km WGS84
@@ -300,12 +311,109 @@ class _IntersectBase(_NamedBase):
300
311
  '''
301
312
  return self._g
302
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
+
303
384
  def _Inversa12(self, A, B=None):
304
385
  lls = (0, 0, A, 0) if B is None else (A.lat2, A.lon2,
305
386
  B.lat2, B.lon2)
306
387
  r = self._g.Inverse(*lls, outmask=Caps.DISTANCE)
307
388
  return r.s12, r.a12 # .a12 always in r
308
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
+
309
417
  def _ll3z4ll(self, lat1, lon1, azi1_lat2, *lon2):
310
418
  t = Lat(lat1=lat1), Lon(lon1=lon1)
311
419
  if lon2: # get azis for All, keep lat-/lons
@@ -315,8 +423,8 @@ class _IntersectBase(_NamedBase):
315
423
  return t
316
424
 
317
425
  @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}.'''
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}.'''
320
428
  return self.All5(glA, glB, X0=X0, aMaX0=aMax, sMaX0=sMax) # PYCHOK attr
321
429
 
322
430
  @Property_RO
@@ -325,17 +433,17 @@ class _IntersectBase(_NamedBase):
325
433
  '''
326
434
  return self.ellipsoid.R2
327
435
 
328
- def _sMaX0_C2(self, aMaX0, **sMaX0_C):
436
+ def _sMaX0_C2(self, aMaX0=0, **sMaX0_C):
329
437
  _g = _xkwds_get
330
438
  s = _g(sMaX0_C, sMaX0=self._cMax)
331
439
  s = _g(sMaX0_C, sMax=s) # for backward ...
332
440
  a = _g(sMaX0_C, aMax=aMaX0) # ... compatibility
333
441
  if a: # degrees to meter, approx.
334
- s = max(s, self.R * radians(a)) # ellipsoid.degrees2m(a)
442
+ s = min(s, self.R * radians(a)) # ellipsoid.degrees2m(a)
335
443
  s = _g(sMaX0_C, _R=s)
336
444
  if s < _EPS3:
337
445
  s = _EPS3 # raise GeodesicError(sMaX0=s)
338
- return s, _xkwds_kwds(sMaX0_C, _C=False)
446
+ return s, _g(sMaX0_C, _C=False)
339
447
 
340
448
  def _xNext(self, glA, glB, eps1, **eps_C): # PYCHOK no cover
341
449
  eps1 = _xkwds_get(eps_C, eps=eps1) # eps for backward compatibility
@@ -343,7 +451,7 @@ class _IntersectBase(_NamedBase):
343
451
  a = glA.lat1 - glB.lat1
344
452
  b = glA.lon1 - glB.lon1
345
453
  if euclid(a, b) > eps1:
346
- raise GeodesicError(lat=a, lon=b, eps1=eps1)
454
+ raise GeodesicError(lat_=a, lon_=b, eps1=eps1)
347
455
  return _xkwds_kwds(eps_C, _C=False)
348
456
 
349
457
 
@@ -358,10 +466,15 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
358
466
  @note: This C{Intersectool} is intended I{for testing purposes only}, it invokes
359
467
  the C{IntersectTool} executable for I{every} method call.
360
468
  '''
469
+ _c_alt = _c__, # Closest latA lonA aziA latB lonB aziB
470
+ _C_option = '-C',
361
471
  _Error = GeodesicError
472
+ _i_alt = _i__, # Segment latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
362
473
  _linelimit = 1200 # line printer width X 10
474
+ _n_alt = _n__, # Next latA lonA aziA aziB
363
475
  _Names_ABs = _latA_, _lonA_, 'latB', 'lonB', _sAB_ # -C to stderr
364
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
365
478
  _Xable_name = 'IntersectTool'
366
479
  _Xable_path = _getenv(_PYGEODESY_INTERSECTTOOL_, _PYGEODESY_INTERSECTTOOL_)
367
480
 
@@ -386,7 +499,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
386
499
  self._GeodesicExact(a_geodesic, f))
387
500
  _IntersectBase.__init__(self, g, **name)
388
501
 
389
- def All(self, glA, glB, X0=_X000, eps1=_0_0, aMaX0=0, **sMaX0_C):
502
+ def All(self, glA, glB, X0=_X000, eps1=_0_0, aMaX0=0, **sMaX0_C): # PYCHOK signature
390
503
  '''Yield all intersection of two geodesic lines up to a limit.
391
504
 
392
505
  @kwarg eps1: Optional margin for the L{euclid<pygeodesy.euclid>}ean distance
@@ -396,6 +509,12 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
396
509
 
397
510
  @return: An L{XDict} for each intersection.
398
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
+ '''
399
518
  def _xz2(**gl):
400
519
  try:
401
520
  n, gl = gl.popitem() # _xkwds_item2(gl)
@@ -423,22 +542,24 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
423
542
  _x = self._o_alt
424
543
  b += X0.sA, X0.sB
425
544
 
426
- sMaX0, _C = self._sMaX0_C2(aMaX0, **sMaX0_C)
545
+ sMaX0, _C = self._sMaX0_C2(**aMaX0_sMaX0_C)
427
546
  for X in self._XDictInvoke(_x, _sX0_, (A + a + B + b),
428
- _R=sMaX0, **_C):
429
- yield X.set_(c=int(X.c))
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
430
555
 
431
556
  def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0):
432
557
  '''Yield all intersection of two geodesic lines up to a limit.
433
558
 
434
559
  @return: An L{Intersectool5Tuple} for each intersection.
435
560
  '''
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__,)
561
+ for _, T in self._All2(glA, glB, X0, _0_0, _C=True, **aMaX0_sMaX0):
562
+ yield T
442
563
 
443
564
  @Property_RO
444
565
  def _cmdBasic(self):
@@ -448,12 +569,6 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
448
569
  self._E_option +
449
570
  self._p_option)
450
571
 
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
572
  def Closest(self, glA, glB, X0=_X000, _C=False):
458
573
  '''Find the closest intersection of two geodesic lines.
459
574
 
@@ -478,18 +593,11 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
478
593
  X = self.Closest(glA, glB, _C=True)
479
594
  return self._In5T(glA, glB, X, X)
480
595
 
481
- @property_RO
596
+ @property_ROver
482
597
  def _GeodesicExact(self):
483
598
  '''Get the I{class} L{GeodesicExact}, I{once}.
484
599
  '''
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
600
+ return _MODS.geodesicx.GeodesicExact # overwrite propertyROver
493
601
 
494
602
  def _In5T(self, glA, glB, S, X, k2=False, **_2X):
495
603
  A = GDict(glA).set_(lat2=X.latA, lon2=X.lonA, s12=S.sA)
@@ -523,7 +631,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
523
631
 
524
632
  def Line(self, lat1, lon1, azi1_lat2, *lon2, **name):
525
633
  '''Return a geodesic line from this C{Intersector}'s geodesic, specified by
526
- two (goedetic) points or a (goedetic) point and an (initial) azimuth.
634
+ two (goedetic) points or a (goedetic) point and an (forward) azimuth.
527
635
 
528
636
  @return: A 3- or 6-item, named L{GDict}.
529
637
  '''
@@ -562,7 +670,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
562
670
 
563
671
  sA, mA, iA, A = _smi4(glA=glA)
564
672
  sB, mB, iB, B = _smi4(glB=glB)
565
- X = XDict_(mA, mB) # center
673
+ X = XDict_(mA, mB) # centers
566
674
  _ = X._outSide(sA, sB)
567
675
  if _C: # _Names_ABs
568
676
  s, a = self._Inversa12(A, B)
@@ -577,13 +685,8 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
577
685
  '''
578
686
  X, A, iA, B, iB = self._middle5(glA, glB, _C=True)
579
687
  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
688
+ return Middle5Tuple(self._illz2G(A, iA),
689
+ self._illz2G(B, iB), s, a, c)
587
690
 
588
691
  def Next(self, glA, glB, eps1=None, **_C): # PYCHOK no cover
589
692
  '''Find the next intersection of two I{intersecting} geodesic lines.
@@ -606,12 +709,6 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
606
709
  X = self.Next(glA, glB, _C=True, **eps1)
607
710
  return self._In5T(glA, glB, X, X)
608
711
 
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
712
  def _R_option(self, _R=None):
616
713
  '''(INTERNAL) Get the C{-R maxdist} option.
617
714
  '''
@@ -624,16 +721,15 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
624
721
 
625
722
  @return: An L{XDict}.
626
723
  '''
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))
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)
637
733
 
638
734
  def Segment5(self, glA, glB, **unused):
639
735
  '''Find the next intersection of two I{intersecting} geodesic lines.
@@ -676,7 +772,7 @@ class Intersectool(_IntersectBase, _SolveCapsBase):
676
772
  Names += self._Names_ABs
677
773
  if _R:
678
774
  cmd += self._R_option(**_R)
679
- X, _R = self._DictInvoke2(cmd + alt, Names, XDict, args, **_R)
775
+ X, _R = self._DictInvoke2(cmd + alt, args, Names, XDict, **_R)
680
776
  return X if _R else X.set_(c=int(X.c)) # generator or XDict
681
777
 
682
778
 
@@ -689,10 +785,6 @@ class Intersector(_IntersectBase):
689
785
  @see: I{Karney}'s C++ class U{Intersect<https://GeographicLib.sourceforge.io/
690
786
  C++/doc/classGeographicLib_1_1Intersect.html#details>} for more details.
691
787
  '''
692
- # _D1 = 0
693
- # _D2 = 0
694
- # _T1 = 0
695
- # _T5 = 0
696
788
 
697
789
  def __init__(self, geodesic, **name):
698
790
  '''New L{Intersector}.
@@ -812,7 +904,7 @@ class Intersector(_IntersectBase):
812
904
  for X in X_[a:]: # addended Xs
813
905
  X._skip(S_, T2d3D)
814
906
 
815
- return X_.sorter(sMaX0, self._C, glA, glB, **_C) # generator
907
+ return X_.sorter(sMaX0, self._C, glA, glB, _C=_C) # generator
816
908
 
817
909
  def All5(self, glA, glB, X0=_X000, **aMaX0_sMaX0_C):
818
910
  '''Yield all intersection of two geodesic lines up to a limit.
@@ -838,7 +930,7 @@ class Intersector(_IntersectBase):
838
930
 
839
931
  raise IntersectionError(Fmt.no_convergence(S.L1(), self._Tol))
840
932
 
841
- def _C(self, X, glA, glB, _C=False, _AB=True):
933
+ def _C(self, X, glA, glB, _C=False, _MM=False):
842
934
  # add the C{_C} items to C{X}, if requested.
843
935
  if _C:
844
936
  A = self._Position(glA, X.sA)
@@ -846,10 +938,10 @@ class Intersector(_IntersectBase):
846
938
  s, a = self._Inversa12(A, B)
847
939
  X.set_(latA=A.lat2, lonA=A.lon2,
848
940
  latB=B.lat2, lonB=B.lon2)
849
- if _AB:
850
- X.set_(sAB=s, aAB=a)
851
- else:
941
+ if _MM: # in .Middle5
852
942
  X.set_(sMM=s, aMM=a)
943
+ else:
944
+ X.set_(sAB=s, aAB=a)
853
945
  return X
854
946
 
855
947
  def Closest(self, glA, glB, X0=_X000, **_C):
@@ -1032,28 +1124,30 @@ class Intersector(_IntersectBase):
1032
1124
  Line} or other C{InverseLine}.
1033
1125
  '''
1034
1126
  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
1127
+ return self._C(M, glA, glB, **_C) if _C else M
1037
1128
 
1038
- def _middle3(self, glA, glB, raiser): # in .All
1129
+ def _middle3(self, glA, glB, raiser): # in .All, .Segment
1039
1130
  # return segment length C{sA} and C{sB} and the
1040
1131
  # center C{X0} of rectangle [sA, sB]
1041
- self._xLines(glA, glB, s13=raiser) # need .Distance
1132
+ self._xLines(glA, glB, s13=raiser) # need .Arc, .Distance
1042
1133
  sA = glA.Distance()
1043
1134
  sB = glB.Distance()
1044
- X = XDict_(sA * _0_5, sB * _0_5) # center
1135
+ X = XDict_(sA * _0_5, sB * _0_5)
1136
+ # _ = X._outSide(sA, sB)
1045
1137
  return self._Delto(X), sA, sB
1046
1138
 
1047
- def Middle5(self, glA, glB, **raiser):
1139
+ def Middle5(self, glA, glB, raiser=True):
1048
1140
  '''Get the mid-points of two geodesic line segments and distances.
1049
1141
 
1050
1142
  @return: A L{Middle5Tuple}C{(A, B, sMM, aMM, c)}.
1051
1143
 
1052
1144
  @see: Method L{Middle} for further details.
1053
1145
  '''
1054
- M = self.Middle(glA, glB, _C=True, **raiser)
1146
+ M, _, _ = self._middle3(glA, glB, raiser)
1147
+ M = self._C(M, glA, glB, _C=True, _MM=True)
1055
1148
  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)
1149
+ return Middle5Tuple(self._illz2G(A, glA),
1150
+ self._illz2G(B, glB), s, a, c)
1057
1151
 
1058
1152
  def _m12_M12_M21(self, gl, s):
1059
1153
  P = gl.Position(s, outmask=Caps._REDUCEDLENGTH_GEODESICSCALE)
@@ -1201,7 +1295,7 @@ class Intersector(_IntersectBase):
1201
1295
  C{B{proven}=None} to return the first or
1202
1296
  C{B{proven}=False} the closest (C{bool}).
1203
1297
  @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
1298
+ are a 4-C{args} L{Line<Intersector.Line>} or
1205
1299
  C{InverseLine} (C{bool}).
1206
1300
  @kwarg _C: If C{True}, include the lat-/longitudes C{latA},
1207
1301
  C{lonA}, C{latB}, C{lonB} of and distances C{sAB}
@@ -1228,7 +1322,7 @@ class Intersector(_IntersectBase):
1228
1322
  d0 = X0.L1(Q)
1229
1323
  if Q._outSide(sA, sB) and d0 <= X0.L1() and not proven:
1230
1324
  i = Q.iteration
1231
- for T in X0._corners(sA, sB, self._T2):
1325
+ for T in Q._corners(sA, sB, self._T2):
1232
1326
  X, i = self._Basic2(glA, glB, T, i)
1233
1327
  X = T._fixCoincident(X)
1234
1328
  if not X._outSide(sA, sB):
@@ -1336,6 +1430,19 @@ class Intersector(_IntersectBase):
1336
1430
  raise GeodesicError(n, gl, cause=x)
1337
1431
 
1338
1432
 
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
+
1339
1446
  class Intersectool5Tuple(_NamedTuple):
1340
1447
  '''5-Tuple C{(A, B, sAB, aAB, c)} with C{A} and C{B} the C{Position}
1341
1448
  of the intersection on each geodesic line, the distance C{sAB}
@@ -1349,8 +1456,8 @@ class Intersectool5Tuple(_NamedTuple):
1349
1456
  C{a1M} in C{degrees} and the segment indicator C{kX}. See
1350
1457
  L{XDict} for more details.
1351
1458
  '''
1352
- _Names_ = (_A_, _B_, _sAB_, 'aAB', _c_)
1353
- _Units_ = (_Pass, _Pass, Meter, Degrees, Int)
1459
+ _Names_ = Intersect7Tuple._Names_[:5]
1460
+ _Units_ = Intersect7Tuple._Units_[:5]
1354
1461
 
1355
1462
 
1356
1463
  class Intersector5Tuple(Intersectool5Tuple):
@@ -1421,23 +1528,19 @@ def _L1(a, b):
1421
1528
  return fabs(a) + fabs(b)
1422
1529
 
1423
1530
 
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
-
1531
+ __all__ += _ALL_DOCS(_IntersectBase)
1430
1532
 
1431
1533
  if __name__ == '__main__': # MCCABE 14
1432
1534
 
1433
1535
  from pygeodesy import printf
1536
+ __help_ = '--help'
1434
1537
 
1435
1538
  def _main(args):
1436
1539
 
1437
1540
  from pygeodesy import GeodesicExact
1438
1541
  from pygeodesy.internals import _plural, _usage
1439
- from pygeodesy.interns import _COLONSPACE_, _DASH_, _DOT_, \
1440
- _EQUAL_, _i_, _n_, _version_, _X_
1542
+ from pygeodesy.interns import _COLONSPACE_, _DOT_, _EQUAL_, \
1543
+ _i_, _m_, _n_, _version_, _X_
1441
1544
  import re
1442
1545
 
1443
1546
  class XY0(Float):
@@ -1453,13 +1556,12 @@ if __name__ == '__main__': # MCCABE 14
1453
1556
  alts=((_c_ + llz2),
1454
1557
  (_i_ + ll4),
1455
1558
  (_m_ + ll4),
1456
- (_M_ + ll4),
1457
1559
  (_n_ + llz + ' aziB'),
1458
1560
  ('o' + llz2 + ' x0 y0')),
1459
1561
  help=_h if isinstance(_h, str) else NN)
1460
1562
 
1461
- def _starts(opt, arg, n=0):
1462
- return opt.startswith(arg) and len(arg.lstrip(_DASH_)) > n
1563
+ def _starts(Opt, arg):
1564
+ return arg == Opt[1:3] or (len(arg) > 2 and Opt.startswith(arg))
1463
1565
 
1464
1566
  _isopt = re.compile('^[-]+[a-z]*$', flags=re.IGNORECASE).match
1465
1567
 
@@ -1469,38 +1571,37 @@ if __name__ == '__main__': # MCCABE 14
1469
1571
 
1470
1572
  while args and _isopt(args[0]):
1471
1573
  arg = args.pop(0)
1472
- if arg == _C__ or _starts('--Check', arg):
1473
- _C = True
1474
- elif arg == _c__:
1574
+ if arg == _c__:
1475
1575
  M, m = I.Closest, 6 # latA lonA aziA latB lonB aziB
1476
- elif arg == '-h' or _starts('--help', arg):
1576
+ elif _starts('--Check', arg):
1577
+ _C = True
1578
+ elif _starts(__help_, arg):
1477
1579
  _h = args[0] if args and _isopt(args[0]) else True
1478
1580
  elif arg == _i__:
1479
1581
  M, m = I.Segment, 8 # latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2
1480
1582
  elif arg == '-m':
1481
1583
  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
1584
+ _R = None # zap -R
1486
1585
  elif arg == _n__:
1487
1586
  M, m = I.Next, 4 # latA lonA aziA aziB
1488
1587
  elif arg == _o__:
1489
1588
  M, m = I.Closest, 8 # latA lonA aziA latB lonB aziB x0 y0
1490
1589
  elif arg == _R__ and args:
1491
1590
  _R = args.pop(0)
1492
- elif arg == '-T' or _starts('--Tool', arg):
1591
+ elif _starts('--Tool', arg):
1493
1592
  I = Intersectool() # PYCHOK I
1494
1593
  if _V:
1495
1594
  I.verbose = True
1496
1595
  if I.IntersectTool in (_PYGEODESY_INTERSECTTOOL_, None): # not set
1497
1596
  I.IntersectTool = '/opt/local/bin/IntersectTool' # '/opt/local/Cellar/geographiclib/2.3/bin/IntersectTool' # HomeBrew
1597
+ elif _V:
1598
+ _ = I.version
1498
1599
  M, _T = None, True
1499
- elif arg == '-V' or _starts('--Verbose', arg):
1600
+ elif _starts('--Verbose', arg):
1500
1601
  _V = True
1501
1602
  if _T:
1502
1603
  I.verbose = True
1503
- elif arg == '-v' or _starts('--version', arg):
1604
+ elif _starts('--version', arg):
1504
1605
  printf(_COLONSPACE_(*((_version_, I.version) if _T else
1505
1606
  (__version__, repr(I)))))
1506
1607
  else:
@@ -1508,7 +1609,6 @@ if __name__ == '__main__': # MCCABE 14
1508
1609
 
1509
1610
  if _h or M is None:
1510
1611
  printf(_usage(__file__, **_opts(_h)), nl=1)
1511
- # exit(0)
1512
1612
  else:
1513
1613
  n = len(args)
1514
1614
  if n < m:
@@ -1547,7 +1647,7 @@ if __name__ == '__main__': # MCCABE 14
1547
1647
 
1548
1648
  from sys import argv, stderr
1549
1649
  try:
1550
- if len(argv) == 2 and argv[1] == _HASH_:
1650
+ if len(argv) == 2 and argv[1] == __help_:
1551
1651
  from pygeodesy.internals import _usage_argv
1552
1652
 
1553
1653
  s = _SPACE_(*_usage_argv(__file__))
@@ -1560,8 +1660,8 @@ if __name__ == '__main__': # MCCABE 14
1560
1660
  '# % echo 0 0 10 10 50 -4 50S 4W | IntersectTool -i -p 0 -C',
1561
1661
  '# -631414 5988887 0 -3',
1562
1662
  '# -4.05187 -4.00000 -4.05187 -4.00000 0',
1663
+ '-m 0 0 10 10 50 -4 50S 4W',
1563
1664
  '-C -m 0 0 10 10 50 -4 50S 4W',
1564
- '-M 0 0 10 10 50 -4 50S 4W',
1565
1665
  '-i 0 0 10 10 50 -4 50S 4W',
1566
1666
  '-T -i 0 0 10 10 50 -4 50S 4W',
1567
1667
  '-C -i 0 0 10 10 50 -4 50S 4W',
@@ -1587,13 +1687,14 @@ if __name__ == '__main__': # MCCABE 14
1587
1687
  raise
1588
1688
  exit(1)
1589
1689
 
1590
- # % env PYGEODESY_INTERSECTTOOL=... python3 -m pygeodesy.geodesici -h
1690
+ # % env PYGEODESY_INTERSECTTOOL=... python3 -m pygeodesy.geodesici --help
1691
+
1692
+ # % python3 -m pygeodesy.geodesici -h
1591
1693
  #
1592
1694
  # usage: python3 -m ....pygeodesy.geodesici [--Verbose | -V] [--version | -v] [--help | -h] [--Tool | -T] [--Check | -C] [-R meter]
1593
1695
  # [-c latA lonA aziA latB lonB aziB |
1594
1696
  # -i latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1595
1697
  # -m latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1596
- # -M latA1 lonA1 latA2 lonA2 latB1 lonB1 latB2 lonB2 |
1597
1698
  # -n latA lonA aziA aziB |
1598
1699
  # -o latA lonA aziA latB lonB aziB x0 y0]
1599
1700
 
@@ -1620,11 +1721,11 @@ if __name__ == '__main__': # MCCABE 14
1620
1721
  # -631414 5988887 0 -3
1621
1722
  # -4.05187 -4.00000 -4.05187 -4.00000 0
1622
1723
 
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)
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)
1625
1726
 
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)
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)
1628
1729
 
1629
1730
  # % python3 -m ....pygeodesy.geodesici -i 0 0 10 10 50 -4 50S 4W
1630
1731
  # Intersector.Segment: XDict(c=0, k=-3, kA=-1, kB=0, sA=-631414.26877, sB=5988887.278435, sX0=1866020.935315)