Exemplo n.º 1
0
class EventsQueue:
    __slots__ = 'context', 'key', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        key = self.key = partial(EventsQueueKey, context.angle_orientation)
        self._queue = PriorityQueue(key=key)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def peek(self) -> Event:
        return self._queue.peek()

    @abstractmethod
    def register(self, segments_endpoints: Iterable[SegmentEndpoints], *,
                 from_test: bool) -> None:
        """
        Registers segments in the events queue.
        """

    @abstractmethod
    def sweep(self, stop_x: Scalar) -> Iterable[LeftEvent]:
        """
        Sweeps plane and emits processed segments' events.
        """

    def _divide_segment(self, event: LeftEvent, break_point: Point) -> None:
        self._queue.push(event.divide(break_point))
        self._queue.push(event.right)
Exemplo n.º 2
0
class Builder:
    __slots__ = ('index', '_beach_line', '_circle_events', '_end_points',
                 'site_events', '_site_event_index')

    def __init__(self,
                 index: int = 0,
                 site_events: Optional[List[SiteEvent]] = None) -> None:
        self.index = index
        self._beach_line = red_black.Tree.from_components([])
        self._circle_events = PriorityQueue(key=itemgetter(0))
        self._end_points = PriorityQueue(key=itemgetter(0))
        self.site_events = [] if site_events is None else site_events
        self._site_event_index = None

    __repr__ = generate_repr(__init__)

    @property
    def beach_line(self) -> List[Tuple[BeachLineKey, BeachLineValue]]:
        return [node.item for node in self._beach_line]

    @property
    def site_event(self) -> SiteEvent:
        return self.site_events[self._site_event_index]

    @property
    def site_event_index(self) -> int:
        return (len(self.site_events)
                if self._site_event_index is None else self._site_event_index)

    def activate_circle_event(self, first_site: SiteEvent,
                              second_site: SiteEvent, third_site: SiteEvent,
                              bisector_node: red_black.Node) -> None:
        event = CircleEvent(0., 0., 0.)
        # check if the three input sites create a circle event
        if compute_circle_event(event, first_site, second_site, third_site):
            # add the new circle event to the circle events queue;
            # update bisector's circle event iterator to point
            # to the new circle event in the circle event queue
            self._circle_events.push((event, bisector_node))
            bisector_node.value.circle_event = event

    def construct(self, output: 'Diagram') -> None:
        self.init_sites_queue()
        self.init_beach_line(output)
        while (self._circle_events
               or self._site_event_index < len(self.site_events)):
            if (not self._circle_events
                    or (self._site_event_index < len(self.site_events)
                        and self.site_event < self._circle_events.peek()[0])):
                self.process_site_event(output)
            else:
                self.process_circle_event(output)
            while (self._circle_events
                   and not self._circle_events.peek()[0].is_active):
                self._circle_events.pop()
        self._beach_line.clear()
        output._build()

    @staticmethod
    def deactivate_circle_event(value: BeachLineValue) -> None:
        if value.circle_event is not None:
            value.circle_event.deactivate()
            value.circle_event = None

    def init_beach_line(self, diagram: 'Diagram') -> None:
        if not self.site_events:
            return
        elif len(self.site_events) == 1:
            # handle single site event case
            diagram._process_single_site(self.site_events[0])
            self._site_event_index += 1
        else:
            skip = 0
            while (self._site_event_index < len(self.site_events)
                   and are_vertical_endpoints(self.site_event.start,
                                              self.site_events[0].start)
                   and self.site_event.is_vertical):
                self._site_event_index += 1
                skip += 1
            if skip == 1:
                # init beach line with the first two sites
                self.init_beach_line_default(diagram)
            else:
                # init beach line with collinear vertical sites
                self.init_beach_line_collinear_sites(diagram)

    def init_beach_line_collinear_sites(self, diagram: 'Diagram') -> None:
        first_index, second_index = 0, 1
        while second_index != self._site_event_index:
            # create a new beach line node
            first_site, second_site = (self.site_events[first_index],
                                       self.site_events[second_index])
            new_node = BeachLineKey(first_site, second_site)
            # update the diagram
            edge, _ = diagram._insert_new_edge(first_site, second_site)
            # insert a new bisector into the beach line
            self._beach_line.insert(new_node, BeachLineValue(edge))
            first_index += 1
            second_index += 1

    def init_beach_line_default(self, diagram: 'Diagram') -> None:
        # get the first and the second site event
        self.insert_new_arc(self.site_events[0], self.site_events[0],
                            self.site_events[1], diagram)
        # the second site was already processed, move the position
        self._site_event_index += 1

    def insert_new_arc(self, first_arc_site: SiteEvent,
                       second_arc_site: SiteEvent, site_event: SiteEvent,
                       output: 'Diagram') -> red_black.Node:
        # create two new bisectors with opposite directions
        new_left_node = BeachLineKey(first_arc_site, site_event)
        new_right_node = BeachLineKey(site_event, second_arc_site)
        # set correct orientation for the first site of the second node
        if site_event.is_segment:
            new_right_node.left_site.inverse()
        # update the output
        edges = output._insert_new_edge(second_arc_site, site_event)
        self._beach_line.insert(new_right_node, BeachLineValue(edges[1]))
        if site_event.is_segment:
            # update the beach line with temporary bisector,
            # that will # disappear after processing site event
            # corresponding to the second endpoint of the segment site
            new_node = BeachLineKey(site_event, site_event)
            new_node.right_site.inverse()
            node = self._beach_line.insert(new_node, BeachLineValue(None))
            # update the data structure that holds temporary bisectors
            self._end_points.push((site_event.end, node))
        return self._beach_line.insert(new_left_node, BeachLineValue(edges[0]))

    def init_sites_queue(self) -> None:
        self.site_events.sort()
        self.site_events = to_unique_just_seen(self.site_events)
        for index, event in enumerate(self.site_events):
            event.sorted_index = index
        self._site_event_index = 0

    def insert_point(self, point: Point) -> int:
        index = self.index
        self.site_events.append(
            SiteEvent.from_point(point,
                                 initial_index=index,
                                 source_category=SourceCategory.SINGLE_POINT))
        self.index += 1
        return index

    def insert_segment(self, segment: Segment) -> int:
        site_events = self.site_events
        index = self.index
        start, end = segment.start, segment.end
        site_events.append(
            SiteEvent.from_point(
                start,
                initial_index=index,
                source_category=SourceCategory.SEGMENT_START_POINT))
        site_events.append(
            SiteEvent.from_point(
                end,
                initial_index=index,
                source_category=SourceCategory.SEGMENT_END_POINT))
        site_events.append(
            SiteEvent(start,
                      end,
                      source_category=SourceCategory.INITIAL_SEGMENT,
                      initial_index=index) if start < end else SiteEvent(
                          end,
                          start,
                          source_category=SourceCategory.REVERSE_SEGMENT,
                          initial_index=index))
        self.index += 1
        return index

    def process_circle_event(self, output: 'Diagram') -> None:
        circle_event, first_node = self._circle_events.pop()
        last_node = first_node
        second_site = copy(first_node.key.right_site)
        second_bisector = first_node.value.edge
        first_node = self._beach_line.predecessor(first_node)
        first_bisector = first_node.value.edge
        first_site = copy(first_node.key.left_site)
        if (not first_site.is_segment and second_site.is_segment
                and second_site.end == first_site.start):
            second_site.inverse()
        first_node.key.right_site = second_site
        first_node.value.edge, _ = output._insert_new_edge_from_intersection(
            first_site, second_site, circle_event, first_bisector,
            second_bisector)
        self._beach_line.remove(last_node)
        last_node = first_node
        if first_node is not self._beach_line.min():
            self.deactivate_circle_event(first_node.value)
            first_node = self._beach_line.predecessor(first_node)
            self.activate_circle_event(first_node.key.left_site, first_site,
                                       second_site, last_node)
        last_node = self._beach_line.successor(last_node)
        if last_node is not red_black.NIL:
            self.deactivate_circle_event(last_node.value)
            self.activate_circle_event(first_site, second_site,
                                       last_node.key.right_site, last_node)

    def process_site_event(self, output: 'Diagram') -> None:
        last_index = self._site_event_index + 1
        if not self.site_event.is_segment:
            while (self._end_points
                   and self._end_points.peek()[0] == self.site_event.start):
                _, node = self._end_points.pop()
                self._beach_line.remove(node)
        else:
            while (last_index < len(self.site_events)
                   and self.site_events[last_index].is_segment
                   and (self.site_events[last_index].start
                        == self.site_event.start)):
                last_index += 1
        # find the node in the binary search tree
        # with left arc lying above the new site point
        new_key = BeachLineKey(self.site_event, self.site_event)
        right_node = self._beach_line.supremum(new_key)
        while self._site_event_index < last_index:
            site_event = copy(self.site_event)
            left_node = right_node
            if right_node is red_black.NIL:
                # the above arc corresponds to the second arc of the last node,
                # move the iterator to the last node
                left_node = self._beach_line.max()
                # get the second site of the last node
                arc_site = left_node.key.right_site
                # insert new nodes into the beach line, update the output
                right_node = self.insert_new_arc(arc_site, arc_site,
                                                 site_event, output)
                # add a candidate circle to the circle event queue;
                # there could be only one new circle event
                # formed by a new bisector and the one on the left
                self.activate_circle_event(left_node.key.left_site,
                                           left_node.key.right_site,
                                           site_event, right_node)
            elif right_node is self._beach_line.min():
                # the above arc corresponds to the first site of the first node
                arc_site = right_node.key.left_site
                # Insert new nodes into the beach line. Update the output.
                left_node = self.insert_new_arc(arc_site, arc_site, site_event,
                                                output)
                # if the site event is a segment, update its direction
                if site_event.is_segment:
                    site_event.inverse()
                # add a candidate circle to the circle event queue;
                # there could be only one new circle event
                # formed by a new bisector and the one on the right
                self.activate_circle_event(site_event,
                                           right_node.key.left_site,
                                           right_node.key.right_site,
                                           right_node)
                right_node = left_node
            else:
                # the above arc corresponds neither to the first,
                # nor to the last site in the beach line
                second_arc_site = right_node.key.left_site
                third_site = right_node.key.right_site
                # remove the candidate circle from the event queue
                self.deactivate_circle_event(right_node.value)
                left_node = self._beach_line.predecessor(left_node)
                first_arc_site = left_node.key.right_site
                first_site = left_node.key.left_site
                # insert new nodes into the beach line. Update the output
                new_node = self.insert_new_arc(first_arc_site, second_arc_site,
                                               site_event, output)
                # add candidate circles to the circle event queue;
                # there could be up to two circle events
                # formed by a new bisector and the one on the left or right
                self.activate_circle_event(first_site, first_arc_site,
                                           site_event, new_node)
                # if the site event is a segment, update its direction
                if site_event.is_segment:
                    site_event.inverse()
                self.activate_circle_event(site_event, second_arc_site,
                                           third_site, right_node)
                right_node = new_node
            self._site_event_index += 1
