Ejemplo n.º 1
0
    def __init__(self,
                 segments: _Sequence[_Segment],
                 *,
                 max_children: int = 16,
                 context: _Optional[_Context] = None) -> None:
        """
        Initializes tree from segments.

        Time complexity:
            ``O(size * log size)``
        Memory complexity:
            ``O(size)``

        where ``size = len(segments)``.
        """
        if context is None:
            context = _get_context()
        box_cls = context.box_cls
        self._context, self._max_children, self._root, self._segments = (
            context, max_children,
            _create_root(segments,
                         [box_cls(*((segment.start.x, segment.end.x)
                                    if segment.start.x < segment.end.x
                                    else (segment.end.x, segment.start.x)),
                                  *((segment.start.y, segment.end.y)
                                    if segment.start.y < segment.end.y
                                    else (segment.end.y, segment.start.y)))
                          for segment in segments], max_children,
                         context.merged_box,
                         context.box_point_squared_distance,
                         context.box_segment_squared_distance,
                         context.segment_point_squared_distance,
                         context.segments_squared_distance),
            segments)
Ejemplo n.º 2
0
    def __init__(self,
                 boxes: Sequence[Box],
                 *,
                 max_children: int = 16,
                 node_cls: Type[Node] = Node) -> None:
        """
        Initializes tree from boxes.

        Time complexity:
            ``O(size * log size)``
        Memory complexity:
            ``O(size)``

        where ``size = len(boxes)``.

        >>> from ground.base import get_context
        >>> context = get_context()
        >>> Box = context.box_cls
        >>> boxes = [Box(-index, index, 0, index) for index in range(1, 11)]
        >>> tree = Tree(boxes)
        """
        self._boxes = boxes
        self._max_children = max_children
        self._root = _create_root(boxes, max_children,
                                  _get_context().box_cls, node_cls)
Ejemplo n.º 3
0
    def __init__(self,
                 segments: Sequence[Segment],
                 *,
                 max_children: int = 16,
                 node_cls: Optional[Type[Node]] = None) -> None:
        """
        Initializes tree from segments.


        Time complexity:
            ``O(size * log size)``
        Memory complexity:
            ``O(size)``

        where ``size = len(segments)``.

        >>> from ground.base import get_context
        >>> context = get_context()
        >>> Point, Segment = context.point_cls, context.segment_cls
        >>> segments = [Segment(Point(0, index), Point(index, index))
        ...             for index in range(1, 11)]
        >>> tree = Tree(segments)
        """
        self._segments = segments
        self._max_children = max_children
        context = _get_context()
        self._root = _create_root(segments, max_children,
                                  _to_default_node_cls(context)
                                  if node_cls is None
                                  else node_cls,
                                  context)
Ejemplo n.º 4
0
def segments_intersect(segments: Sequence[Segment]) -> bool:
    """
    Checks if segments have at least one intersection.

    Based on Shamos-Hoey algorithm.

    Time complexity:
        ``O(len(segments) * log len(segments))``
    Memory complexity:
        ``O(len(segments))``
    Reference:
        https://en.wikipedia.org/wiki/Sweep_line_algorithm

    :param segments: sequence of segments.
    :returns: true if segments intersection found, false otherwise.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_intersect([])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2))])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 0)),
    ...                     Segment(Point(0, 2), Point(2, 2))])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2)),
    ...                     Segment(Point(0, 0), Point(2, 2))])
    True
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2)),
    ...                     Segment(Point(2, 0), Point(0, 2))])
    True
    """
    return any(_sweep(segments, context=_get_context()))
Ejemplo n.º 5
0
    def __init__(self,
                 points: _Sequence[_Point],
                 *,
                 context: _Optional[_Context] = None) -> None:
        """
        Initializes tree from points.

        Time complexity:
            ``O(dimension * size * log size)``
        Memory complexity:
            ``O(dimension * size)``

        where ``dimension = len(points[0])``, ``size = len(points)``.
        """
        if context is None:
            context = _get_context()
        self._context, self._points, self._root = (
            context, points, _create_node(range(len(points)), points, False,
                                          context.points_squared_distance))
