def unpack_segments(segments: Sequence[Segment], context: Context) -> Union[Empty, Multisegment, Segment]: return ((context.multisegment_cls(segments) if len(segments) > 1 else segments[0]) if segments else context.empty)
def unpack_polygons(polygons: Sequence[Polygon], context: Context) -> Union[Empty, Multipolygon, Polygon]: return ((context.multipolygon_cls(polygons) if len(polygons) > 1 else polygons[0]) if polygons else context.empty)
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 constrict_convex_hull_size( points: Sequence[Point[Scalar]], *, context: Context, max_size: Optional[int]) -> Sequence[Point[Scalar]]: if max_size is None: return points convex_hull = context.points_convex_hull(points) if len(convex_hull) <= max_size: return points sorted_convex_hull = sorted(convex_hull, key=partial(context.points_squared_distance, convex_hull[0])) new_border_points = [] for index in range(max_size): quotient, remainder = divmod(index, 2) new_border_points.append(sorted_convex_hull[-quotient - 1] if remainder else sorted_convex_hull[quotient]) orienteer = context.angle_orientation new_border = list(to_max_convex_hull(new_border_points, orienteer)) new_border_extra_endpoints_pairs = tuple( {(new_border[index - 1], new_border[index]) for index in range(len(new_border))} - {(convex_hull[index], convex_hull[index - 1]) for index in range(len(convex_hull))}) return (new_border + [ point for point in set(points) - set(convex_hull) if all( orienteer(start, end, point) is Orientation.COUNTERCLOCKWISE for start, end in new_border_extra_endpoints_pairs) ])
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 to_convex_vertices_sequence(points: Sequence[Point[Scalar]], random: Random, context: Context) -> Sequence[Point[Scalar]]: """ Based on Valtr algorithm by Sander Verdonschot. Time complexity: ``O(len(points) * log len(points))`` Memory complexity: ``O(len(points))`` Reference: http://cglab.ca/~sander/misc/ConvexGeneration/convex.html """ xs, ys = [point.x for point in points], [point.y for point in points] xs, ys = sorted(xs), sorted(ys) min_x, *xs, max_x = xs min_y, *ys, max_y = ys def to_vectors_coordinates(coordinates: List[Scalar], min_coordinate: Scalar, max_coordinate: Scalar) -> List[Scalar]: last_min = last_max = min_coordinate result = [] for coordinate in coordinates: if random.getrandbits(1): result.append(coordinate - last_min) last_min = coordinate else: result.append(last_max - coordinate) last_max = coordinate result.extend((max_coordinate - last_min, last_max - max_coordinate)) return result vectors_xs = to_vectors_coordinates(xs, min_x, max_x) vectors_ys = to_vectors_coordinates(ys, min_y, max_y) random.shuffle(vectors_ys) def to_vector_angle(vector: Tuple[Scalar, Scalar]) -> Key: x, y = vector return atan2(y, x) vectors = sorted(zip(vectors_xs, vectors_ys), key=to_vector_angle) point_x = point_y = 0 min_polygon_x = min_polygon_y = 0 coordinates_pairs = [] for vector_x, vector_y in vectors: coordinates_pairs.append((point_x, point_y)) point_x += vector_x point_y += vector_y min_polygon_x, min_polygon_y = (min(min_polygon_x, point_x), min(min_polygon_y, point_y)) shift_x, shift_y = min_x - min_polygon_x, min_y - min_polygon_y point_cls = context.point_cls return context.points_convex_hull([ point_cls(min(max(x + shift_x, min_x), max_x), min(max(y + shift_y, min_y), max_y)) for x, y in coordinates_pairs ])
def unpack_regions(regions: Sequence[Contour], context: Context) -> Union[Empty, Polygon, Multipolygon]: polygon_cls = context.polygon_cls return ((context.multipolygon_cls([polygon_cls(region, []) for region in regions]) if len(regions) > 1 else polygon_cls(regions[0], [])) if regions else context.empty)
def unpack_linear_mix(discrete: Maybe[Multipoint], linear: Maybe[Linear], context: Context ) -> Union[Empty, Mix, Multipoint, Linear]: return (linear if discrete is context.empty else (discrete if linear is context.empty else context.mix_cls(discrete, linear, context.empty)))
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_multipolygon(goal: Multipolygon, test: Multipolygon, context: Context) -> Relation: goal_bounding_box = context.polygons_box(goal.polygons) test_bounding_box = context.polygons_box(test.polygons) events_queue = CompoundEventsQueue(context) events_queue.register(to_oriented_segments(goal, context), from_test=False) events_queue.register(to_oriented_segments(test, context), from_test=True) return process_compound_queue( events_queue, min(goal_bounding_box.max_x, test_bounding_box.max_x))
def test_step(context: Context, segments: List[Segment]) -> None: first_segment, *rest_segments = segments result = segments_cross_or_overlap(rest_segments) next_result = segments_cross_or_overlap(segments) assert (next_result is (result or any( context.segments_relation(first_segment, segment) in (Relation.COMPONENT, Relation.COMPOSITE, Relation.CROSS, Relation.EQUAL, Relation.OVERLAP) for segment in rest_segments)))
def _locate_point_in_indexed_polygons(tree: r.Tree, polygons: Sequence[Polygon], point: Point, context: Context) -> Location: candidates_indices = tree.find_supersets_indices( context.box_cls(point.x, point.x, point.y, point.y)) for candidate_index in candidates_indices: location = polygons[candidate_index].locate(point) if location is not Location.EXTERIOR: return location return Location.EXTERIOR
def relate_multisegment(goal: Multisegment, test: Multisegment, context: Context) -> Relation: goal_bounding_box = context.segments_box(goal.segments) test_bounding_box = context.segments_box(test.segments) if box.disjoint_with(goal_bounding_box, test_bounding_box): return Relation.DISJOINT events_queue = LinearEventsQueue(context) events_queue.register(to_segments_endpoints(goal), from_test=False) events_queue.register(to_segments_endpoints(test), from_test=True) return process_open_linear_queue( events_queue, min(goal_bounding_box.max_x, test_bounding_box.max_x))
def relate_contour(multipolygon: Multipolygon, contour: Contour, context: Context) -> Relation: contour_bounding_box = context.contour_box(contour) disjoint, multipolygon_max_x, events_queue = True, None, None for polygon in multipolygon.polygons: polygon_bounding_box = context.polygon_box(polygon) if not box.disjoint_with(polygon_bounding_box, contour_bounding_box): if disjoint: disjoint = False multipolygon_max_x = polygon_bounding_box.max_x events_queue = CompoundEventsQueue(context) events_queue.register(contour_to_edges_endpoints(contour), 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) return (Relation.DISJOINT if disjoint else process_linear_compound_queue( events_queue, min(contour_bounding_box.max_x, multipolygon_max_x)))
def symmetric_subtract_segments(first: Segment, second: Segment, context: Context ) -> Union_[Empty, Multisegment, Segment]: relation = context.segments_relation(first, second) if relation is Relation.DISJOINT: return context.multisegment_cls([first, second]) elif relation is Relation.EQUAL: return context.empty else: return (_unite_segments_touch(first, second, context) if relation is Relation.TOUCH else (_unite_segments_cross(first, second, context) if relation is Relation.CROSS else (_symmetric_subtract_segments_overlap(first, second, context) if relation is Relation.OVERLAP else (_subtract_segments_composite(first, second, context) if relation is Relation.COMPOSITE else _subtract_segments_composite(second, first, context)))))
def relate_multiregion(goal: Multiregion, test: Multiregion, context: Context) -> Relation: goal_bounding_box = context.contours_box(goal) test_bounding_box = context.contours_box(test) if box.disjoint_with(goal_bounding_box, test_bounding_box): return Relation.DISJOINT 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_compound_queue( events_queue, min(goal_bounding_box.max_x, test_bounding_box.max_x))
def _subtract_segment_from_multisegment(multisegment: Multisegment, segment: Segment, context: Context) -> List[Segment]: result = [] segment_cls = context.segment_cls for index, sub_segment in enumerate(multisegment.segments): relation = context.segments_relation(segment, sub_segment) if relation is Relation.EQUAL: result.extend(multisegment.segments[index + 1:]) break elif relation is Relation.COMPONENT: left_start, left_end, right_start, right_end = sorted([ sub_segment.start, sub_segment.end, segment.start, segment.end]) result.extend( [segment_cls(right_start, right_end)] if left_start == segment.start or left_start == segment.end else ((([segment_cls(left_start, left_end)] if right_start == right_end else [segment_cls(left_start, left_end), segment_cls(right_start, right_end)]) if (right_start == segment.start or right_start == segment.end) else [segment_cls(left_start, left_end)]) if left_end == segment.start or left_end == segment.end else [segment_cls(left_start, right_start)])) result.extend(multisegment.segments[index + 1:]) break elif relation is Relation.CROSS: cross_point = context.segments_intersection(sub_segment, segment) result.append(segment_cls(sub_segment.start, cross_point)) result.append(segment_cls(cross_point, sub_segment.end)) elif relation is Relation.OVERLAP: result.append(_subtract_segments_overlap(sub_segment, segment, context)) elif relation is not Relation.COMPOSITE: result.append(sub_segment) return result
def test_step(context: Context, contour: Contour) -> None: first_vertex, *rest_vertices = contour.vertices rest_contour = Contour(rest_vertices) result = edges_intersect(rest_contour) next_result = edges_intersect(contour) first_edge, last_edge = (Segment(first_vertex, rest_vertices[0]), Segment(rest_vertices[-1], first_vertex)) rest_edges = contour_to_edges(rest_contour) overlap_relations = (Relation.COMPONENT, Relation.COMPOSITE, Relation.EQUAL, Relation.OVERLAP) assert (next_result is (result and len(rest_vertices) > 2 and (any( segments_pair_intersections( rest_edges[index].start, rest_edges[index].end, rest_edges[other_index].start, rest_edges[other_index].end) for index in range(len(rest_edges) - 1) for other_index in chain(range(index - 1), range(index + 2, len(rest_edges) - 1)) ) or any( segments_pair_intersections(edge.start, edge.end, other_edge.start, other_edge.end) in overlap_relations for edge, other_edge in combinations(rest_edges[:-1], 2))) or any( segments_pair_intersections(first_edge.start, first_edge.end, edge.start, edge.end) for edge in rest_edges[1:-1]) or any( segments_pair_intersections(last_edge.start, last_edge.end, edge.start, edge.end) for edge in rest_edges[:-2]) or len(rest_vertices) > 1 and ( context.segments_relation( first_edge.start, first_edge.end, rest_edges[0].start, rest_edges[0].end) in overlap_relations or context.segments_relation(first_edge.start, first_edge.end, last_edge.start, last_edge.end) in overlap_relations or context.segments_relation( last_edge.start, last_edge.end, rest_edges[0].start, rest_edges[0].end) in overlap_relations)))
def test_step(context: Context, segments: List[Segment]) -> None: *rest_segments, last_segment = segments result = segments_intersections(rest_segments) next_result = segments_intersections(segments) assert (next_result.keys() == ( result.keys() | {(index, len(segments) - 1) for index, segment in enumerate(rest_segments) if context. segments_relation(segment, last_segment) is not Relation.DISJOINT})) assert result.items() <= next_result.items() assert all(segment_id < next_segment_id == len(segments) - 1 for segment_id, next_segment_id in (next_result.keys() - result.keys())) assert all( context.segments_intersection( segments[segment_id], segments[next_segment_id]) == next_result[( segment_id, next_segment_id)][0] if len(next_result[( segment_id, next_segment_id)]) == 1 else context.segments_intersection( segments[segment_id], segments[next_segment_id]) not in (Relation.DISJOINT, Relation.TOUCH, Relation.CROSS) and ( to_sorted_pair(*next_result[(segment_id, next_segment_id)]) == next_result[(segment_id, next_segment_id)]) and all( context.segment_contains_point(segments[segment_id], point) for point in next_result[(segment_id, next_segment_id)]) and all( context.segment_contains_point(segments[next_segment_id], point) for point in next_result[(segment_id, next_segment_id)]) for segment_id, next_segment_id in (next_result.keys() - result.keys())) assert all( context.segments_relation(segments[segment_id], segments[next_segment_id]) is not Relation.DISJOINT for segment_id, next_segment_id in (next_result.keys() - result.keys()))
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 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 subtract_segments(minuend: Segment, subtrahend: Segment, context: Context ) -> Union_[Empty, Multisegment, Segment]: relation = context.segments_relation(minuend, subtrahend) if relation is Relation.COMPONENT or relation is Relation.EQUAL: return context.empty elif (relation is Relation.DISJOINT or relation is Relation.TOUCH or relation is Relation.CROSS): return minuend else: return (_subtract_segments_overlap(minuend, subtrahend, context) if relation is Relation.OVERLAP else _subtract_segments_composite(minuend, subtrahend, context))
def test_sizes( context: Context, polygon_with_extra_points: Tuple[Polygon, Sequence[Point]]) -> None: polygon, extra_points = polygon_with_extra_points result = Triangulation.constrained_delaunay(polygon, extra_points=extra_points, context=context) triangles = result.triangles() assert 0 < len(triangles) <= (2 * (len( to_distinct( chain(polygon.border.vertices, * [hole.vertices for hole in polygon.holes], extra_points))) - 1) - len( context.points_convex_hull(polygon.border.vertices))) assert all(is_contour_triangular(triangle) for triangle in triangles)
def _intersect_segment_with_multisegment(segment: Segment, multisegment: Multisegment, context: Context) -> List[Segment]: result = [] for sub_segment in multisegment.segments: relation = context.segments_relation(segment, sub_segment) if (relation is not Relation.DISJOINT and relation is not Relation.TOUCH and relation is not Relation.CROSS): result.append(segment if (relation is Relation.EQUAL or relation is Relation.COMPONENT) else (sub_segment if relation is Relation.COMPOSITE else _intersect_segments_overlap(segment, sub_segment, context))) return result
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 from_points(points: Iterable[Point], *, context: Context) -> Box: """ Builds box from points. """ points = iter(points) point = next(points) min_x, min_y = max_x, max_y = point.x, point.y for point in points: x, y = point.x, point.y if x < min_x: min_x = x elif x > max_x: max_x = x if y < min_y: min_y = y elif y > max_y: max_y = y return context.box_cls(min_x, max_x, min_y, max_y)
def _relate_multisegment(multiregion: Multiregion, multisegment: Multisegment, multisegment_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, multisegment_bounding_box): if disjoint: disjoint = False multiregion_max_x = region_bounding_box.max_x events_queue = CompoundEventsQueue(context) events_queue.register(to_segments_endpoints(multisegment), 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(multisegment_bounding_box.max_x, multiregion_max_x)))
def _subtract_segments_composite(minuend: Segment, subtrahend: Segment, context: Context ) -> Union_[Multisegment, Segment]: left_start, left_end, right_start, right_end = sorted([ minuend.start, minuend.end, subtrahend.start, subtrahend.end]) return (context.segment_cls(right_start, right_end) if left_start == subtrahend.start or left_start == subtrahend.end else (((context.segment_cls(left_start, left_end) if right_start == right_end else context.multisegment_cls([context.segment_cls(left_start, left_end), context.segment_cls(right_start, right_end)])) if (right_start == subtrahend.start or right_start == subtrahend.end) else context.segment_cls(left_start, left_end)) if left_end == subtrahend.start or left_end == subtrahend.end else context.segment_cls(left_start, right_start)))
def _unite_segments_touch(first: Segment, second: Segment, context: Context) -> Union_[Multisegment, Segment]: return (context.multisegment_cls([first, second]) if ((first.start != second.start or (context.angle_orientation(first.start, first.end, second.end) is not Orientation.COLLINEAR)) and (first.start != second.end or (context.angle_orientation(first.start, first.end, second.start) is not Orientation.COLLINEAR)) and (first.end != second.start or (context.angle_orientation(first.end, first.start, second.end) is not Orientation.COLLINEAR)) and (first.end != second.end or (context.angle_orientation(first.end, first.start, second.start) is not Orientation.COLLINEAR))) else context.segment_cls(min(first.start, first.end, second.start, second.end), max(first.start, first.end, second.start, second.end)))
def relate_segment(goal: Segment, test: Segment, context: Context) -> Relation: return context.segments_relation(test, goal)