Exemplo n.º 3
0
class Operation:
    __slots__ = ('_left', '_right', '_type', '_events_queue', '_resultant',
                 '_already_run')

    def __init__(self, left: Polygon, right: Polygon,
                 type_: OperationType) -> None:
        self._left = left
        self._right = right
        self._type = type_
        self._events_queue = PriorityQueue(key=EventsQueueKey, reverse=True)
        self._resultant = Polygon([])
        self._already_run = False

    __repr__ = generate_repr(__init__, field_seeker=seekers.complex_)

    def __eq__(self, other: 'Operation') -> bool:
        return (self._left == other._left and self._right == other._right
                and self._type is other._type
                if isinstance(other, Operation) else NotImplemented)

    @property
    def left(self) -> Polygon:
        return self._left

    @property
    def right(self) -> Polygon:
        return self._right

    @property
    def events(self) -> List[SweepEvent]:
        events_queue = copy(self._events_queue)
        return [events_queue.pop() for _ in range(len(events_queue))]

    @property
    def resultant(self) -> Polygon:
        return self._resultant

    @property
    def type(self) -> OperationType:
        return self._type

    @property
    def is_trivial(self) -> bool:
        # test 1 for trivial result case
        if not (self._left.contours and self._right.contours):
            # at least one of the polygons is empty
            if self._type is OperationType.DIFFERENCE:
                self._resultant = self._left
            if (self._type is OperationType.UNION
                    or self._type is OperationType.XOR):
                self._resultant = (self._left
                                   if self._left.contours else self._right)
            self._already_run = True
            return True
        # test 2 for trivial result case
        left_bounding_box = self._left.bounding_box
        right_bounding_box = self._right.bounding_box
        if (left_bounding_box.x_min > right_bounding_box.x_max
                or right_bounding_box.x_min > left_bounding_box.x_max
                or left_bounding_box.y_min > right_bounding_box.y_max
                or right_bounding_box.y_min > left_bounding_box.y_max):
            # the bounding boxes do not overlap
            if self._type is OperationType.DIFFERENCE:
                self._resultant = self._left
            elif (self._type is OperationType.UNION
                  or self._type is OperationType.XOR):
                self._resultant = self._left
                self._resultant.join(self._right)
            self._already_run = True
            return True
        return False

    @staticmethod
    def collect_events(events: List[SweepEvent]) -> List[SweepEvent]:
        result = [
            event for event in events if event.is_left and event.in_result
            or not event.is_left and event.other_event.in_result
        ]
        is_sorted = False
        while not is_sorted:
            is_sorted = True
            for index in range(len(result) - 1):
                if (EventsQueueKey(result[index]) < EventsQueueKey(
                        result[index + 1])):
                    result[index], result[index + 1] = (result[index + 1],
                                                        result[index])
                    is_sorted = False
        for index, event in enumerate(result):
            event.position = index
            if not event.is_left:
                event.position, event.other_event.position = (
                    event.other_event.position, event.position)
        return result

    def connect_edges(self, events: List[SweepEvent]) -> None:
        self.process_events(self.collect_events(events))

    def divide_segment(self, event: SweepEvent, point: Point) -> None:
        # "left event" of the "right line segment"
        # resulting from dividing event.segment
        left_event = SweepEvent(True, point, event.other_event,
                                event.polygon_type, EdgeType.NORMAL)
        # "right event" of the "left line segment"
        # resulting from dividing event.segment
        right_event = SweepEvent(False, point, event, event.polygon_type,
                                 EdgeType.NORMAL)
        if EventsQueueKey(left_event) < EventsQueueKey(event.other_event):
            # avoid a rounding error,
            # the left event would be processed after the right event
            event.other_event.is_left = True
            left_event.is_left = False
        event.other_event.other_event = left_event
        event.other_event = right_event
        self._events_queue.push(left_event)
        self._events_queue.push(right_event)

    def in_result(self, event: SweepEvent) -> bool:
        operation_type = self._type
        edge_type = event.edge_type
        if edge_type is EdgeType.NORMAL:
            if operation_type is OperationType.INTERSECTION:
                return not event.other_in_out
            elif operation_type is OperationType.UNION:
                return event.other_in_out
            elif operation_type is OperationType.DIFFERENCE:
                return (event.polygon_type is PolygonType.SUBJECT
                        and event.other_in_out
                        or event.polygon_type is PolygonType.CLIPPING
                        and not event.other_in_out)
            else:
                return operation_type is OperationType.XOR
        elif edge_type is EdgeType.SAME_TRANSITION:
            return (operation_type is OperationType.INTERSECTION
                    or operation_type is OperationType.UNION)
        elif edge_type is EdgeType.DIFFERENT_TRANSITION:
            return operation_type is OperationType.DIFFERENCE
        else:
            return False

    def possible_intersection(self, first_event: SweepEvent,
                              second_event: SweepEvent) -> int:
        intersections_count, first_point, second_point = find_intersections(
            first_event.segment, second_event.segment)

        if not intersections_count:
            # no intersection
            return 0

        if ((intersections_count == 1)
                and (first_event.point == second_event.point or
                     (first_event.other_event.point
                      == second_event.other_event.point))):
            # the line segments intersect at an endpoint of both line segments
            return 0

        if (intersections_count == 2
                and first_event.polygon_type is second_event.polygon_type):
            raise ValueError("Edges of the same polygon should not overlap.")

        # The line segments associated to le1 and le2 intersect
        if intersections_count == 1:
            if (first_event.point != first_point
                    and first_event.other_event.point != first_point):
                # if the intersection point is not an endpoint of le1.segment
                self.divide_segment(first_event, first_point)
            if (second_event.point != first_point
                    and second_event.other_event.point != first_point):
                # if the intersection point is not an endpoint of le2.segment
                self.divide_segment(second_event, first_point)
            return 1

        # The line segments associated to le1 and le2 overlap
        sorted_events = []
        if first_event.point == second_event.point:
            sorted_events.append(None)
        elif EventsQueueKey(first_event) < EventsQueueKey(second_event):
            sorted_events.append(second_event)
            sorted_events.append(first_event)
        else:
            sorted_events.append(first_event)
            sorted_events.append(second_event)

        if first_event.other_event.point == second_event.other_event.point:
            sorted_events.append(None)
        elif (EventsQueueKey(first_event.other_event) < EventsQueueKey(
                second_event.other_event)):
            sorted_events.append(second_event.other_event)
            sorted_events.append(first_event.other_event)
        else:
            sorted_events.append(first_event.other_event)
            sorted_events.append(second_event.other_event)

        if (len(sorted_events) == 2
                or len(sorted_events) == 3 and sorted_events[2]):
            # both line segments are equal or share the left endpoint
            first_event.edge_type = EdgeType.NON_CONTRIBUTING
            second_event.edge_type = (EdgeType.SAME_TRANSITION if
                                      first_event.in_out is second_event.in_out
                                      else EdgeType.DIFFERENT_TRANSITION)
            if len(sorted_events) == 3:
                self.divide_segment(sorted_events[2].other_event,
                                    sorted_events[1].point)
            return 2
        if len(sorted_events) == 3:
            # the line segments share the right endpoint
            self.divide_segment(sorted_events[0], sorted_events[1].point)
            return 3

        if sorted_events[0] is not sorted_events[3].other_event:
            # no line segment includes totally the other one
            self.divide_segment(sorted_events[0], sorted_events[1].point)
            self.divide_segment(sorted_events[1], sorted_events[2].point)
            return 3

        # one line segment includes the other one
        self.divide_segment(sorted_events[0], sorted_events[1].point)
        self.divide_segment(sorted_events[3].other_event,
                            sorted_events[2].point)
        return 3

    def process_events(self, events: List[SweepEvent]) -> None:
        depth, hole_of = [], []
        processed = [False] * len(events)
        contours = self._resultant.contours
        for index, event in enumerate(events):
            if processed[index]:
                continue
            contour = Contour([], [], True)
            contour_id = len(contours)
            contours.append(contour)
            depth.append(0)
            hole_of.append(-1)
            if event.prev_in_result_event is not None:
                lower_contour_id = event.prev_in_result_event.contour_id
                if not event.prev_in_result_event.result_in_out:
                    contours[lower_contour_id].add_hole(contour_id)
                    hole_of[contour_id] = lower_contour_id
                    depth[contour_id] = depth[lower_contour_id] + 1
                    contour.is_external = False
                elif not contours[lower_contour_id].is_external:
                    contours[hole_of[lower_contour_id]].add_hole(contour_id)
                    hole_of[contour_id] = hole_of[lower_contour_id]
                    depth[contour_id] = depth[lower_contour_id]
                    contour.is_external = False
            position = index
            initial = event.point
            contour.add(initial)
            event = events[position]
            while event.other_event.point != initial:
                processed[position] = True
                if event.is_left:
                    event.result_in_out = False
                    event.contour_id = contour_id
                else:
                    event.other_event.result_in_out = True
                    event.other_event.contour_id = contour_id
                position = event.position
                processed[position] = True
                contour.add(events[position].point)
                position = self.to_next_position(position, events, processed)
                event = events[position]
            processed[position] = processed[event.position] = True
            event.other_event.result_in_out = True
            event.other_event.contour_id = contour_id
            if depth[contour_id] & 1:
                contour.reverse()

    def process_segments(self) -> None:
        for contour in self._left.contours:
            for segment in to_segments(contour.points):
                self._process_segment(segment, PolygonType.SUBJECT)
        for contour in self._right.contours:
            for segment in to_segments(contour.points):
                self._process_segment(segment, PolygonType.CLIPPING)

    def _process_segment(self, segment: Segment,
                         polygon_type: PolygonType) -> None:
        source_event = SweepEvent(True, segment.source, None, polygon_type,
                                  EdgeType.NORMAL)
        target_event = SweepEvent(True, segment.target, source_event,
                                  polygon_type, EdgeType.NORMAL)
        source_event.other_event = target_event
        if segment.min == segment.source:
            target_event.is_left = False
        else:
            source_event.is_left = False
        self._events_queue.push(source_event)
        self._events_queue.push(target_event)

    def sweep(self) -> List[SweepEvent]:
        min_max_x = min(self._left.bounding_box.x_max,
                        self._right.bounding_box.x_max)
        result = []
        events_queue = self._events_queue
        sweep_line = red_black.set_(key=SweepLineKey)
        while events_queue:
            event = events_queue.peek()
            if (self._type is OperationType.INTERSECTION
                    and event.point.x > min_max_x
                    or self._type is OperationType.DIFFERENCE
                    and event.point.x > self._left.bounding_box.x_max):
                break
            result.append(event)
            events_queue.pop()
            if event.is_left:
                sweep_line.add(event)
                try:
                    next_event = sweep_line.next(event)
                except ValueError:
                    next_event = None
                try:
                    previous_event = sweep_line.prev(event)
                except ValueError:
                    previous_event = None
                self.compute_fields(event, previous_event)
                if next_event is not None:
                    if self.possible_intersection(event, next_event) == 2:
                        self.compute_fields(event, previous_event)
                        self.compute_fields(next_event, event)
                if previous_event is not None:
                    if self.possible_intersection(previous_event, event) == 2:
                        try:
                            pre_previous_event = sweep_line.prev(
                                previous_event)
                        except ValueError:
                            pre_previous_event = None
                        self.compute_fields(previous_event, pre_previous_event)
                        self.compute_fields(event, previous_event)
            else:
                event = event.other_event
                if event not in sweep_line:
                    continue
                try:
                    next_event = sweep_line.next(event)
                except ValueError:
                    next_event = None
                try:
                    previous_event = sweep_line.prev(event)
                except ValueError:
                    previous_event = None
                sweep_line.remove(event)
                if next_event is not None and previous_event is not None:
                    self.possible_intersection(previous_event, next_event)
        return result

    def compute_fields(self, event: SweepEvent,
                       previous_event: Optional[SweepEvent]) -> None:
        if previous_event is None:
            event.in_out = False
            event.other_in_out = True
        else:
            if event.polygon_type is previous_event.polygon_type:
                event.in_out = not previous_event.in_out
                event.other_in_out = previous_event.other_in_out
            else:
                event.in_out = not previous_event.other_in_out
                event.other_in_out = (not previous_event.in_out
                                      if previous_event.is_vertical else
                                      previous_event.in_out)
            event.prev_in_result_event = (
                previous_event.prev_in_result_event if
                (not self.in_result(previous_event)
                 or previous_event.is_vertical) else previous_event)
        event.in_result = self.in_result(event)

    def run(self) -> None:
        if self._already_run:
            return
        if self.is_trivial:
            return
        self.process_segments()
        self.connect_edges(self.sweep())
        self._already_run = True

    @staticmethod
    def to_next_position(position: int, events: List[SweepEvent],
                         processed: List[bool]) -> int:
        result = position + 1
        while (result < len(events)
               and events[result].point == events[position].point):
            if not processed[result]:
                return result
            else:
                result += 1
        if not position:
            return 0
        result = position - 1
        while processed[result]:
            if not result:
                break
            result -= 1
        return result