Ejemplo n.º 6
0
def segments_cross_or_overlap(segments: _Sequence[_Segment],
                              *,
                              context: _Optional[_Context] = None) -> bool:
    """
    Checks if at least one pair of segments crosses or overlaps.

    Based on Bentley-Ottmann algorithm.

    Time complexity:
        ``O(len(segments) * log len(segments))``
    Memory complexity:
        ``O(len(segments))``
    Reference:
        https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm

    :param segments: sequence of segments.
    :param context: geometrical context.
    :returns: true if segments overlap or cross found, false otherwise.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_cross_or_overlap([])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2))])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 0)),
    ...                            Segment(Point(0, 2), Point(2, 2))])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2)),
    ...                            Segment(Point(0, 0), Point(2, 2))])
    True
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2)),
    ...                            Segment(Point(0, 2), Point(2, 0))])
    True
    """
    return not all(
        event.has_only_relations(_Relation.DISJOINT, _Relation.TOUCH)
        for event in _sweep(
            segments, context=(
                _get_context() if context is None else context)))
Ejemplo n.º 7
0
    def __init__(self,
                 boxes: _Sequence[_Box],
                 *,
                 max_children: int = 16,
                 context: _Optional[_Context] = None) -> None:
        """
        Initializes tree from boxes.

        Time complexity:
            ``O(size * log size)``
        Memory complexity:
            ``O(size)``

        where ``size = len(boxes)``.
        """
        if context is None:
            context = _get_context()
        self._boxes, self._context, self._max_children, self._root = (
            boxes, context, max_children,
            _create_root(boxes, max_children, context.merged_box,
                         context.box_point_squared_distance))
Ejemplo n.º 8
0
def segments_intersect(segments: _Sequence[_Segment],
                       *,
                       context: _Optional[_Context] = None) -> bool:
    """
    Checks if segments have at least one intersection.

    Based on Shamos-Hoey algorithm.

    Time complexity:
        ``O(len(segments) * log len(segments))``
    Memory complexity:
        ``O(len(segments))``
    Reference:
        https://en.wikipedia.org/wiki/Sweep_line_algorithm

    :param segments: sequence of segments.
    :param context: geometrical context.
    :returns: true if segments intersection found, false otherwise.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_intersect([])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2))])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 0)),
    ...                     Segment(Point(0, 2), Point(2, 2))])
    False
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2)),
    ...                     Segment(Point(0, 0), Point(2, 2))])
    True
    >>> segments_intersect([Segment(Point(0, 0), Point(2, 2)),
    ...                     Segment(Point(2, 0), Point(0, 2))])
    True
    """
    return not all(
        event.has_only_relations(_Relation.DISJOINT) for event in _sweep(
            segments, context=(
                _get_context() if context is None else context)))
Ejemplo n.º 9
0
def segments_cross_or_overlap(segments: Sequence[Segment]) -> bool:
    """
    Checks if at least one pair of segments crosses or overlaps.

    Based on Shamos-Hoey algorithm.

    Time complexity:
        ``O((len(segments) + len(intersections)) * log len(segments))``
    Memory complexity:
        ``O(len(segments))``
    Reference:
        https://en.wikipedia.org/wiki/Sweep_line_algorithm

    :param segments: sequence of segments.
    :returns: true if segments overlap or cross found, false otherwise.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_cross_or_overlap([])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2))])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 0)),
    ...                            Segment(Point(0, 2), Point(2, 2))])
    False
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2)),
    ...                            Segment(Point(0, 0), Point(2, 2))])
    True
    >>> segments_cross_or_overlap([Segment(Point(0, 0), Point(2, 2)),
    ...                            Segment(Point(2, 0), Point(0, 2))])
    True
    """
    rest_relations = _Relation.DISJOINT, _Relation.TOUCH
    return any(first_event.relation not in rest_relations
               or second_event.relation not in rest_relations
               for first_event, second_event in _sweep(segments,
                                                       context=_get_context()))
Ejemplo n.º 10
0
class Segment(_ContextMixin, _Segment[_Coordinate]):
    __slots__ = ()


class Multisegment(_ContextMixin, _Multisegment[_Coordinate]):
    __slots__ = ()


