def intersects_with_polygon(box: Box, polygon: Polygon, context: Context) -> bool: """ Checks if the box intersects the polygon. """ border = polygon.border polygon_box = context.contour_box(border) if not intersects_with(polygon_box, box): return False elif (is_subset_of(polygon_box, box) or any(contains_point(box, vertex) for vertex in border.vertices)): return True locations = [ point_in_region(vertex, border) for vertex in to_vertices(box, context) ] if (within_of(box, polygon_box) and all(location is Location.INTERIOR for location in locations) and all( context.segments_relation(edge, border_edge) is Relation.DISJOINT for edge in to_edges(box, context) for border_edge in context.contour_segments(border))): return not any( within_of(box, context.contour_box(hole)) and within_of_region(box, hole, context) for hole in polygon.holes) else: return (any(location is not Location.EXTERIOR for location in locations) or any( intersects_with_segment(box, edge, context) for edge in context.contour_segments(border)))
def relate_region(polygon: Polygon, region: Region, context: Context) -> Relation: region_bounding_box = context.contour_box(region) border, holes = polygon.border, polygon.holes relation_with_border = relate_regions(border, region, context.contour_box(border), region_bounding_box, context) if relation_with_border in (Relation.DISJOINT, Relation.TOUCH, Relation.OVERLAP, Relation.COVER, Relation.ENCLOSES): return relation_with_border elif (relation_with_border is Relation.COMPOSITE or relation_with_border is Relation.EQUAL): return (Relation.ENCLOSES if holes else relation_with_border) else: relation_with_holes = relate_region_to_regions(holes, region, region_bounding_box, context) if relation_with_holes is Relation.DISJOINT: return relation_with_border elif relation_with_holes is Relation.TOUCH: return Relation.ENCLOSED elif relation_with_holes in (Relation.EQUAL, Relation.COMPONENT, Relation.ENCLOSED): return Relation.TOUCH elif relation_with_holes is Relation.WITHIN: return Relation.DISJOINT else: return Relation.OVERLAP
def relate_multiregion(polygon: Polygon, multiregion: Multiregion, context: Context) -> Relation: border, holes = polygon.border, polygon.holes border_bounding_box = context.contour_box(border) if not holes: return relate_region_to_regions(multiregion, border, border_bounding_box, context).complement none_touch = True subsets_regions_indices = [] for region_index, region in enumerate(multiregion): region_relation = relate_regions(border, region, border_bounding_box, context.contour_box(region), context) if region_relation is Relation.TOUCH: if none_touch: none_touch = False elif region_relation in (Relation.OVERLAP, Relation.COVER, Relation.ENCLOSES): return region_relation elif (region_relation is Relation.COMPOSITE or region_relation is Relation.EQUAL): return Relation.ENCLOSES elif region_relation is not Relation.DISJOINT: if none_touch and (region_relation is Relation.ENCLOSED or region_relation is Relation.COMPONENT): none_touch = False subsets_regions_indices.append(region_index) if subsets_regions_indices: is_subset_of_border = len(subsets_regions_indices) == len(multiregion) relation_with_holes = relate_multiregions( holes, multiregion if is_subset_of_border else [multiregion[index] for index in subsets_regions_indices], context) if relation_with_holes is Relation.DISJOINT: return ((Relation.WITHIN if none_touch else Relation.ENCLOSED) if is_subset_of_border else Relation.OVERLAP) elif relation_with_holes is Relation.TOUCH: return (Relation.ENCLOSED if is_subset_of_border else Relation.OVERLAP) elif relation_with_holes is Relation.OVERLAP: return relation_with_holes elif relation_with_holes in (Relation.COVER, Relation.ENCLOSES, Relation.COMPOSITE): return Relation.OVERLAP elif relation_with_holes is Relation.WITHIN: return (Relation.DISJOINT if none_touch else Relation.TOUCH) else: return Relation.TOUCH else: return (Relation.DISJOINT if none_touch else Relation.TOUCH)
def relate_region(multipolygon: Multipolygon, region: Region, context: Context) -> Relation: region_bounding_box = context.contour_box(region) all_disjoint, none_disjoint, multipolygon_max_x, events_queue = (True, True, None, None) for polygon in multipolygon.polygons: polygon_bounding_box = context.polygon_box(polygon) if box.disjoint_with(region_bounding_box, polygon_bounding_box): if none_disjoint: none_disjoint = False else: if all_disjoint: all_disjoint = False multipolygon_max_x = polygon_bounding_box.max_x events_queue = CompoundEventsQueue(context) events_queue.register(region_to_oriented_segments( region, context), from_test=True) else: multipolygon_max_x = max(multipolygon_max_x, polygon_bounding_box.max_x) events_queue.register(polygon_to_oriented_segments( polygon, context), from_test=False) if all_disjoint: return Relation.DISJOINT relation = process_compound_queue( events_queue, min(multipolygon_max_x, region_bounding_box.max_x)) return (relation if none_disjoint else ( Relation.COMPONENT if relation is Relation.EQUAL else (Relation.OVERLAP if relation in (Relation.COVER, Relation.ENCLOSES, Relation.COMPOSITE) else relation)))
def _relate_region(goal_regions: Iterable[Region], region: Region, region_bounding_box: Box, context: Context) -> Relation: all_disjoint, none_disjoint, goal_regions_max_x, events_queue = (True, True, None, None) for goal_region in goal_regions: goal_region_bounding_box = context.contour_box(goal_region) if box.disjoint_with(region_bounding_box, goal_region_bounding_box): if none_disjoint: none_disjoint = False else: if all_disjoint: all_disjoint = False goal_regions_max_x = goal_region_bounding_box.max_x events_queue = CompoundEventsQueue(context) events_queue.register(region_to_oriented_segments( region, context), from_test=True) else: goal_regions_max_x = max(goal_regions_max_x, goal_region_bounding_box.max_x) events_queue.register(region_to_oriented_segments( goal_region, context), from_test=False) if all_disjoint: return Relation.DISJOINT relation = process_compound_queue( events_queue, min(goal_regions_max_x, region_bounding_box.max_x)) return (relation if none_disjoint else ( Relation.COMPONENT if relation is Relation.EQUAL else (Relation.OVERLAP if relation in (Relation.COVER, Relation.ENCLOSES, Relation.COMPOSITE) else relation)))
def relate_contour(polygon: Polygon, contour: Contour, context: Context) -> Relation: contour_bounding_box = context.contour_box(contour) relation_without_holes = relate_contour_to_region(polygon.border, contour, contour_bounding_box, context) holes = polygon.holes if holes and (relation_without_holes is Relation.ENCLOSED or relation_without_holes is Relation.WITHIN): relation_with_holes = relate_contour_to_multiregion( holes, contour, contour_bounding_box, context) if relation_with_holes is Relation.DISJOINT: return relation_without_holes elif relation_with_holes is Relation.TOUCH: return Relation.ENCLOSED elif (relation_with_holes is Relation.CROSS or relation_with_holes is Relation.COMPONENT): return relation_with_holes elif relation_with_holes is Relation.ENCLOSED: return Relation.TOUCH else: # contour is within holes return Relation.DISJOINT else: return relation_without_holes
def coupled_with_polygon(box: Box, polygon: Polygon, context: Context) -> bool: """ Checks if the box intersects the polygon in continuous points set. """ border = polygon.border polygon_box = context.contour_box(border) if not coupled_with(polygon_box, box): return False elif (is_subset_of(polygon_box, box) or any(covers_point(box, vertex) for vertex in border.vertices)): return True locations = [ point_in_region(vertex, border) for vertex in to_vertices(box, context) ] if any(location is Location.INTERIOR for location in locations): return (not all(location is Location.INTERIOR for location in locations) or not is_subset_of_multiregion(box, polygon.holes, context)) else: return (not is_subset_of_multiregion(box, polygon.holes, context) if (is_subset_of(box, polygon_box) and is_subset_of_region(box, border, context)) else any( segment_in_contour(segment, border) is Relation.OVERLAP or segment_in_region(segment, border) in ( Relation.CROSS, Relation.COMPONENT, Relation.ENCLOSED) for segment in to_edges(box, context)))
def is_subset_of_multiregion(box: Box, multiregion: Multiregion, context: Context) -> bool: """ Checks if the box is the subset of the multiregion. """ return any( is_subset_of(box, context.contour_box(region)) and is_subset_of_region(box, region, context) for region in multiregion)
def relate_multisegment(contour: Contour, multisegment: Multisegment, context: Context) -> Relation: contour_bounding_box = context.contour_box(contour) multisegment_bounding_box = context.segments_box(multisegment.segments) if box.disjoint_with(contour_bounding_box, multisegment_bounding_box): return Relation.DISJOINT events_queue = LinearEventsQueue(context) events_queue.register(to_edges_endpoints(contour), from_test=False) events_queue.register(to_segments_endpoints(multisegment), from_test=True) return process_open_linear_queue( events_queue, min(contour_bounding_box.max_x, multisegment_bounding_box.max_x))
def relate_multisegment(region: Region, multisegment: Multisegment, context: Context) -> Relation: multisegment_bounding_box = context.segments_box(multisegment.segments) region_bounding_box = context.contour_box(region) if box.disjoint_with(multisegment_bounding_box, region_bounding_box): return Relation.DISJOINT events_queue = CompoundEventsQueue(context) events_queue.register(to_oriented_segments(region, context), from_test=False) events_queue.register(to_segments_endpoints(multisegment), from_test=True) return process_linear_compound_queue( events_queue, min(multisegment_bounding_box.max_x, region_bounding_box.max_x))
def intersects_with_region(box: Box, region: Region, context: Context) -> bool: """ Checks if the box intersects the region. """ region_box = context.contour_box(region) return (intersects_with(region_box, box) and (is_subset_of(region_box, box) or any( contains_point(box, vertex) for vertex in region.vertices) or any( point_in_region(vertex, region) is not Location.EXTERIOR for vertex in to_vertices(box, context)) or any( intersects_with_segment(box, edge, context) for edge in context.contour_segments(region))))
def relate_contour(goal: Contour, test: Contour, context: Context) -> Relation: goal_bounding_box, test_bounding_box = (context.contour_box(goal), context.contour_box(test)) if box.disjoint_with(goal_bounding_box, test_bounding_box): return Relation.DISJOINT if equal(goal, test, context): return Relation.EQUAL events_queue = CompoundEventsQueue(context) events_queue.register(to_oriented_edges_endpoints(goal, context), from_test=False) events_queue.register(to_oriented_edges_endpoints(test, context), from_test=True) return process_closed_linear_queue( events_queue, min(goal_bounding_box.max_x, test_bounding_box.max_x))
def _relate_contour(region: Region, contour: Contour, contour_bounding_box: Box, context: Context) -> Relation: region_bounding_box = context.contour_box(region) if box.disjoint_with(contour_bounding_box, region_bounding_box): return Relation.DISJOINT if equal(region, contour, context): return Relation.COMPONENT events_queue = CompoundEventsQueue(context) events_queue.register(to_oriented_segments(region, context), from_test=False) events_queue.register(contour_to_edges_endpoints(contour), from_test=True) return process_linear_compound_queue( events_queue, min(contour_bounding_box.max_x, region_bounding_box.max_x))
def coupled_with_region(box: Box, region: Region, context: Context) -> bool: """ Checks if the box intersects the region in continuous points set. """ region_box = context.contour_box(region) if not coupled_with(region_box, box): return False elif (is_subset_of(region_box, box) or any(covers_point(box, vertex) for vertex in region.vertices)): return True return (any( point_in_region(vertex, region) is Location.INTERIOR for vertex in to_vertices(box, context)) or is_subset_of(box, region_box) and is_subset_of_region(box, region, context) or any( segment_in_contour(segment, region) is Relation.OVERLAP or segment_in_region(segment, region) in (Relation.CROSS, Relation.COMPONENT, Relation.ENCLOSED) for segment in to_edges(box, context)))
def _relate_contour(multiregion: Multiregion, contour: Contour, contour_bounding_box: Box, context: Context) -> Relation: disjoint, multiregion_max_x, events_queue = True, None, None for region in multiregion: region_bounding_box = context.contour_box(region) if not box.disjoint_with(region_bounding_box, contour_bounding_box): if disjoint: disjoint = False multiregion_max_x = region_bounding_box.max_x events_queue = CompoundEventsQueue(context) events_queue.register(contour_to_edges_endpoints(contour), from_test=True) else: multiregion_max_x = max(multiregion_max_x, region_bounding_box.max_x) events_queue.register(region_to_oriented_segments(region, context), from_test=False) return (Relation.DISJOINT if disjoint else process_linear_compound_queue( events_queue, min(contour_bounding_box.max_x, multiregion_max_x)))
def relate_polygon(goal: Polygon, test: Polygon, context: Context) -> Relation: goal_bounding_box, test_bounding_box = (context.polygon_box(goal), context.polygon_box(test)) goal_border, goal_holes = goal.border, goal.holes test_border, test_holes = test.border, test.holes borders_relation = relate_regions(goal_border, test_border, goal_bounding_box, test_bounding_box, context) if borders_relation in (Relation.DISJOINT, Relation.TOUCH, Relation.OVERLAP): return borders_relation elif borders_relation is Relation.EQUAL: if goal_holes and test_holes: holes_relation = relate_multiregions(test_holes, goal_holes, context) if holes_relation in (Relation.DISJOINT, Relation.TOUCH, Relation.OVERLAP): return Relation.OVERLAP elif holes_relation in (Relation.COVER, Relation.ENCLOSES, Relation.COMPOSITE): return Relation.ENCLOSES elif holes_relation is Relation.EQUAL: return borders_relation else: return Relation.ENCLOSED else: return (Relation.ENCLOSES if goal_holes else (Relation.ENCLOSED if test_holes else Relation.EQUAL)) elif borders_relation in (Relation.WITHIN, Relation.ENCLOSED, Relation.COMPONENT): if goal_holes: none_touch = True subsets_holes_indices = [] for hole_index, hole in enumerate(goal_holes): hole_relation = relate_regions(test_border, hole, test_bounding_box, context.contour_box(hole), context) if hole_relation is Relation.TOUCH: if none_touch: none_touch = False elif hole_relation is Relation.OVERLAP: return hole_relation elif hole_relation is Relation.COVER: return Relation.DISJOINT elif hole_relation in (Relation.ENCLOSES, Relation.COMPOSITE, Relation.EQUAL): return Relation.TOUCH elif hole_relation is not Relation.DISJOINT: subsets_holes_indices.append(hole_index) if subsets_holes_indices: holes_relation = (relate_multiregions( test_holes, goal_holes if len(subsets_holes_indices) == len(goal_holes) else [goal_holes[index] for index in subsets_holes_indices], context) if test_holes else Relation.DISJOINT) if holes_relation is Relation.EQUAL: return (Relation.ENCLOSED if borders_relation is Relation.WITHIN else borders_relation) elif (holes_relation is Relation.COMPONENT or holes_relation is Relation.ENCLOSED): return Relation.ENCLOSED elif holes_relation is Relation.WITHIN: return borders_relation else: return Relation.OVERLAP else: return (borders_relation if none_touch else Relation.ENCLOSED) else: return (Relation.ENCLOSED if test_holes and borders_relation is Relation.COMPONENT else borders_relation) elif test_holes: none_touch = True subsets_holes_indices = [] for hole_index, hole in enumerate(test_holes): hole_relation = relate_regions(goal_border, hole, goal_bounding_box, context.contour_box(hole), context) if hole_relation is Relation.TOUCH: if none_touch: none_touch = False elif hole_relation is Relation.OVERLAP: return hole_relation elif hole_relation is Relation.COVER: return Relation.DISJOINT elif hole_relation in (Relation.ENCLOSES, Relation.COMPOSITE, Relation.EQUAL): return Relation.TOUCH elif hole_relation is not Relation.DISJOINT: subsets_holes_indices.append(hole_index) if subsets_holes_indices: holes_relation = (relate_multiregions( goal_holes, test_holes if len(subsets_holes_indices) == len(test_holes) else [test_holes[index] for index in subsets_holes_indices], context) if goal_holes else Relation.DISJOINT) if holes_relation is Relation.EQUAL: return (Relation.ENCLOSES if borders_relation is Relation.COVER else borders_relation) elif (holes_relation is Relation.COMPONENT or holes_relation is Relation.ENCLOSED): return Relation.ENCLOSES elif holes_relation is Relation.WITHIN: return borders_relation else: return Relation.OVERLAP else: return (borders_relation if none_touch else Relation.ENCLOSES) else: return (Relation.ENCLOSES if goal_holes and borders_relation is Relation.COMPOSITE else borders_relation)
def relate_region(goal: Region, test: Region, context: Context) -> Relation: return _relate_region(goal, test, context.contour_box(goal), context.contour_box(test), context)
def relate_region(multiregion: Multiregion, region: Region, context: Context) -> Relation: return _relate_region(multiregion, region, context.contour_box(region), context)
def relate_contour(multiregion: Multiregion, contour: Contour, context: Context) -> Relation: return _relate_contour(multiregion, contour, context.contour_box(contour), context)
def from_polygon(cls, polygon: Polygon, *, shuffler: Shuffler = random.shuffle, context: Context) -> 'Graph': """ Constructs trapezoidal decomposition graph of given polygon. Based on incremental randomized algorithm by R. Seidel. Time complexity: ``O(vertices_count * log vertices_count)`` expected, ``O(vertices_count ** 2)`` worst Memory complexity: ``O(vertices_count)`` where .. code-block:: python vertices_count = (len(polygon.border.vertices) + sum(len(hole.vertices) for hole in polygon.holes) + len(extra_points) + len(extra_constraints)) Reference: https://doi.org/10.1016%2F0925-7721%2891%2990012-4 https://www.cs.princeton.edu/courses/archive/fall05/cos528/handouts/A%20Simple%20and%20fast.pdf :param polygon: target polygon. :param shuffler: function which mutates sequence by shuffling its elements, required for randomization. :param context: geometric context. :returns: trapezoidal decomposition graph of the border and holes. >>> from ground.base import get_context >>> context = get_context() >>> Contour, Point, Polygon = (context.contour_cls, context.point_cls, ... context.polygon_cls) >>> graph = Graph.from_polygon( ... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6), ... Point(0, 6)]), ... [Contour([Point(2, 2), Point(2, 4), Point(4, 4), ... Point(4, 2)])]), ... context=context) >>> Point(1, 1) in graph True >>> Point(2, 2) in graph True >>> Point(3, 3) in graph False >>> graph.locate(Point(1, 1)) is Location.INTERIOR True >>> graph.locate(Point(2, 2)) is Location.BOUNDARY True >>> graph.locate(Point(3, 3)) is Location.EXTERIOR True """ border = polygon.border orienteer = context.angle_orientation is_border_positively_oriented = (to_contour_orientation( border, orienteer) is Orientation.COUNTERCLOCKWISE) edges = [ Edge.from_endpoints(start, end, is_border_positively_oriented, context) if start < end else Edge.from_endpoints( end, start, not is_border_positively_oriented, context) for start, end in contour_to_edges_endpoints(border) ] for hole in polygon.holes: is_hole_negatively_oriented = (to_contour_orientation( hole, orienteer) is Orientation.CLOCKWISE) edges.extend( Edge.from_endpoints( start, end, is_hole_negatively_oriented, context ) if start < end else Edge.from_endpoints( end, start, not is_hole_negatively_oriented, context) for start, end in contour_to_edges_endpoints(hole)) shuffler(edges) result = cls(box_to_node(context.contour_box(border), context)) for edge in edges: add_edge(result, edge) return result