Exemplo n.º 4
0
class ShapedEventsQueue(Generic[LeftShapedEvent]):
    __slots__ = 'context', 'event_cls', '_queue'

    def __init__(self,
                 event_cls: Type[LeftShapedEvent],
                 context: Context) -> None:
        self.event_cls, self.context = event_cls, context
        self._queue = PriorityQueue(key=partial(BinaryEventsQueueKey,
                                                context.angle_orientation))

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    @property
    def key(self) -> Callable[[ShapedEvent], BinaryEventsQueueKey]:
        return self._queue.key

    def detect_intersection(self,
                            below_event: LeftShapedEvent,
                            event: LeftShapedEvent) -> bool:
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.CROSS or relation is Relation.TOUCH:
            if (event.start != below_event.start
                    and event.end != below_event.end):
                # segments do not intersect_multipolygons at endpoints
                point = self.context.segments_intersection(below_event, event)
                if point != below_event.start and point != below_event.end:
                    self._divide_segment(below_event, point)
                if point != event.start and point != event.end:
                    self._divide_segment(event, point)
        elif relation is not Relation.DISJOINT:
            # segments overlap
            if below_event.from_first is event.from_first:
                raise ValueError('Edges of the same geometry '
                                 'should not overlap.')
            starts_equal = below_event.start == event.start
            if starts_equal:
                start_min = start_max = None
            elif self.key(event) < self.key(below_event):
                start_min, start_max = event, below_event
            else:
                start_min, start_max = below_event, event
            ends_equal = event.end == below_event.end
            if ends_equal:
                end_min = end_max = None
            elif self.key(event.opposite) < self.key(below_event.opposite):
                end_min, end_max = event.opposite, below_event.opposite
            else:
                end_min, end_max = below_event.opposite, event.opposite
            if starts_equal:
                # both line segments are equal or share the left endpoint
                below_event.overlap_kind = event.overlap_kind = (
                    OverlapKind.SAME_ORIENTATION
                    if event.interior_to_left is below_event.interior_to_left
                    else OverlapKind.DIFFERENT_ORIENTATION)
                if not ends_equal:
                    self._divide_segment(end_max.opposite, end_min.start)
                return True
            elif ends_equal:
                # the line segments share the right endpoint
                self._divide_segment(start_min, start_max.start)
            elif start_min is end_max.opposite:
                # one line segment includes the other one
                self._divide_segment(start_min, end_min.start)
                self._divide_segment(start_min, start_max.start)
            else:
                # no line segment includes the other one
                self._divide_segment(start_max, end_min.start)
                self._divide_segment(start_min, start_max.start)
        return False

    def pop(self) -> ShapedEvent:
        return self._queue.pop()

    def register(self,
                 segments_endpoints: Iterable[SegmentEndpoints],
                 from_first: bool) -> None:
        event_cls, push = self.event_cls, self._queue.push
        for segment_endpoints in segments_endpoints:
            event = event_cls.from_endpoints(segment_endpoints, from_first)
            push(event)
            push(event.opposite)

    def _divide_segment(self, event: LeftShapedEvent, point: Point) -> None:
        tail = event.divide(point)
        self._queue.push(tail)
        self._queue.push(event.opposite)