class Polygon(_ContextMixin, _Polygon[_Coordinate]):
    __slots__ = ()


class Multipolygon(_ContextMixin, _Multipolygon[_Coordinate]):
    __slots__ = ()


_initial_context = _get_context()
_context = _Context(box_cls=_initial_context.box_cls,
                    contour_cls=Contour,
                    empty_cls=Empty,
                    mix_cls=Mix,
                    multipoint_cls=Multipoint,
                    multipolygon_cls=Multipolygon,
                    multisegment_cls=Multisegment,
                    point_cls=Point,
                    polygon_cls=Polygon,
                    segment_cls=Segment,
                    mode=_initial_context.mode,
                    sqrt=_initial_context.sqrt)
_set_context(_context)
Ejemplo n.º 11
0
def segments_intersections(segments: _Sequence[_Segment],
                           *,
                           context: _Optional[_Context] = None
                           ) -> _Dict[_Tuple[int, int], _Intersection]:
    """
    Returns mapping between intersection points
    and corresponding segments indices.

    Based on Bentley-Ottmann algorithm.

    Time complexity:
        ``O(len(segments) * log len(segments) + len(intersections))``
    Memory complexity:
        ``O(len(segments) + len(intersections))``
    Reference:
        https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm

    :param segments: sequence of segments.
    :param context: geometrical context.
    :returns:
        mapping between intersection points and corresponding segments indices.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_intersections([]) == {}
    True
    >>> segments_intersections([Segment(Point(0, 0), Point(2, 2))]) == {}
    True
    >>> segments_intersections([Segment(Point(0, 0), Point(2, 0)),
    ...                         Segment(Point(0, 2), Point(2, 2))]) == {}
    True
    >>> (segments_intersections([Segment(Point(0, 0), Point(2, 2)),
    ...                          Segment(Point(0, 0), Point(2, 2))])
    ...  == {(0, 1): (Point(0, 0), Point(2, 2))})
    True
    >>> (segments_intersections([Segment(Point(0, 0), Point(2, 2)),
    ...                          Segment(Point(2, 0), Point(0, 2))])
    ...  == {(0, 1): (Point(1, 1),)})
    True
    """
    left_parts_ids, right_parts_ids = {}, {}
    left_tangents, right_tangents = {}, {}
    for event in _sweep(
            segments, context=_get_context() if context is None else context):
        if event.tangents:
            (left_tangents.setdefault(event.start, {}).setdefault(
                event.end,
                set()).update(tangent.end for tangent in event.tangents))
        if event.right.tangents:
            (right_tangents.setdefault(event.end, {}).setdefault(
                event.start,
                set()).update(tangent.end for tangent in event.right.tangents))
        for start, ends_ids in event.parts_ids.items():
            for end, ids in ends_ids.items():
                (left_parts_ids.setdefault(start,
                                           {}).setdefault(end,
                                                          set()).update(ids))
                (right_parts_ids.setdefault(end,
                                            {}).setdefault(start,
                                                           set()).update(ids))
    discrete = {}  # type: _Dict[_Tuple[int, int], _Tuple[_Point]]
    for intersection_point, ends_tangents_ends in left_tangents.items():
        left_intersection_point_ids, right_intersection_point_ids = (
            left_parts_ids.get(intersection_point),
            right_parts_ids.get(intersection_point))
        for end, tangents_ends in ends_tangents_ends.items():
            ids = left_intersection_point_ids[end]
            for tangent_end in tangents_ends:
                tangent_ids = (left_intersection_point_ids[tangent_end]
                               if intersection_point < tangent_end else
                               right_intersection_point_ids[tangent_end])
                ids_pairs = [
                    _to_sorted_pair(id_, tangent_id)
                    for id_, tangent_id in _product(ids -
                                                    tangent_ids, tangent_ids -
                                                    ids)
                ]
                discrete.update(zip(ids_pairs, _repeat(
                    (intersection_point, ))))
    for intersection_point, starts_tangents_ends in right_tangents.items():
        left_intersection_point_ids, right_intersection_point_ids = (
            left_parts_ids.get(intersection_point),
            right_parts_ids.get(intersection_point))
        for start, tangents_ends in starts_tangents_ends.items():
            ids = right_intersection_point_ids[start]
            for tangent_end in tangents_ends:
                tangent_ids = (left_intersection_point_ids[tangent_end]
                               if intersection_point < tangent_end else
                               right_intersection_point_ids[tangent_end])
                ids_pairs = [
                    _to_sorted_pair(id_, tangent_id)
                    for id_, tangent_id in _product(ids -
                                                    tangent_ids, tangent_ids -
                                                    ids)
                ]
                discrete.update(zip(ids_pairs, _repeat(
                    (intersection_point, ))))
    continuous = {}  # type: _Dict[_Tuple[int, int], _Tuple[_Point, _Point]]
    for start, ends_ids in left_parts_ids.items():
        for end, ids in ends_ids.items():
            for ids_pair in _to_pairs_combinations(sorted(ids)):
                if ids_pair in continuous:
                    prev_start, prev_end = continuous[ids_pair]
                    endpoints = min(prev_start, start), max(prev_end, end)
                else:
                    endpoints = (start, end)
                continuous[ids_pair] = endpoints
    return {**discrete, **continuous}
