def intersections2(self, rad1, other, rad2, radius=R_M): '''Compute the intersection points of two circles each defined by a center point and a radius. @arg rad1: Radius of the this circle (C{meter} or C{radians}, see B{C{radius}}). @arg other: Center of the other circle (C{Cartesian}). @arg rad2: Radius of the other circle (C{meter} or C{radians}, see B{C{radius}}). @kwarg radius: Mean earth radius (C{meter} or C{None} if both B{C{rad1}} and B{C{rad2}} are given in C{radians}). @return: 2-Tuple of the intersection points, each C{Cartesian}. The intersection points are the same C{Cartesian} instance for abutting circles. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles. @raise TypeError: If B{C{other}} is not C{Cartesian}. @raise ValueError: Invalid B{C{rad1}}, B{C{rad2}} or B{C{radius}}. @see: U{Java code<https://GIS.StackExchange.com/questions/48937/ calculating-intersection-of-two-circles>} and function L{trilaterate3d2}. ''' x1, x2 = self, self.others(other) r1, r2, x = _rads3(rad1, rad2, radius) if x: x1, x2 = x2, x1 n, q = x1.cross(x2), x1.dot(x2) n2, q21 = n.length2, _1_0 - q**2 if min(abs(q21), n2) < EPS: raise IntersectionError(center=self, other=other, txt=_near_concentric_) try: cr1, cr2 = cos(r1), cos(r2) a = (cr1 - q * cr2) / q21 b = (cr2 - q * cr1) / q21 x0 = x1.times(a).plus(x2.times(b)) except ValueError: raise IntersectionError(center=self, rad1=rad1, other=other, rad2=rad2) x = _1_0 - x0.length2 # XXX x0.dot(x0) if x < EPS: raise IntersectionError(center=self, rad1=rad1, other=other, rad2=rad2, txt=_too_(_distant_)) n = n.times(sqrt(x / n2)) if n.length > EPS: x1, x2 = x0.plus(n), x0.minus(n) else: # abutting circles x1 = x2 = x0 for x in (x1, x2): x.datum = self.datum x.name = self.intersections2.__name__ return x1, x2
def _x3d2(start, end, wrap, n, hs): # see <https://www.EdWilliams.org/intersect.htm> (5) ff a1, b1 = start.philam if isscalar(end): # bearing, make a point a2, b2 = _destination2(a1, b1, PI_4, radians(end)) else: # must be a point _Trll.others(end, name=_end_ + n) hs.append(end.height) a2, b2 = end.philam db, b2 = unrollPI(b1, b2, wrap=wrap) if max(abs(db), abs(a2 - a1)) < EPS: raise IntersectionError(start=start, end=end, txt='null path' + n) # note, in EdWilliams.org/avform.htm W is + and E is - b21, b12 = db * 0.5, -(b1 + b2) * 0.5 sb21, cb21, sb12, cb12, \ sa21, _, sa12, _ = sincos2(b21, b12, a1 - a2, a1 + a2) x = _Nvector(sa21 * sb12 * cb21 - sa12 * cb12 * sb21, sa21 * cb12 * cb21 + sa12 * sb12 * sb21, cos(a1) * cos(a2) * sin(db)) # ll=start return x.unit(), (db, (a2 - a1)) # negated d
def radical2(distance, radius1, radius2): '''Compute the I{radical ratio} and I{radical line} of two U{intersecting circles<https://MathWorld.Wolfram.com/ Circle-CircleIntersection.html>}. The I{radical line} is perpendicular to the axis thru the centers of the circles at C{(0, 0)} and C{(B{distance}, 0)}. @arg distance: Distance between the circle centers (C{scalar}). @arg radius1: Radius of the first circle (C{scalar}). @arg radius2: Radius of the second circle (C{scalar}). @return: A L{Radical2Tuple}C{(ratio, xline)} where C{0.0 <= ratio <= 1.0} and C{xline} is along the B{C{distance}}. @raise IntersectionError: The B{C{distance}} exceeds the sum of B{C{radius1}} and B{C{radius2}}. @raise UnitError: Invalid B{C{distance}}, B{C{radius1}} or B{C{radius2}}. ''' d = Distance_(distance) r1 = Radius_(radius1=radius1) r2 = Radius_(radius2=radius2) if d > (r1 + r2): raise IntersectionError(distance=d, radius1=r1, radius2=r2, txt=_too_(_distant_)) return _radical2(d, r1, r2)
def _trilaterate5(p1, d1, p2, d2, p3, d3, area=True, eps=EPS1, radius=R_M, wrap=False): # (INTERNAL) Trilaterate three points by area overlap or # by perimeter intersection of three circles (radius is # only needed for spherical LatLon.distanceTo) r1 = Distance_(distance1=d1) r2 = Distance_(distance2=d2) r3 = Distance_(distance3=d3) m = 0 if area else (r1 + r2 + r3) pc = 0 t = [] for _ in range(3): try: c1, c2 = p1.intersections2(r1, p2, r2, wrap=wrap) if area: # check overlap if c1 is c2: # abutting c = c1 else: # nearest point on radical c = p3.nearestOn(c1, c2, within=True, wrap=wrap) d = r3 - p3.distanceTo(c, radius=radius, wrap=wrap) if d > eps: # sufficient overlap t.append((d, c)) m = max(m, d) else: # check intersection for c in ((c1,) if c1 is c2 else (c1, c2)): d = abs(r3 - p3.distanceTo(c, radius=radius, wrap=wrap)) if d < eps: # below margin t.append((d, c)) m = min(m, d) except IntersectionError as x: if _near_concentric_ in str(x): # XXX ConcentricError? pc += 1 p1, r1, p2, r2, p3, r3 = p2, r2, p3, r3, p1, r1 # rotate if t: # get min, max, points and count ... t = tuple(sorted(t)) n = len(t), # as tuple # ... or for a single trilaterated result, # min *is* max, min- *is* maxPoint and n=1 return Trilaterate5Tuple(*(t[0] + t[-1] + n)) if area and pc == 3: # all pairwise concentric ... r, p = min((r1, p1), (r2, p2), (r3, p3)) # ... return smallest point twice, the smallest # and largest distance and n=0 for concentric return Trilaterate5Tuple(float(r), p, float(max(r1, r2, r3)), p, 0) f = max if area else min t = _no_(_overlap_ if area else _intersection_) t = '%s (%s %.3f)' % (t, f.__name__, m) raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
def trilaterate3d2(center1, radius1, center2, radius2, center3, radius3, eps=EPS, Vector=None, **Vector_kwds): '''Trilaterate three spheres, each given as a (3d) center point and radius. @arg center1: Center of the 1st sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius1: Radius of the 1st sphere (same C{units} as C{x}, C{y} and C{z}). @arg center2: Center of the 2nd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of this sphere (same C{units} as C{x}, C{y} and C{z}). @arg center3: Center of the 3rd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius3: Radius of the 3rd sphere (same C{units} as C{x}, C{y} and C{z}). @kwarg eps: Tolerance (C{scalar}), same units as C{x}, C{y}, and C{z}. @kwarg Vector: Class to return intersections (L{Vector3d} or C{Vector3Tuple}) or C{None} for L{Vector3d}. @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments, ignored if C{B{Vector}=None}. @return: 2-Tuple with two trilaterated points, each a B{C{Vector}} instance. Both points are the same instance if all three spheres abut/intersect in a single point. @raise ImportError: Package C{numpy} not found, not installed or older than version 1.15. @raise IntersectionError: No intersection, colinear or concentric centers or trilateration failed some other way. @raise TypeError: Invalid B{C{center1}}, B{C{center2}} or B{C{center3}}. @raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{radius3}}. @note: Package U{numpy<https://pypi.org/project/numpy>} is required, version 1.15 or later. @see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration Problem}<https://www.ResearchGate.net/publication/ 275027725_An_Algebraic_Solution_to_the_Multilateration_Problem>} and U{I{implementation}<https://www.ResearchGate.net/publication/ 288825016_Trilateration_Matlab_Code>}. ''' try: return _trilaterate3d2(_otherV3d(center1=center1), Radius_(radius1=radius1, low=eps), center2, radius2, center3, radius3, eps=eps, Vector=Vector, **Vector_kwds) except (AssertionError, FloatingPointError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, center3=center3, radius3=radius3, txt=str(x))
def trilaterate5( self, distance1, point2, distance2, point3, distance3, # PYCHOK signature area=False, eps=EPS1, radius=R_M, wrap=False): '''B{Not implemented} for C{B{area}=True} or C{B{wrap}=True} and falls back to method C{trilaterate} otherwise. @return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)} with a single trilaterated intersection C{minPoint I{is} maxPoint}, C{min I{is} max} the nearest intersection margin and count C{n = 1}. @raise IntersectionError: No intersection, trilateration failed. @raise NotImplementedError: Keyword argument C{B{area}=True} or B{C{wrap}=True} not (yet) supported. @raise TypeError: Invalid B{C{point2}} or B{C{point3}}. @raise ValueError: Some B{C{points}} coincide or invalid B{C{distance1}}, B{C{distance2}}, B{C{distance3}} or B{C{radius}}. ''' if area or wrap: from pygeodesy.named import notImplemented notImplemented(self, self.trilaterate5, area=area, wrap=wrap) t = _trilaterate(self, distance1, self.others(point2=point2), distance2, self.others(point3=point3), distance3, radius=radius, height=None, useZ=True, LatLon=self.classof) # ... and handle B{C{eps}} and C{IntersectionError} as # method C{.latlonBase.LatLonBase.trilaterate2} d = self.distanceTo(t, radius=radius, wrap=wrap) # PYCHOK distanceTo d = abs(distance1 - d), abs(distance2 - d), abs(distance3 - d) d = float(min(d)) if d < eps: # min is max, minPoint is maxPoint return Trilaterate5Tuple(d, t, d, t, 1) # n = 1 t = _SPACE_(_no_(_intersection_), Fmt.PAREN(min.__name__, Fmt.f(d, prec=3))) raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
def intersections2(center1, rad1, center2, rad2, radius=R_M, height=None, wrap=True, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection points of two circles each defined by a center point and a radius. @arg center1: Center of the first circle (L{LatLon}). @arg rad1: Radius of the first circle (C{meter} or C{radians}, see B{C{radius}}). @arg center2: Center of the second circle (L{LatLon}). @arg rad2: Radius of the second circle (C{meter} or C{radians}, see B{C{radius}}). @kwarg radius: Mean earth radius (C{meter} or C{None} if both B{C{rad1}} and B{C{rad2}} are given in C{radians}). @kwarg height: Optional height for the intersection points, overriding the "radical height" at the "radical line" between both centers (C{meter}) or C{None}. @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection points (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: 2-Tuple of the intersection points, each a B{C{LatLon}} instance or L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. For abutting circles, both intersection points are the same instance. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles. @raise TypeError: If B{C{center1}} or B{C{center2}} not L{LatLon}. @raise ValueError: Invalid B{C{rad1}}, B{C{rad2}}, B{C{radius}} or B{C{height}}. @note: Courtesy U{Samuel Čavoj<https://GitHub.com/mrJean1/PyGeodesy/issues/41>}. @see: This U{Answer<https://StackOverflow.com/questions/53324667/ find-intersection-coordinates-of-two-circles-on-earth/53331953>}. ''' c1 = _T00.others(center1, name=_center1_) c2 = _T00.others(center2, name=_center2_) try: return _intersects2(c1, rad1, c2, rad2, radius=radius, height=height, wrap=wrap, LatLon=LatLon, **LatLon_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2, txt=str(x))
def trilaterate3d2(self, radius, center2, radius2, center3, radius3, eps=EPS): '''Trilaterate this and two other spheres, each given as a (3d) center and radius. @arg radius: Radius of this sphere (same C{units} as this C{x}, C{y} and C{z}). @arg center2: Center of the 2nd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of this sphere (same C{units} as this C{x}, C{y} and C{z}). @arg center3: Center of the 3rd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius3: Radius of the 3rd sphere (same C{units} as this C{x}, C{y} and C{z}). @kwarg eps: Tolerance (C{scalar}), same units as C{x}, C{y}, and C{z}. @return: 2-Tuple with two trilaterated points, each an instance of this L{Vector3d} (sub-)class. Both points are the same instance if all three spheres intersect or abut in a single point. @raise ImportError: Package C{numpy} not found, not installed or older than version 1.15. @raise IntersectionError: No intersection, colinear or near concentric centers or trilateration failed some other way. @raise TypeError: Invalid B{C{center2}} or B{C{center3}}. @raise UnitError: Invalid B{C{radius}}, B{C{radius2}} or B{C{radius3}}. @note: Package U{numpy<https://pypi.org/project/numpy>} is required, version 1.15 or later. @see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration Problem}<https://www.ResearchGate.net/publication/ 275027725_An_Algebraic_Solution_to_the_Multilateration_Problem>} and U{I{implementation}<https://www.ResearchGate.net/publication/ 288825016_Trilateration_Matlab_Code>}. ''' try: return _trilaterate3d2(self if self.name else _otherV3d(center=self), Radius_(radius, low=eps), center2, radius2, center3, radius3, eps=eps, Vector=self.classof) except (AssertionError, FloatingPointError) as x: raise IntersectionError(center=self, radius=radius, center2=center2, radius2=radius2, center3=center3, radius3=radius3, txt=str(x))
def intersections2(center1, radius1, center2, radius2, sphere=True, Vector=None, **Vector_kwds): '''Compute the intersection of two spheres or circles, each defined by a center point and a radius. @arg center1: Center of the first sphere or circle (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius1: Radius of the first sphere or circle (same units as the B{C{center1}} coordinates). @arg center2: Center of the second sphere or circle (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of the second sphere or circle (same units as the B{C{center1}} and B{C{center2}} coordinates). @kwarg sphere: If C{True} compute the center and radius of the intersection of two spheres. If C{False}, ignore the C{z}-component and compute the intersection of two circles (C{bool}). @kwarg Vector: Class to return intersections (L{Vector3d} or C{Vector3Tuple}) or C{None} for L{Vector3d}. @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments, ignored if C{B{Vector}=None}. @return: If B{C{sphere}} is C{True}, a 2-Tuple of the C{center} and C{radius} of the intersection of the spheres. The C{radius} is C{0.0} for abutting spheres. If B{C{sphere}} is C{False}, a 2-tuple of the intersection points of two circles. For abutting circles, both points are the same B{C{Vector}} instance. @raise IntersectionError: Concentric, invalid or non-intersecting spheres or circles. @raise UnitError: Invalid B{C{radius1}} or B{C{radius2}}. @see: U{Sphere-Sphere<https://MathWorld.Wolfram.com/Sphere- SphereIntersection.html>} and U{circle-circle <https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>} intersections. ''' try: return _intersects2(center1, Radius_(radius1=radius1), center2, Radius_(radius2=radius2), sphere=sphere, Vector=Vector, **Vector_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))
def _rads3(rad1, rad2, radius): # in .sphericalTrigonometry '''(INTERNAL) Convert radii to radians. ''' r1 = Radius_(rad1=rad1) r2 = Radius_(rad2=rad2) if radius is not None: # convert radii to radians r = _1_0 / Radius_(radius=radius) r1 *= r r2 *= r x = r1 < r2 if x: r1, r2 = r2, r1 if r1 > PI: raise IntersectionError(rad1=rad1, rad2=rad2, txt=_exceed_PI_radians_) return r1, r2, x
def _rads3(rad1, rad2, radius): # in .sphericalTrigonometry '''(INTERNAL) Convert radii to radians. ''' r1 = Radius_(rad1, name='rad1') r2 = Radius_(rad2, name='rad2') r = radius if r is not None: # convert radii to radians r = 1.0 / Radius_(r, name=_radius_) r1 *= r r2 *= r if r1 < r2: r1, r2, r = r2, r1, True else: r = False if r1 > PI: raise IntersectionError(rad1=rad1, rad2=rad2, txt='exceeds PI radians') return r1, r2, r
def _intersections2(center1, radius1, center2, radius2, height=None, wrap=True, equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): # (INTERNAL) Iteratively compute the intersection points of two circles # each defined by an (ellipsoidal) center point and a radius, imported # by .ellipsoidalKarney and -Vincenty c1 = _xellipsoidal(center1=center1) c2 = c1.others(center2=center2) r1 = Radius_(radius1=radius1) r2 = Radius_(radius2=radius2) try: return _intersects2(c1, r1, c2, r2, height=height, wrap=wrap, equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))
def _trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, useZ=False, **LatLon_LatLon_kwds): # (INTERNAL) Locate a point at given distances from # three other points, see LatLon.triangulate above def _nd2(p, d, r, _i_, *qs): # .toNvector and angular distance squared for q in qs: if p.isequalTo(q, EPS): raise _ValueError(points=p, txt=_coincident_) return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2 r = Radius_(radius) n1, r12 = _nd2(point1, distance1, r, _1_) n2, r22 = _nd2(point2, distance2, r, _2_, point1) n3, r32 = _nd2(point3, distance3, r, _3_, point1, point2) # the following uses x,y coordinate system with origin at n1, x axis n1->n2 y = n3.minus(n1) x = n2.minus(n1) z = None d = x.length # distance n1->n2 if d > EPS_2: # and y.length > EPS_2: X = x.unit() # unit vector in x direction n1->n2 i = X.dot(y) # signed magnitude of x component of n1->n3 Y = y.minus(X.times(i)).unit() # unit vector in y direction j = Y.dot(y) # signed magnitude of y component of n1->n3 if abs(j) > EPS_2: # courtesy Carlos Freitas <https://GitHub.com/mrJean1/PyGeodesy/issues/33> x = fsum_(r12, -r22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(r12, -r32, i**2, j**2, -2 * x * i) / ( 2 * j) # ... y-component # courtesy AleixDev <https://GitHub.com/mrJean1/PyGeodesy/issues/43> z = fsum_(max(r12, r22, r32), -(x**2), -(y**2)) # XXX not just r12! if z > EPS: n = n1.plus(X.times(x)).plus(Y.times(y)) if useZ: # include Z component Z = X.cross(Y) # unit vector perpendicular to plane n = n.plus(Z.times(sqrt(z))) if height is None: h = fidw((point1.height, point2.height, point3.height), map1(fabs, distance1, distance2, distance3)) else: h = Height(height) kwds = _xkwds(LatLon_LatLon_kwds, height=h) return n.toLatLon(** kwds) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or abs(j) < EPS_2 or z < EPS t = NN(_no_, _intersection_, _SPACE_) raise IntersectionError(point1=point1, distance1=distance1, point2=point2, distance2=distance2, point3=point3, distance3=distance3, txt=unstr(t, z=z, useZ=useZ))
def intersections2( center1, rad1, center2, rad2, radius=R_M, # MCCABE 13 height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection points of two circles each defined by a center point and radius. @arg center1: Center of the first circle (L{LatLon}). @arg rad1: Radius of the second circle (C{meter} or C{radians}, see B{C{radius}}). @arg center2: Center of the second circle (L{LatLon}). @arg rad2: Radius of the second circle (C{meter} or C{radians}, see B{C{radius}}). @kwarg radius: Mean earth radius (C{meter} or C{None} if both B{C{rad1}} and B{C{rad2}} are given in C{radians}). @kwarg height: Optional height for the intersection point, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection points (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: 2-Tuple of the intersection points, each a B{C{LatLon}} instance or L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. The intersection points are the same instance for abutting circles. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles. @raise TypeError: If B{C{center1}} or B{C{center2}} not L{LatLon}. @raise ValueError: Invalid B{C{rad1}}, B{C{rad2}}, B{C{radius}} or B{C{height}}. @note: Courtesy U{Samuel Čavoj<https://GitHub.com/mrJean1/PyGeodesy/issues/41>}. @see: This U{Answer<https://StackOverflow.com/questions/53324667/ find-intersection-coordinates-of-two-circles-on-earth/53331953>}. ''' def _destination1(bearing): a, b = _destination2(a1, b1, r1, bearing) return _latlon3(degrees90(a), degrees180(b), h, intersections2, LatLon, **LatLon_kwds) _Trll.others(center1, name='center1') _Trll.others(center2, name='center2') a1, b1 = center1.philam a2, b2 = center2.philam r1, r2, x = _rads3(rad1, rad2, radius) if x: a1, b1, a2, b2 = a2, b2, a1, b1 db, _ = unrollPI(b1, b2, wrap=wrap) d = vincentys_(a2, a1, db) # radians if d < max(r1 - r2, EPS): raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2, txt=_near_concentric_) x = fsum_(r1, r2, -d) if x > EPS: try: sd, cd, s1, c1, _, c2 = sincos2(d, r1, r2) x = sd * s1 if abs(x) < EPS: raise ValueError x = acos1((c2 - cd * c1) / x) except ValueError: raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2) elif x < 0: raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2, txt=_too_distant_) b = bearing_(a1, b1, a2, b2, final=False, wrap=wrap) if height is None: h = fmean((center1.height, center2.height)) else: Height(height) if abs(x) > EPS: return _destination1(b + x), _destination1(b - x) else: # abutting circles x = _destination1(b) return x, x
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @arg start2: Start point of the second path (L{LatLon}). @arg end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @kwarg height: Optional height for the intersection point, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The intersection point (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise IntersectionError: Intersection is ambiguous or infinite or the paths are coincident, colinear or parallel. @raise TypeError: A B{C{start}} or B{C{end}} point not L{LatLon}. @raise ValueError: Invalid B{C{height}}. @example: >>> p = LatLon(51.8853, 0.2545) >>> s = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.547, s, 32.435) # '50.9078°N, 004.5084°E' ''' _Trll.others(start1, name=_start1_) _Trll.others(start2, name=_start2_) hs = [start1.height, start2.height] a1, b1 = start1.philam a2, b2 = start2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = vincentys_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = favg(a1, a2), favg(b1, b2) # see <https://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings sa1, ca1, sa2, ca2, sr12, cr12 = sincos2(a1, a2, r12) x1, x2 = (sr12 * ca1), (sr12 * ca2) if abs(x1) < EPS or abs(x2) < EPS: raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt='parallel') # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <https://www.EdWilliams.org/avform.htm#Math> t1, t2 = map1(acos1, (sa2 - sa1 * cr12) / x1, (sa1 - sa2 * cr12) / x2) if sin(db) > 0: t12, t21 = t1, PI2 - t2 else: t12, t21 = PI2 - t1, t2 t13, t23 = map1(radiansPI2, end1, end2) x1, x2 = map1( wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, cx1, sx2, cx2 = sincos2(x1, x2) if sx1 == 0 and sx2 == 0: # max(abs(sx1), abs(sx2)) < EPS raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt='infinite') sx3 = sx1 * sx2 # if sx3 < 0: # raise IntersectionError(start1=start1, end1=end1, # start2=start2, end2=end2, txt=_ambiguous_) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) # choose antipode for opposing bearings if _xb(a1, b1, end1, a, b, wrap) < 0 or \ _xb(a2, b2, end2, a, b, wrap) < 0: a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple else: # end point(s) or bearing(s) x1, d1 = _x3d2(start1, end1, wrap, _1_, hs) x2, d2 = _x3d2(start2, end2, wrap, _2_, hs) x = x1.cross(x2) if x.length < EPS: # [nearly] colinear or parallel paths raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt=_colinear_) a, b = x.philam # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) if d1: d2 = _xdot(d2, a2, b2, a, b, wrap) if (d2 < 0 and d1 > 0) or (d2 > 0 and d1 < 0): a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple h = fmean(hs) if height is None else Height(height) return _latlon3(degrees90(a), degrees180(b), h, intersection, LatLon, **LatLon_kwds)
def _intersections2(center1, radius1, center2, radius2, height=None, wrap=True, equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): '''Iteratively compute the intersection points of two circles each defined by an (ellipsoidal) center point and a radius. @arg center1: Center of the first circle (ellipsoidal C{LatLon}). @arg radius1: Radius of the first circle (C{meter}). @arg center2: Center of the second circle (ellipsoidal C{LatLon}). @arg radius2: Radius of the second circle (C{meter}). @kwarg height: Optional height for the intersection points, overriding the "radical height" at the "radical line" between both centers (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg equidistant: An azimuthal equidistant projection class (L{Equidistant} or L{EquidistantKarney}) or C{None} for function L{azimuthal.equidistant}. @kwarg tol: Convergence tolerance (C{meter}). @kwarg LatLon: Optional class to return the intersection points (ellipsoidal C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: 2-Tuple of the intersection points, each a B{C{LatLon}} instance or L{LatLon4Tuple}C{(lat, lon, height, datum)} if B{C{LatLon}} is C{None}. For abutting circles, the intersection points are the same instance. @raise ImportError: If B{C{equidistant}} is L{EquidistantKarney}) and package U{geographiclib <https://PyPI.org/project/geographiclib>} not installed or not found. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles or no convergence for B{C{tol}}. @raise TypeError: If B{C{center1}} or B{C{center2}} not ellipsoidal. @raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{height}}. @see: U{The B{ellipsoidal} case<https://GIS.StackExchange.com/questions/48937/ calculating-intersection-of-two-circles>}, U{Karney's paper <https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section 14 I{Maritime Boundaries}, U{circle-circle<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>} and U{sphere-sphere<https://MathWorld.Wolfram.com/Sphere-SphereIntersection.html>} intersections. ''' c1 = _xellipsoidal(center1=center1) c2 = c1.others(center2, name=_center2_) r1 = Radius_(radius1, name=_radius1_) r2 = Radius_(radius2, name=_radius2_) try: return _intersects2(c1, r1, c2, r2, height=height, wrap=wrap, equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))