Exemplo n.º 5
0
class NaryEventsQueue:
    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=partial(NaryEventsQueueKey,
                                                context.angle_orientation))

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    @property
    def key(self) -> Callable[[NaryEvent], NaryEventsQueueKey]:
        return self._queue.key

    def detect_intersection(self,
                            below_event: LeftNaryEvent,
                            event: LeftNaryEvent) -> None:
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.CROSS or relation is Relation.TOUCH:
            if (event.start != below_event.start
                    and event.end != below_event.end):
                # segments do not intersect_multipolygons at endpoints
                point = self.context.segments_intersection(below_event, event)
                if point != below_event.start and point != below_event.end:
                    self._divide_segment(below_event, point)
                if point != event.start and point != event.end:
                    self._divide_segment(event, point)
        elif relation is not Relation.DISJOINT:
            # segments overlap
            starts_equal = below_event.start == event.start
            if starts_equal:
                start_min = start_max = None
            elif self.key(event) < self.key(below_event):
                start_min, start_max = event, below_event
            else:
                start_min, start_max = below_event, event
            ends_equal = event.end == below_event.end
            if ends_equal:
                end_min = end_max = None
            elif self.key(event.opposite) < self.key(below_event.opposite):
                end_min, end_max = event.opposite, below_event.opposite
            else:
                end_min, end_max = below_event.opposite, event.opposite
            if starts_equal:
                # both line segments are equal or share the left endpoint
                if not ends_equal:
                    self._divide_segment(end_max.opposite, end_min.start)
            elif ends_equal:
                # the line segments share the right endpoint
                self._divide_segment(start_min, start_max.start)
            elif start_min is end_max.opposite:
                # one line segment includes the other one
                self._divide_segment(start_min, end_min.start)
                self._divide_segment(start_min, start_max.start)
            else:
                # no line segment includes the other one
                self._divide_segment(start_max, end_min.start)
                self._divide_segment(start_min, start_max.start)

    def pop(self) -> NaryEvent:
        return self._queue.pop()

    def register(self, segments_endpoints: Iterable[SegmentEndpoints]) -> None:
        push = self._queue.push
        for segment_endpoints in segments_endpoints:
            event = LeftNaryEvent.from_segment_endpoints(segment_endpoints)
            push(event)
            push(event.opposite)

    def _divide_segment(self, event: LeftNaryEvent, point: Point) -> None:
        tail = LeftNaryEvent.divide(event, point)
        self._queue.push(tail)
        self._queue.push(event.opposite)