Ejemplo n.º 12
0
def contour_self_intersects(contour: _Contour,
                            *,
                            context: _Optional[_Context] = None) -> bool:
    """
    Checks if contour has self-intersection.

    Based on Bentley-Ottmann algorithm.

    Time complexity:
        ``O(len(contour.vertices) * log len(contour.vertices))``
    Memory complexity:
        ``O(len(contour.vertices))``
    Reference:
        https://en.wikipedia.org/wiki/Sweep_line_algorithm

    :param contour: contour to check.
    :param context: geometrical context.
    :returns: true if contour is self-intersecting, false otherwise.

    .. note::
        Consecutive equal vertices like ``Point(2, 0)`` in

        .. code-block:: python

            Contour([Point(0, 0), Point(2, 0), Point(2, 0), Point(2, 2)])

        will be considered as self-intersection,
        if you don't want them to be treated as such
        -- filter out before passing as argument.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Contour, Point = context.contour_cls, context.point_cls
    >>> contour_self_intersects(Contour([Point(0, 0), Point(2, 0),
    ...                                  Point(2, 2)]))
    False
    >>> contour_self_intersects(Contour([Point(0, 0), Point(2, 0),
    ...                                  Point(1, 0)]))
    True
    """
    vertices = contour.vertices
    if len(vertices) < 3:
        raise ValueError(
            'Contour {contour} is degenerate.'.format(contour=contour))
    if not _all_unique(vertices):
        return True
    if context is None:
        context = _get_context()
    segments = context.contour_segments(contour)

    def non_neighbours_disjoint(
            segment_id: int,
            other_segment_id: int,
            last_segment_id: int = len(segments) - 1) -> bool:
        min_edge_id, max_edge_id = _to_sorted_pair(segment_id,
                                                   other_segment_id)
        return (max_edge_id - min_edge_id == 1
                or (min_edge_id == 0 and max_edge_id == last_segment_id))

    return not all(
        event.has_only_relations(_Relation.DISJOINT, _Relation.TOUCH) and all(
            non_neighbours_disjoint(id_, other_id)
            for tangent in [*event.tangents, *event.right.tangents] for id_,
            other_id in _product(event.segments_ids, tangent.segments_ids))
        for event in _sweep(segments, context=context))
