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 within_of_region(box: Box, border: Contour, context: Context) -> bool: """ Checks if the box is contained in an interior of the region. """ return (all( point_in_region(vertex, border) is Location.INTERIOR for vertex in to_vertices(box, context)) 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)))
def _relate_segment_to_contour(contour: Contour, segment: Segment, context: Context) -> Relation: # similar to segment-in-contour check # but cross has higher priority over overlap # because cross with contour will be considered as cross with region # whereas overlap with contour can't be an overlap with region # and should be classified by further analysis has_no_touch = has_no_overlap = True last_touched_edge_index = last_touched_edge_start = None start, end = segment.start, segment.end for index, edge in enumerate(context.contour_segments(contour)): edge_start, edge_end = edge_endpoints = edge.start, edge.end relation_with_edge = relate_segments(edge, segment, context) if (relation_with_edge is Relation.COMPONENT or relation_with_edge is Relation.EQUAL): return Relation.COMPONENT elif (relation_with_edge is Relation.OVERLAP or relation_with_edge is Relation.COMPOSITE): if has_no_overlap: has_no_overlap = False elif relation_with_edge is Relation.TOUCH: if has_no_touch: has_no_touch = False elif (index - last_touched_edge_index == 1 and start not in edge_endpoints and end not in edge_endpoints and (context.angle_orientation( start, end, edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, last_touched_edge_start, edge_start, edge_end, context)): return Relation.CROSS last_touched_edge_index = index last_touched_edge_start = edge_start elif relation_with_edge is Relation.CROSS: return Relation.CROSS vertices = contour.vertices if not has_no_touch and last_touched_edge_index == len(vertices) - 1: first_edge_endpoints = first_edge_start, first_edge_end = ( vertices[-1], vertices[0]) if (relate_segments( context.segment_cls(first_edge_start, first_edge_end), segment, context) is Relation.TOUCH and start not in first_edge_endpoints and end not in first_edge_endpoints and (context.angle_orientation(start, end, first_edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, vertices[-2], first_edge_start, first_edge_end, context)): return Relation.CROSS return ((Relation.DISJOINT if has_no_touch else Relation.TOUCH) if has_no_overlap else Relation.OVERLAP)
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 _locate_point(region: Region, point: Point, context: Context) -> Tuple[Optional[int], Location]: result = False point_y = point.y for index, edge in enumerate(context.contour_segments(region)): if locate_point_in_segment(edge, point, context) is Location.BOUNDARY: return index, Location.BOUNDARY start, end = edge.start, edge.end if ((start.y > point_y) is not (end.y > point_y) and ((end.y > start.y) is (context.angle_orientation( start, end, point) is Orientation.COUNTERCLOCKWISE))): result = not result return None, (Location.INTERIOR if result else Location.EXTERIOR)
def relate_segment(contour: Contour, segment: Segment, context: Context) -> Relation: angle_orientation = context.angle_orientation has_no_touch = has_no_cross = True last_touched_edge_index = last_touched_edge_start = None start, end = segment.start, segment.end for index, sub_segment in enumerate(context.contour_segments(contour)): sub_segment_start, sub_segment_end = sub_segment_endpoints = ( sub_segment.start, sub_segment.end) relation = relate_segments(sub_segment, segment, context) if relation is Relation.COMPONENT or relation is Relation.EQUAL: return Relation.COMPONENT elif relation is Relation.OVERLAP or relation is Relation.COMPOSITE: return Relation.OVERLAP elif relation is Relation.TOUCH: if has_no_touch: has_no_touch = False elif (has_no_cross and index - last_touched_edge_index == 1 and start not in sub_segment_endpoints and end not in sub_segment_endpoints and (angle_orientation( start, end, sub_segment_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, last_touched_edge_start, sub_segment_start, sub_segment_end, context)): has_no_cross = False last_touched_edge_index = index last_touched_edge_start = sub_segment_start elif has_no_cross and relation is Relation.CROSS: has_no_cross = False vertices = contour.vertices if (has_no_cross and not has_no_touch and last_touched_edge_index == len(vertices) - 1): first_sub_segment_endpoints = (first_sub_segment_start, first_sub_segment_end) = (vertices[-1], vertices[0]) if (relate_segments( context.segment_cls(first_sub_segment_start, first_sub_segment_end), segment, context) is Relation.TOUCH and start not in first_sub_segment_endpoints and end not in first_sub_segment_endpoints and (angle_orientation(start, end, first_sub_segment_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, vertices[-2], first_sub_segment_start, first_sub_segment_end, context)): has_no_cross = False return ((Relation.DISJOINT if has_no_touch else Relation.TOUCH) if has_no_cross else Relation.CROSS)
def constrained_delaunay(cls, polygon: Polygon, *, extra_constraints: Sequence[Segment] = (), extra_points: Sequence[Point] = (), context: Context) -> 'Triangulation': """ Constructs constrained Delaunay triangulation of given polygon (with potentially extra points and constraints). Based on * divide-and-conquer algorithm by L. Guibas & J. Stolfi for generating Delaunay triangulation, * algorithm by S. W. Sloan for adding constraints to Delaunay triangulation, * clipping algorithm by F. Martinez et al. for deleting in-hole triangles. Time complexity: ``O(vertices_count * log vertices_count)`` for convex polygons without extra constraints, ``O(vertices_count ** 2)`` otherwise 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: http://www.sccg.sk/~samuelcik/dgs/quad_edge.pdf https://www.newcastle.edu.au/__data/assets/pdf_file/0019/22519/23_A-fast-algortithm-for-generating-constrained-Delaunay-triangulations.pdf https://doi.org/10.1016/j.advengsoft.2013.04.004 http://www4.ujaen.es/~fmartin/bool_op.html :param polygon: target polygon. :param extra_points: additional points to be presented in the triangulation. :param extra_constraints: additional constraints to be presented in the triangulation. :param context: geometric context. :returns: triangulation of the border, holes & extra points considering constraints. """ border, holes = polygon.border, polygon.holes if extra_points: border, holes, extra_points = complete_vertices( border, holes, extra_points, context) result = cls.delaunay(list( chain(border.vertices, flatten(hole.vertices for hole in holes), extra_points)), context=context) border_edges = context.contour_segments(border) constrain( result, chain(border_edges, flatten(map(context.contour_segments, holes)), extra_constraints)) bound(result, border_edges) cut(result, holes) result._triangular_holes_vertices.update( frozenset(hole.vertices) for hole in holes if len(hole.vertices) == 3) return result
def locate_point(contour: Contour, point: Point, context: Context) -> Location: return (Location.EXTERIOR if all( locate_point_to_segment(segment, point, context) is Location.EXTERIOR for segment in context.contour_segments(contour)) else Location.BOUNDARY)