Exemplo n.º 6
0
class MixedEventsQueue:
    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=partial(BinaryEventsQueueKey,
                                                context.angle_orientation))

    @property
    def key(self) -> Callable[[MixedEvent], BinaryEventsQueueKey]:
        return self._queue.key

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def detect_intersection(self,
                            below_event: LeftMixedEvent,
                            event: LeftMixedEvent) -> bool:
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.CROSS or relation is Relation.TOUCH:
            if (event.start != below_event.start
                    and event.end != below_event.end):
                # segments do not intersect_multipolygons at endpoints
                point = self.context.segments_intersection(below_event, event)
                if point != below_event.start and point != below_event.end:
                    self._divide_segment(below_event, point)
                if point != event.start and point != event.end:
                    self._divide_segment(event, point)
        elif relation is not Relation.DISJOINT:
            # segments overlap
            if below_event.from_first is event.from_first:
                raise ValueError('Edges of the {geometry} '
                                 'should not overlap.'
                                 .format(geometry=('multisegment'
                                                   if event.from_first
                                                   else 'multipolygon')))
            event.is_overlap = below_event.is_overlap = True
            starts_equal = below_event.start == event.start
            if starts_equal:
                start_min = start_max = None
            elif self.key(event) < self.key(below_event):
                start_min, start_max = event, below_event
            else:
                start_min, start_max = below_event, event
            ends_equal = event.end == below_event.end
            if ends_equal:
                end_min = end_max = None
            elif self.key(event.opposite) < self.key(below_event.opposite):
                end_min, end_max = event.opposite, below_event.opposite
            else:
                end_min, end_max = below_event.opposite, event.opposite
            if starts_equal:
                # both line segments are equal or share the left endpoint
                if not ends_equal:
                    self._divide_segment(end_max.opposite, end_min.start)
                return True
            elif ends_equal:
                # the line segments share the right endpoint
                self._divide_segment(start_min, start_max.start)
            elif start_min is end_max.opposite:
                # one line segment includes the other one
                self._divide_segment(start_min, end_min.start)
                self._divide_segment(start_min, start_max.start)
            else:
                # no line segment includes the other one
                self._divide_segment(start_max, end_min.start)
                self._divide_segment(start_min, start_max.start)
        return False

    def pop(self) -> MixedEvent:
        return self._queue.pop()

    def register(self,
                 segments_endpoints: Iterable[SegmentEndpoints],
                 from_first: bool) -> None:
        push = self._queue.push
        for segment_endpoints in segments_endpoints:
            event = LeftMixedEvent.from_endpoints(segment_endpoints,
                                                  from_first)
            push(event)
            push(event.opposite)

    def _divide_segment(self, event: LeftMixedEvent, point: Point) -> None:
        tail = event.divide(point)
        self._queue.push(tail)
        self._queue.push(event.opposite)
