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)
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)
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)
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()))
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))
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)))
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))
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)))
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()))
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)
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}
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))
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
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))