Exemplo n.º 1
0
    def subtract_and_intersect_circle(self, center, radius):
        '''
        Circle subtraction / intersection only supported by 2-gon regions, otherwise a VennRegionException is thrown.
        In addition, such an exception will be thrown if the circle to be subtracted is completely within the region and forms a "hole".
        
        The result may be either a VennArcgonRegion or a VennMultipieceRegion (the latter happens when the circle "splits" a crescent in two).
        '''
        if len(self.arcs) != 2:
            raise VennRegionException(
                "Circle subtraction and intersection with poly-arc regions is currently only supported for 2-arc-gons."
            )

        # In the following we consider the 2-arc-gon case.
        # Before we do anything, we check for a special case, where the circle of interest is one of the two circles forming the arcs.
        # In this case we can determine the answer quite easily.
        matching_arcs = [
            a for a in self.arcs if a.lies_on_circle(center, radius)
        ]
        if len(matching_arcs) != 0:
            # If the circle matches a positive arc, the result is [empty, self], otherwise [self, empty]
            return [VennEmptyRegion(), self
                    ] if matching_arcs[0].direction else [
                        self, VennEmptyRegion()
                    ]

        # Consider the intersection points of the circle with the arcs.
        # If any of the intersection points corresponds exactly to any of the arc's endpoints, we will end up with
        # a lot of messy special cases (as if the usual situation is not messy enough, eh).
        # To avoid that, we cheat by slightly increasing the circle's radius until this is not the case any more.
        center = np.asarray(center)
        illegal_intersections = [a.start_point() for a in self.arcs]
        while True:
            valid = True
            intersections = [
                a.intersect_circle(center, radius) for a in self.arcs
            ]
            for ints in intersections:
                for pt in ints:
                    for illegal_pt in illegal_intersections:
                        if np.all(abs(pt - illegal_pt) < tol):
                            valid = False
            if valid:
                break
            else:
                radius += tol

        # There must be an even number of those points in total.
        # (If this is not the case, then we have an unfortunate case with weird numeric errors [TODO: find examples and deal with it?]).
        # There are three possibilities with the following subcases:
        #   I. No intersection points
        #       a) The polyarc is completely within the circle.
        #           result = [ empty, self ]
        #       b) The polyarc is completely outside the circle.
        #           result = [ self, empty ]
        #   II. Four intersection points, two for each arc. Points x1, x2 for arc X and y1, y2 for arc Y, ordered along the arc.
        #       a) The polyarc endpoints are both outside the circle.
        #           result_subtraction = a combination of two 3-arc polyarcs:
        #               1: {X - start to x1,
        #                   x1 to y2 along circle (negative direction)),
        #                   Y - y2 to end}
        #               2: {Y start to y1,
        #                   y1 to x2 along circle (negative direction)),
        #                   X - x2 to end}
        #       b) The polyarc endpoints are both inside the circle
        #               same as above, but the "along circle" arc directions are flipped and subtraction/intersection parts are exchanged
        #   III. Two intersection points
        #       a) One arc, X, has two intersection points i & j, another arc, Y, has no intersection points
        #           a.1) Polyarc endpoints are outside the circle
        #               result_subtraction = {X from start to i, circle i to j (direction = negative), X j to end, Y}
        #               result_intersection = {X i to j, circle j to i (direction = positive}
        #           a.2) Polyarc endpoints are inside the circle
        #               result_subtraction = {X i to j, circle j to i negative}
        #               result_intersection = {X 0 to i, circle i to j positive, X j to end, Y}
        #       b) Both arcs, X and Y, have one intersection point each. In this case one of the arc endpoints must be inside circle, another outside.
        #          call the arc that starts with the outside point X, the other arc Y.
        #           result_subtraction = {X start to intersection, intersection to intersection along circle (negative direction), Y from intersection to end}
        #           result_intersection = {X intersection to end, Y start to intersecton, intersection to intersecion along circle (positive)}
        center = np.asarray(center)
        intersections = [a.intersect_circle(center, radius) for a in self.arcs]

        if len(intersections[0]) == 0 and len(intersections[1]) == 0:
            # Case I
            if point_in_circle(self.arcs[0].start_point(), center, radius):
                # Case I.a)
                return [VennEmptyRegion(), self]
            else:
                # Case I.b)
                return [self, VennEmptyRegion()]
        elif len(intersections[0]) == 2 and len(intersections[1]) == 2:
            # Case II. a) or b)
            case_II_a = not point_in_circle(self.arcs[0].start_point(), center,
                                            radius)

            a1 = self.arcs[0].subarc_between_points(None, intersections[0][0])
            a2 = Arc(center, radius,
                     vector_angle_in_degrees(intersections[0][0] - center),
                     vector_angle_in_degrees(intersections[1][1] - center),
                     not case_II_a)
            a2.fix_360_to_0()
            a3 = self.arcs[1].subarc_between_points(intersections[1][1], None)
            piece1 = VennArcgonRegion([a1, a2, a3])

            b1 = self.arcs[1].subarc_between_points(None, intersections[1][0])
            b2 = Arc(center, radius,
                     vector_angle_in_degrees(intersections[1][0] - center),
                     vector_angle_in_degrees(intersections[0][1] - center),
                     not case_II_a)
            b2.fix_360_to_0()
            b3 = self.arcs[0].subarc_between_points(intersections[0][1], None)
            piece2 = VennArcgonRegion([b1, b2, b3])

            subtraction = VennMultipieceRegion([piece1, piece2])

            c1 = self.arcs[0].subarc(a1.to_angle, b3.from_angle)
            c2 = b2.reversed()
            c3 = self.arcs[1].subarc(b1.to_angle, a3.from_angle)
            c4 = a2.reversed()
            intersection = VennArcgonRegion([c1, c2, c3, c4])

            return [subtraction, intersection
                    ] if case_II_a else [intersection, subtraction]
        else:
            # Case III. Yuck.
            if len(intersections[0]) == 0 or len(intersections[1]) == 0:
                # Case III.a)
                x = 0 if len(intersections[0]) != 0 else 1
                y = 1 - x
                if len(intersections[x]) != 2:
                    warnings.warn(
                        "Numeric precision error during polyarc intersection, case IIIa. Expect wrong results."
                    )
                    intersections[x] = [
                        intersections[x][0], intersections[x][0]
                    ]  # This way we'll at least produce some result, although it will probably be wrong
                if not point_in_circle(self.arcs[0].start_point(), center,
                                       radius):
                    # Case III.a.1)
                    #   result_subtraction = {X from start to i, circle i to j (direction = negative), X j to end, Y}
                    a1 = self.arcs[x].subarc_between_points(
                        None, intersections[x][0])
                    a2 = Arc(
                        center, radius,
                        vector_angle_in_degrees(intersections[x][0] - center),
                        vector_angle_in_degrees(intersections[x][1] - center),
                        False)
                    a3 = self.arcs[x].subarc_between_points(
                        intersections[x][1], None)
                    a4 = self.arcs[y]
                    subtraction = VennArcgonRegion([a1, a2, a3, a4])

                    #   result_intersection = {X i to j, circle j to i (direction = positive)}
                    b1 = self.arcs[x].subarc(a1.to_angle, a3.from_angle)
                    b2 = a2.reversed()
                    intersection = VennArcgonRegion([b1, b2])

                    return [subtraction, intersection]
                else:
                    # Case III.a.2)
                    #   result_subtraction = {X i to j, circle j to i negative}
                    a1 = self.arcs[x].subarc_between_points(
                        intersections[x][0], intersections[x][1])
                    a2 = Arc(
                        center, radius,
                        vector_angle_in_degrees(intersections[x][1] - center),
                        vector_angle_in_degrees(intersections[x][0] - center),
                        False)
                    subtraction = VennArcgonRegion([a1, a2])

                    #   result_intersection = {X 0 to i, circle i to j positive, X j to end, Y}
                    b1 = self.arcs[x].subarc(None, a1.from_angle)
                    b2 = a2.reversed()
                    b3 = self.arcs[x].subarc(a1.to_angle, None)
                    b4 = self.arcs[y]
                    intersection = VennArcgonRegion([b1, b2, b3, b4])

                    return [subtraction, intersection]
            else:
                # Case III.b)
                if len(intersections[0]) == 2 or len(intersections[1]) == 2:
                    warnings.warn(
                        "Numeric precision error during polyarc intersection, case IIIb. Expect wrong results."
                    )

                # One of the arcs must start outside the circle, call it x
                x = 0 if not point_in_circle(self.arcs[0].start_point(),
                                             center, radius) else 1
                y = 1 - x

                a1 = self.arcs[x].subarc_between_points(
                    None, intersections[x][0])
                a2 = Arc(center, radius,
                         vector_angle_in_degrees(intersections[x][0] - center),
                         vector_angle_in_degrees(intersections[y][0] - center),
                         False)
                a3 = self.arcs[y].subarc_between_points(
                    intersections[y][0], None)
                subtraction = VennArcgonRegion([a1, a2, a3])

                b1 = self.arcs[x].subarc(a1.to_angle, None)
                b2 = self.arcs[y].subarc(None, a3.from_angle)
                b3 = a2.reversed()
                intersection = VennArcgonRegion([b1, b2, b3])
                return [subtraction, intersection]