Exemplo n.º 7
0
class EventsQueue:
    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=partial(EventsQueueKey,
                                                context.angle_orientation))

    @staticmethod
    def compute_position(below_event: Optional[LeftEvent],
                         event: LeftEvent) -> None:
        if below_event is not None:
            event.other_interior_to_left = (below_event.other_interior_to_left
                                            if (event.from_first
                                                is below_event.from_first)
                                            else below_event.interior_to_left)

    def detect_intersection(self,
                            below_event: LeftEvent,
                            event: LeftEvent) -> bool:
        """
        Populates events queue with intersection events.
        Checks if events' segments overlap and have the same start.
        """
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.CROSS or relation is Relation.TOUCH:
            point = self.context.segments_intersection(below_event, event)
            if point != below_event.start and point != below_event.end:
                self.divide_segment(below_event, point)
            if point != event.start and point != event.end:
                self.divide_segment(event, point)
        elif relation is not Relation.DISJOINT:
            # segments overlap
            if below_event.from_first is event.from_first:
                raise ValueError('Edges of the same polygon '
                                 'should not overlap.')
            starts_equal = below_event.start == event.start
            ends_equal = below_event.end == event.end
            start_min, start_max = (
                (event, below_event)
                if starts_equal or (self._queue.key(event)
                                    < self._queue.key(below_event))
                else (below_event, event))
            end_min, end_max = (
                (event.right, below_event.right)
                if ends_equal or (self._queue.key(event.right)
                                  < self._queue.key(below_event.right))
                else (below_event.right, event.right))
            if starts_equal:
                # both line segments are equal or share the left endpoint
                event.is_overlap = below_event.is_overlap = True
                if not ends_equal:
                    self.divide_segment(end_max.left, end_min.start)
                return True
            elif ends_equal:
                # the line segments share the right endpoint
                self.divide_segment(start_min, start_max.start)
            else:
                self.divide_segment(start_min
                                    # one line segment includes the other one
                                    if start_min is end_max.left
                                    # no line segment includes the other one
                                    else start_max,
                                    end_min.start)
                self.divide_segment(start_min, start_max.start)
        return False

    def divide_segment(self, event: LeftEvent, point: Point) -> None:
        self.push(event.divide(point))
        self.push(event.right)

    def push(self, event: Event) -> None:
        self._queue.push(event)

    def register_edge(self,
                      edge: QuadEdge,
                      *,
                      from_first: bool,
                      is_counterclockwise_contour: bool) -> None:
        start_event = LeftEvent.from_segment_endpoints(
                to_endpoints(edge), from_first, is_counterclockwise_contour)
        start_event.edge = edge
        self.push(start_event)
        self.push(start_event.right)

    def register_segment(self,
                         endpoints: SegmentEndpoints,
                         *,
                         from_first: bool,
                         is_counterclockwise_contour: bool) -> None:
        start_event = LeftEvent.from_segment_endpoints(
                endpoints, from_first, is_counterclockwise_contour)
        self.push(start_event)
        self.push(start_event.right)

    def sweep(self) -> Iterable[LeftEvent]:
        sweep_line = SweepLine(self.context)
        queue = self._queue
        while queue:
            event = queue.pop()
            if event.is_left:
                sweep_line.add(event)
                above_event, below_event = (sweep_line.above(event),
                                            sweep_line.below(event))
                self.compute_position(below_event, event)
                if (above_event is not None
                        and self.detect_intersection(event, above_event)):
                    self.compute_position(event, above_event)
                if (below_event is not None
                        and self.detect_intersection(below_event, event)):
                    self.compute_position(sweep_line.below(below_event),
                                          below_event)
            else:
                event = event.left
                if event in sweep_line:
                    above_event, below_event = (sweep_line.above(event),
                                                sweep_line.below(event))
                    sweep_line.remove(event)
                    if above_event is not None and below_event is not None:
                        self.detect_intersection(below_event, above_event)
                yield event
