Пример #1
0
def test_step(priority_queue: PriorityQueue) -> None:
    original = deepcopy(priority_queue)

    result = priority_queue.pop()

    assert all(not item < priority_queue._item_factory(result)
               for item in priority_queue._items)
    assert len(priority_queue) == len(original) - 1
Пример #2
0
def test_base_case(priority_queue: PriorityQueue) -> None:
    with pytest.raises(IndexError):
        priority_queue.pop()
Пример #3
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)
Пример #4
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
Пример #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)
Пример #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)
Пример #7
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)
Пример #8
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)
Пример #9
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