Ejemplo n.º 13
0
def segments_intersections(
        segments: Sequence[Segment]) -> Dict[Point, Set[Tuple[int, int]]]:
    """
    Returns mapping between intersection points
    and corresponding segments indices.

    Based on Bentley-Ottmann algorithm.

    Time complexity:
        ``O((len(segments) + len(intersections)) * log len(segments))``
    Memory complexity:
        ``O(len(segments) + len(intersections))``
    Reference:
        https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Point, Segment = context.point_cls, context.segment_cls
    >>> segments_intersections([]) == {}
    True
    >>> segments_intersections([Segment(Point(0, 0), Point(2, 2))]) == {}
    True
    >>> segments_intersections([Segment(Point(0, 0), Point(2, 0)),
    ...                         Segment(Point(0, 2), Point(2, 2))]) == {}
    True
    >>> (segments_intersections([Segment(Point(0, 0), Point(2, 2)),
    ...                          Segment(Point(0, 0), Point(2, 2))])
    ...  == {Point(0, 0): {(0, 1)}, Point(2, 2): {(0, 1)}})
    True
    >>> (segments_intersections([Segment(Point(0, 0), Point(2, 2)),
    ...                          Segment(Point(2, 0), Point(0, 2))])
    ...  == {Point(1, 1): {(0, 1)}})
    True

    :param segments: sequence of segments.
    :returns:
        mapping between intersection points and corresponding segments indices.
    """
    result = {}
    context = _get_context()

    def segments_intersector(
            first_start: Point,
            first_end: Point,
            second_start: Point,
            second_end: Point,
            relater=context.segments_relation,
            intersector=context.segments_intersection) -> Tuple[Point, ...]:
        relation = relater(first_start, first_end, second_start, second_end)
        if relation is _Relation.DISJOINT:
            return ()
        if relation is _Relation.TOUCH or relation is _Relation.CROSS:
            return intersector(first_start, first_end, second_start,
                               second_end),
        else:
            _, first_point, second_point, _ = sorted(
                [first_start, first_end, second_start, second_end])
            return first_point, second_point

    for first_event, second_event in _sweep(segments, context=context):
        for segment_id, next_segment_id in _to_pairs_combinations(
                _merge_ids(first_event.segments_ids,
                           second_event.segments_ids)):
            segment, next_segment = (segments[segment_id],
                                     segments[next_segment_id])
            for point in segments_intersector(segment.start, segment.end,
                                              next_segment.start,
                                              next_segment.end):
                result.setdefault(point, set()).add(
                    (segment_id, next_segment_id))
    return result
Ejemplo n.º 14
0
def edges_intersect(contour: Contour) -> bool:
    """
    Checks if polygonal contour has self-intersection.

    Based on Shamos-Hoey algorithm.

    Time complexity:
        ``O(len(contour) * log len(contour))``
    Memory complexity:
        ``O(len(contour))``
    Reference:
        https://en.wikipedia.org/wiki/Sweep_line_algorithm

    :param contour: contour to check.
    :returns: true if contour is self-intersecting, false otherwise.

    .. note::
        Consecutive equal vertices like ``Point(2, 0)`` in

        .. code-block:: python

            Contour([Point(0, 0), Point(2, 0), Point(2, 0), Point(2, 2)])

        will be considered as self-intersection,
        if you don't want them to be treated as such
        -- filter out before passing as argument.

    >>> from ground.base import get_context
    >>> context = get_context()
    >>> Contour, Point = context.contour_cls, context.point_cls
    >>> edges_intersect(Contour([Point(0, 0), Point(2, 0), Point(2, 2)]))
    False
    >>> edges_intersect(Contour([Point(0, 0), Point(2, 0), Point(1, 0)]))
    True
    """
    vertices = contour.vertices
    if len(vertices) < 3:
        raise ValueError(
            'Contour {contour} is degenerate.'.format(contour=contour))
    if not _all_unique(vertices):
        return True
    context = _get_context()
    segment_cls = context.segment_cls
    edges = [
        segment_cls(vertices[index - 1], vertices[index])
        for index in range(len(vertices))
    ]

    def non_neighbours_intersect(
            edges_ids: Iterable[Tuple[int, int]],
            last_edge_index: int = len(edges) - 1) -> bool:
        return any(next_segment_id - segment_id > 1 and (
            segment_id != 0 or next_segment_id != last_edge_index)
                   for segment_id, next_segment_id in edges_ids)

    non_overlap_relations = (_Relation.CROSS, _Relation.DISJOINT,
                             _Relation.TOUCH)
    return any(
        (first_event.relation not in non_overlap_relations or second_event.
         relation not in non_overlap_relations or non_neighbours_intersect(
             _to_pairs_combinations(
                 _merge_ids(first_event.segments_ids,
                            second_event.segments_ids))))
        for first_event, second_event in _sweep(edges, context=context))