Exemplo n.º 8
0
class EventsQueue:
    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=EventsQueueKey)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def detect_intersection(self, below_event: Event, event: Event) -> None:
        relation = self.context.segments_relation(below_event.start,
                                                  below_event.end, event.start,
                                                  event.end)
        if relation is Relation.TOUCH or relation is Relation.CROSS:
            # segments touch or cross
            point = self.context.segments_intersection(below_event.start,
                                                       below_event.end,
                                                       event.start, event.end)
            if point != below_event.start and point != below_event.end:
                self._divide_segment(below_event, point)
            if point != event.start and point != event.end:
                self._divide_segment(event, point)
            event.set_both_relations(max(event.relation, relation))
            below_event.set_both_relations(max(below_event.relation, relation))
        elif relation is not Relation.DISJOINT:
            # segments overlap
            starts_equal = event.start == below_event.start
            start_min, start_max = ((None, None) if starts_equal else (
                (event, below_event)
                if EventsQueueKey(event) < EventsQueueKey(below_event) else
                (below_event, event)))
            ends_equal = event.end == below_event.end
            end_min, end_max = ((None, None) if ends_equal else
                                ((event.complement, below_event.complement) if
                                 (EventsQueueKey(event.complement) <
                                  EventsQueueKey(below_event.complement)) else
                                 (below_event.complement, event.complement)))
            if starts_equal:
                if ends_equal:
                    # segments are equal
                    event.set_both_relations(relation)
                    below_event.set_both_relations(relation)
                else:
                    # segments share the left endpoint
                    end_min.set_both_relations(relation)
                    end_max.complement.relation = relation
                    self._divide_segment(end_max.complement, end_min.start)
            elif ends_equal:
                # segments share the right endpoint
                start_max.set_both_relations(relation)
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)
            elif start_min is end_max.complement:
                # one line segment includes the other one
                start_max.set_both_relations(relation)
                start_min_original_relationship = start_min.relation
                start_min.relation = relation
                self._divide_segment(start_min, end_min.start)
                start_min.relation = start_min_original_relationship
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)
            else:
                # no line segment includes the other one
                start_max.relation = relation
                self._divide_segment(start_max, end_min.start)
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)

    def peek(self) -> Event:
        return self._queue.peek()

    def pop(self) -> Event:
        return self._queue.pop()

    def push(self, event: Event) -> None:
        if event.start == event.end:
            raise ValueError('Degenerate segment found '
                             'with both endpoints being: {}.'.format(
                                 event.start))
        self._queue.push(event)

    def _divide_segment(self, event: Event, break_point: Point) -> None:
        left_event = event.complement.complement = Event(
            start=break_point,
            complement=event.complement,
            is_left_endpoint=True,
            relation=event.complement.relation,
            segments_ids=event.segments_ids)
        right_event = event.complement = Event(
            start=break_point,
            complement=event,
            is_left_endpoint=False,
            relation=event.relation,
            segments_ids=event.complement.segments_ids)
        self.push(left_event)
        self.push(right_event)
Exemplo n.º 9
0
class EventsQueue:
    @classmethod
    def from_segments(cls, segments: Sequence[Segment], *,
                      context: Context) -> 'EventsQueue':
        result = cls(context)
        for index, segment in enumerate(segments):
            event = LeftEvent.from_segment(segment, index)
            result.push(event)
            result.push(event.right)
        return result

    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=EventsQueueKey)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def detect_intersection(self, below_event: LeftEvent, event: LeftEvent,
                            sweep_line: SweepLine) -> None:
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.DISJOINT:
            return
        elif relation is Relation.TOUCH or relation is Relation.CROSS:
            # segments touch or cross
            point = self.context.segments_intersection(below_event, event)
            assert event.segments_ids.isdisjoint(below_event.segments_ids)
            if point != below_event.start and point != below_event.end:
                below_below = sweep_line.below(below_event)
                assert not (below_below is not None and below_below.start
                            == below_event.start and below_below.end == point)
                self.push(below_event.divide(point))
                self.push(below_event.right)
            if point != event.start and point != event.end:
                above_event = sweep_line.above(event)
                if (above_event is not None
                        and above_event.start == event.start
                        and above_event.end == point):
                    sweep_line.remove(above_event)
                    self.push(event.divide(point))
                    self.push(event.right)
                    event.merge_with(above_event)
                else:
                    self.push(event.divide(point))
                    self.push(event.right)
        else:
            # segments overlap
            starts_equal = event.start == below_event.start
            start_min, start_max = ((event, below_event) if (
                starts_equal
                or EventsQueueKey(event) < EventsQueueKey(below_event)) else
                                    (below_event, event))
            ends_equal = event.end == below_event.end
            end_min, end_max = ((event.right,
                                 below_event.right) if ends_equal or
                                (EventsQueueKey(event.right) < EventsQueueKey(
                                    below_event.right)) else
                                (below_event.right, event.right))
            if starts_equal:
                assert not ends_equal
                # segments share the left endpoint
                sweep_line.remove(end_max.left)
                self.push(end_max.left.divide(end_min.start))
                event.merge_with(below_event)
            elif ends_equal:
                # segments share the right endpoint
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_min.right)
            elif start_min is end_max.left:
                # one line segment includes the other one
                self.push(start_min.divide(end_min.start))
                self.push(start_min.right)
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_min.right)
            else:
                # no line segment includes the other one
                self.push(start_max.divide(end_min.start))
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_max.right)
                self.push(start_min.right)

    def peek(self) -> Event:
        return self._queue.peek()

    def pop(self) -> Event:
        return self._queue.pop()

    def push(self, event: Event) -> None:
        if event.start == event.end:
            raise ValueError('Degenerate segment found '
                             'with both endpoints being: {}.'.format(
                                 event.start))
        self._queue.push(event)