Exemplo n.º 2
0
    def subtract_and_intersect_circle(self, center, radius):
        '''
        Circle subtraction / intersection only supported by 2-gon regions, otherwise a VennRegionException is thrown.
        In addition, such an exception will be thrown if the circle to be subtracted is completely within the region and forms a "hole".
        
        The result may be either a VennArcgonRegion or a VennMultipieceRegion (the latter happens when the circle "splits" a crescent in two).
        '''
        if len(self.arcs) != 2:
            raise VennRegionException("Circle subtraction and intersection with poly-arc regions is currently only supported for 2-arc-gons.")
        
        # In the following we consider the 2-arc-gon case.
        # Before we do anything, we check for a special case, where the circle of interest is one of the two circles forming the arcs.
        # In this case we can determine the answer quite easily.
        matching_arcs = [a for a in self.arcs if a.lies_on_circle(center, radius)]
        if len(matching_arcs) != 0:
            # If the circle matches a positive arc, the result is [empty, self], otherwise [self, empty]
            return [VennEmptyRegion(), self] if matching_arcs[0].direction else [self, VennEmptyRegion()]
            
        # Consider the intersection points of the circle with the arcs.
        # If any of the intersection points corresponds exactly to any of the arc's endpoints, we will end up with
        # a lot of messy special cases (as if the usual situation is not messy enough, eh).
        # To avoid that, we cheat by slightly increasing the circle's radius until this is not the case any more.
        center = np.asarray(center)
        illegal_intersections = [a.start_point() for a in self.arcs]
        while True:
            valid = True
            intersections = [a.intersect_circle(center, radius) for a in self.arcs]
            for ints in intersections:
                for pt in ints:
                    for illegal_pt in illegal_intersections:
                        if np.all(abs(pt - illegal_pt) < tol):
                            valid = False
            if valid:
                break
            else:
                radius += tol
                

        # There must be an even number of those points in total.
        # (If this is not the case, then we have an unfortunate case with weird numeric errors [TODO: find examples and deal with it?]).
        # There are three possibilities with the following subcases:
        #   I. No intersection points
        #       a) The polyarc is completely within the circle.
        #           result = [ empty, self ]
        #       b) The polyarc is completely outside the circle.
        #           result = [ self, empty ]
        #   II. Four intersection points, two for each arc. Points x1, x2 for arc X and y1, y2 for arc Y, ordered along the arc.
        #       a) The polyarc endpoints are both outside the circle.
        #           result_subtraction = a combination of two 3-arc polyarcs:
        #               1: {X - start to x1,
        #                   x1 to y2 along circle (negative direction)),
        #                   Y - y2 to end}
        #               2: {Y start to y1,
        #                   y1 to x2 along circle (negative direction)),
        #                   X - x2 to end}
        #       b) The polyarc endpoints are both inside the circle
        #               same as above, but the "along circle" arc directions are flipped and subtraction/intersection parts are exchanged
        #   III. Two intersection points
        #       a) One arc, X, has two intersection points i & j, another arc, Y, has no intersection points
        #           a.1) Polyarc endpoints are outside the circle
        #               result_subtraction = {X from start to i, circle i to j (direction = negative), X j to end, Y}
        #               result_intersection = {X i to j, circle j to i (direction = positive}
        #           a.2) Polyarc endpoints are inside the circle
        #               result_subtraction = {X i to j, circle j to i negative}
        #               result_intersection = {X 0 to i, circle i to j positive, X j to end, Y}
        #       b) Both arcs, X and Y, have one intersection point each. In this case one of the arc endpoints must be inside circle, another outside.
        #          call the arc that starts with the outside point X, the other arc Y.
        #           result_subtraction = {X start to intersection, intersection to intersection along circle (negative direction), Y from intersection to end}
        #           result_intersection = {X intersection to end, Y start to intersecton, intersection to intersecion along circle (positive)}
        center = np.asarray(center)
        intersections = [a.intersect_circle(center, radius) for a in self.arcs]
        
        if len(intersections[0]) == 0 and len(intersections[1]) == 0:
            # Case I
            if point_in_circle(self.arcs[0].start_point(), center, radius):
                # Case I.a)
                return [VennEmptyRegion(), self]
            else:
                # Case I.b)
                return [self, VennEmptyRegion()]
        elif len(intersections[0]) == 2 and len(intersections[1]) == 2:
            # Case II. a) or b)
            case_II_a = not point_in_circle(self.arcs[0].start_point(), center, radius)
            
            a1 = self.arcs[0].subarc_between_points(None, intersections[0][0])
            a2 = Arc(center, radius,
                     vector_angle_in_degrees(intersections[0][0] - center),
                     vector_angle_in_degrees(intersections[1][1] - center),
                     not case_II_a)
            a2.fix_360_to_0()
            a3 = self.arcs[1].subarc_between_points(intersections[1][1], None)
            piece1 = VennArcgonRegion([a1, a2, a3])
            
            b1 = self.arcs[1].subarc_between_points(None, intersections[1][0])
            b2 = Arc(center, radius,
                     vector_angle_in_degrees(intersections[1][0] - center),
                     vector_angle_in_degrees(intersections[0][1] - center),
                     not case_II_a)
            b2.fix_360_to_0()
            b3 = self.arcs[0].subarc_between_points(intersections[0][1], None)
            piece2 = VennArcgonRegion([b1, b2, b3])
            
            subtraction = VennMultipieceRegion([piece1, piece2])
            
            c1 = self.arcs[0].subarc(a1.to_angle, b3.from_angle)
            c2 = b2.reversed()
            c3 = self.arcs[1].subarc(b1.to_angle, a3.from_angle)
            c4 = a2.reversed()
            intersection = VennArcgonRegion([c1, c2, c3, c4])
            
            return [subtraction, intersection] if case_II_a else [intersection, subtraction]
        else:
            # Case III. Yuck.
            if len(intersections[0]) == 0 or len(intersections[1]) == 0:
                # Case III.a)
                x = 0 if len(intersections[0]) != 0 else 1
                y = 1 - x
                if len(intersections[x]) != 2:
                    warnings.warn("Numeric precision error during polyarc intersection, case IIIa. Expect wrong results.")
                    intersections[x] = [intersections[x][0], intersections[x][0]]  # This way we'll at least produce some result, although it will probably be wrong
                if not point_in_circle(self.arcs[0].start_point(), center, radius):
                    # Case III.a.1)
                    #   result_subtraction = {X from start to i, circle i to j (direction = negative), X j to end, Y}
                    a1 = self.arcs[x].subarc_between_points(None, intersections[x][0])
                    a2 = Arc(center, radius,
                             vector_angle_in_degrees(intersections[x][0] - center),
                             vector_angle_in_degrees(intersections[x][1] - center),
                             False)
                    a3 = self.arcs[x].subarc_between_points(intersections[x][1], None)
                    a4 = self.arcs[y]
                    subtraction = VennArcgonRegion([a1, a2, a3, a4])
                    
                    #   result_intersection = {X i to j, circle j to i (direction = positive)}
                    b1 = self.arcs[x].subarc(a1.to_angle, a3.from_angle)
                    b2 = a2.reversed()
                    intersection = VennArcgonRegion([b1, b2])
                    
                    return [subtraction, intersection]
                else:
                    # Case III.a.2)
                    #   result_subtraction = {X i to j, circle j to i negative}
                    a1 = self.arcs[x].subarc_between_points(intersections[x][0], intersections[x][1])
                    a2 = Arc(center, radius,
                             vector_angle_in_degrees(intersections[x][1] - center),
                             vector_angle_in_degrees(intersections[x][0] - center),
                             False)
                    subtraction = VennArcgonRegion([a1, a2])
                    
                    #   result_intersection = {X 0 to i, circle i to j positive, X j to end, Y}
                    b1 = self.arcs[x].subarc(None, a1.from_angle)
                    b2 = a2.reversed()
                    b3 = self.arcs[x].subarc(a1.to_angle, None)
                    b4 = self.arcs[y]
                    intersection = VennArcgonRegion([b1, b2, b3, b4])
                    
                    return [subtraction, intersection]
            else:
                # Case III.b)
                if len(intersections[0]) == 2 or len(intersections[1]) == 2:
                    warnings.warn("Numeric precision error during polyarc intersection, case IIIb. Expect wrong results.")
                
                # One of the arcs must start outside the circle, call it x
                x = 0 if not point_in_circle(self.arcs[0].start_point(), center, radius) else 1
                y = 1 - x
                
                a1 = self.arcs[x].subarc_between_points(None, intersections[x][0])
                a2 = Arc(center, radius,
                         vector_angle_in_degrees(intersections[x][0] - center),
                         vector_angle_in_degrees(intersections[y][0] - center), False)
                a3 = self.arcs[y].subarc_between_points(intersections[y][0], None)
                subtraction = VennArcgonRegion([a1, a2, a3])
                
                b1 = self.arcs[x].subarc(a1.to_angle, None)
                b2 = self.arcs[y].subarc(None, a3.from_angle)
                b3 = a2.reversed()
                intersection = VennArcgonRegion([b1, b2, b3])
                return [subtraction, intersection]