Exemple #1
0
    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
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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))
Exemple #6
0
    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))
Exemple #8
0
    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))
Exemple #9
0
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))
Exemple #10
0
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
Exemple #11
0
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
Exemple #12
0
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))
Exemple #13
0
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)
Exemple #16
0
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))