Exemplo n.º 10
0
class EventsQueue:
    __slots__ = '_queue',

    def __init__(self) -> None:
        self._queue = PriorityQueue(key=EventsQueueKey)

    @staticmethod
    def compute_position(below_event: Optional[Event], event: Event) -> None:
        if below_event is not None:
            event.other_interior_to_left = (
                below_event.other_interior_to_left if
                (event.from_left is below_event.from_left) else
                below_event.interior_to_left)

    def detect_intersection(self, below_event: Event, event: Event) -> bool:
        """
        Populates events queue with intersection events.
        Checks if events' segments overlap and have the same start.
        """
        below_segment, segment = below_event.segment, event.segment
        relationship = segments_relationship(below_segment, segment)
        if relationship is SegmentsRelationship.OVERLAP:
            # segments overlap
            if below_event.from_left is event.from_left:
                raise ValueError('Edges of the same polygon '
                                 'should not overlap.')
            starts_equal = below_event.start == event.start
            ends_equal = below_event.end == event.end
            start_min, start_max = ((None, None) if starts_equal else (
                (event, below_event) if
                (EventsQueueKey(event) < EventsQueueKey(below_event)) else
                (below_event, event)))
            end_min, end_max = ((None, None) if ends_equal else
                                ((event.complement, below_event.complement) if
                                 (EventsQueueKey(event.complement) <
                                  EventsQueueKey(below_event.complement)) else
                                 (below_event.complement, event.complement)))
            if starts_equal:
                # both line segments are equal or share the left endpoint
                event.is_overlap = below_event.is_overlap = True
                if not ends_equal:
                    self.divide_segment(end_max.complement, end_min.start)
                return True
            elif ends_equal:
                # the line segments share the right endpoint
                self.divide_segment(start_min, start_max.start)
            else:
                self.divide_segment(
                    start_min
                    # one line segment includes the other one
                    if start_min is end_max.complement
                    # no line segment includes the other one
                    else start_max,
                    end_min.start)
                self.divide_segment(start_min, start_max.start)
        elif (relationship is not SegmentsRelationship.NONE
              and below_event.start != event.start
              and below_event.end != event.end):
            # segments do not intersect at endpoints
            point = segments_intersection(below_segment, segment)
            if point != below_event.start and point != below_event.end:
                self.divide_segment(below_event, point)
            if point != event.start and point != event.end:
                self.divide_segment(event, point)
        return False

    def divide_segment(self, event: Event, point: Point) -> None:
        left_event = Event(True, point, event.complement, event.from_left,
                           event.interior_to_left, event.edge)
        right_event = Event(False, point, event, event.from_left,
                            event.interior_to_left, event.edge)
        event.complement.complement, event.complement = left_event, right_event
        self._queue.push(left_event)
        self._queue.push(right_event)

    def register_edge(self, edge: QuadEdge, *, from_left: bool,
                      is_counterclockwise_contour: bool) -> None:
        start, end = edge.start, edge.end
        interior_to_left = is_counterclockwise_contour
        if start > end:
            start, end = end, start
            interior_to_left = not interior_to_left
        start_event = Event(True, start, None, from_left, interior_to_left,
                            edge)
        end_event = Event(False, end, start_event, from_left, interior_to_left,
                          edge)
        start_event.complement = end_event
        self._queue.push(start_event)
        self._queue.push(end_event)

    def register_segment(self, segment: Segment, *, from_left: bool,
                         is_counterclockwise_contour: bool) -> None:
        start, end = segment
        interior_to_left = is_counterclockwise_contour
        if start > end:
            start, end = end, start
            interior_to_left = not interior_to_left
        start_event = Event(True, start, None, from_left, interior_to_left)
        end_event = Event(False, end, start_event, from_left, interior_to_left)
        start_event.complement = end_event
        self._queue.push(start_event)
        self._queue.push(end_event)

    def sweep(self) -> Iterable[Event]:
        sweep_line = SweepLine()
        while self._queue:
            event = self._queue.pop()
            if event.is_left_endpoint:
                sweep_line.add(event)
                above_event, below_event = (sweep_line.above(event),
                                            sweep_line.below(event))
                self.compute_position(below_event, event)
                if (above_event is not None
                        and self.detect_intersection(event, above_event)):
                    self.compute_position(event, above_event)
                if (below_event is not None
                        and self.detect_intersection(below_event, event)):
                    self.compute_position(sweep_line.below(below_event),
                                          below_event)
            else:
                event = event.complement
                if event in sweep_line:
                    above_event, below_event = (sweep_line.above(event),
                                                sweep_line.below(event))
                    sweep_line.remove(event)
                    if above_event is not None and below_event is not None:
                        self.detect_intersection(below_event, above_event)
                yield event