def test_area(triangle: Polygon, is_counterclockwise: bool, fraction: Fraction) -> None: assume(0 < fraction < 1) requirement = triangle.area * fraction if not is_counterclockwise: triangle = Polygon(triangle.border.to_clockwise()) pivot, low_area_point, high_area_point = triangle.border.vertices new_point = splitter_point(requirement=requirement, pivot=pivot, low_area_point=low_area_point, high_area_point=high_area_point) new_triangle = Polygon(Contour((pivot, low_area_point, new_point))) assert new_triangle.area == requirement
def splitter_point(requirement: float, pivot: Point, low_area_point: Point, high_area_point: Point) -> Point: """Alternative to bisection search since we always have triangles""" if requirement <= 0: raise ValueError("Can't have a zero or negative requirement") p = pivot l = low_area_point h = high_area_point contour = Contour([p, l, h]) triangle = Polygon(contour) if triangle.area < requirement: raise ValueError("Can't have a requirement greater than the area of " "the triangle") r = requirement dx = h.x - l.x if dx == 0: if contour.orientation is Orientation.COUNTERCLOCKWISE: a = (p.y - l.y) / (p.x - l.x) b = (p.x * l.y - l.x * p.y - 2 * r) / (p.x - l.x) x = l.x y = a * l.x + b return Point(x, y) else: t = triangle.area - r a = (p.y - h.y) / (p.x - h.x) b = (p.x * h.y - h.x * p.y - 2 * t) / (p.x - h.x) x = h.x y = a * h.x + b return Point(x, y) k = (h.y - l.y) / dx m = h.y - k * h.x if contour.orientation is Orientation.COUNTERCLOCKWISE: dx = p.x - l.x if dx == 0: x = (2 * r + l.x * p.y - p.x * l.y) / (p.y - l.y) y = k * x + m return Point(x, y) a = (p.y - l.y) / dx b = (p.x * l.y - l.x * p.y - 2 * r) / dx else: t = triangle.area - r dx = p.x - h.x if dx == 0: x = (2 * t + h.x * p.y - p.x * h.y) / (p.y - h.y) y = k * x + m return Point(x, y) a = (p.y - h.y) / dx b = (p.x * h.y - h.x * p.y - 2 * t) / dx x = (m - b) / (a - k) y = a * x + b return Point(x, y)
def to_graph(polygon: Polygon, extra_points: List[Point], *, convex_divisor: ConvexDivisorType) -> nx.Graph: """ Converts polygon to a region-adjacency graph by dividing it to parts using `convex_divisor` function. Resulting parts become nodes connected when they touch each other. :param polygon: input polygon that will be split :param extra_points: list of points which will be used in splitting the polygon to convex parts :param convex_divisor: function to split the polygon into convex parts :return: graph with parts of the polygon as nodes; edges will keep `side` attributes with the touching segments. """ graph = nx.Graph() polygon_border = polygon.border.raw() holes = list(map(Contour.raw, polygon.holes)) site_points = list(map(Point.raw, extra_points)) polygon_points = {*polygon_border, *chain.from_iterable(holes)} extra_points = list(set(site_points) - polygon_points) parts = convex_divisor(polygon_border, holes, extra_points=extra_points, extra_constraints=()) parts = [Polygon.from_raw((list(part), [])) for part in parts] if len(parts) == 1: graph.add_nodes_from(parts) else: if convex_divisor is constrained_delaunay_triangles: parts_per_sides = defaultdict(set) for part in parts: for side in edges(part.border): parts_per_sides[side].add(part) for side, parts in parts_per_sides.items(): if len(parts) == 2: graph.add_edge(*parts, side=side) else: pairs: Iterator[Tuple[Polygon, Polygon]] = combinations(parts, 2) for part, other in pairs: intersection = part & other if isinstance(intersection, Segment): graph.add_edge(part, other, side=intersection) return graph
def joined_constrained_delaunay_triangles( border: ContourType, holes: Sequence[ContourType] = (), *, extra_points: Sequence[PointType] = (), extra_constraints: Sequence[SegmentType] = () ) -> ConvexPartsType: """Joins polygons to form convex parts of greater size""" triangles = constrained_delaunay_triangles( border, holes, extra_points=extra_points, extra_constraints=extra_constraints) polygons = [ Polygon.from_raw((list(triangle), [])) for triangle in triangles ] initial_polygon = polygons.pop() result = [] while True: resulting_polygon = initial_polygon for index, polygon in enumerate(iter(polygons)): polygon_sides = set(edges(polygon.border)) common_side = next((edge for edge in edges(resulting_polygon.border) if edge in polygon_sides), None) if common_side is None: continue has_point_on_edge = any( Point.from_raw(raw_point) in common_side for raw_point in extra_points) if has_point_on_edge: continue union_ = unite(resulting_polygon, polygon) if isinstance(union_, Polygon) and union_.is_convex: polygons.pop(index) resulting_polygon = union_ if resulting_polygon is not initial_polygon: initial_polygon = resulting_polygon continue result.append(resulting_polygon.border.raw()) if not polygons: return result initial_polygon = polygons.pop()
def plr(self, polygon: Polygon, splitter: Segment) -> RootedPolygons: """ Portion of the current polygon to the right of the splitter plus all immediate ancestors accessible through the polygon edges that lie on the right of the splitter """ vertices_in_interval = cut(polygon.border, splitter.start, splitter.end) if len(vertices_in_interval) < 3: current_polygon_part = EMPTY else: contour = Contour(shrink_collinear_vertices( Contour(vertices_in_interval))) current_polygon_part = Polygon(contour) pred_poly_by_line = self.pred_poly_by_line(polygon, splitter=splitter) return RootedPolygons(root=current_polygon_part, predecessors=pred_poly_by_line)
def _(geometry: Polygon) -> Polygon: return Polygon(to_fractions(geometry.border), list(map(to_fractions, geometry.holes)))
def orient(polygon: Polygon) -> Polygon: """To counterclockwise. No holes""" return Polygon(polygon.border.to_counterclockwise())
def split(*, polygon: Polygon, vertices: List[Point], sites: FrozenSet[Requirement], graph: Graph ) -> Union[Tuple[nx.DiGraph, nx.DiGraph], Tuple[nx.DiGraph, nx.DiGraph, nx.DiGraph]]: """Splits a PredPoly to 2 or 3 parts for further division""" if polygon.border.orientation is not Orientation.COUNTERCLOCKWISE: raise ValueError("Polygon division is implemented only for polygons " "oriented counter-clockwise") sites_locations = set(site.point for site in sites) first_site_index, first_site_point = next( ((index, vertex) for index, vertex in enumerate(vertices[1:], start=1) if vertex in sites_locations), (0, vertices[0])) first_head_index = max(1, first_site_index) plrs = [graph.plr(polygon, Segment(vertices[0], vertex)) for vertex in vertices[first_head_index:]] head_indices = range(first_head_index, len(vertices)) heads = vertices[first_head_index:] if len(sites) == 1: sites_per_vertex = [sites] * len(vertices) requirements = [list(sites)[0].area] * len(vertices) else: sites_per_vertex = list(to_requirements_per_vertex(heads, sites)) requirements = [sum(site.area for site in sites_) for sites_ in sites_per_vertex] for (plr, head_index, requirement, right_sites) in zip(plrs, head_indices, requirements, sites_per_vertex): if plr.area >= requirement: break if plr.area >= requirement: if head_index == first_site_index: origins_indices = range(first_site_index - 1, -1, -1) origins = vertices[first_site_index - 1::-1] plrs = [graph.plr(polygon, Segment(origin, first_site_point)) for origin in origins] for plr, origin_index in zip(plrs, origins_indices): if plr.area >= requirement: break pivot_index = head_index low_area_index = origin_index + 1 high_area_index = origin_index splitter = Segment(vertices[low_area_index], vertices[head_index]) else: pivot_index = 0 low_area_index = head_index - 1 high_area_index = head_index splitter = Segment(vertices[pivot_index], vertices[low_area_index]) pivot_point = vertices[pivot_index] low_area_point = vertices[low_area_index] high_area_point = vertices[high_area_index] triangle = Polygon(Contour((pivot_point, low_area_point, high_area_point))) plr_1 = graph.plr(polygon, splitter) pll_1 = graph.pll(polygon, splitter) edge = (Segment(low_area_point, high_area_point) if pivot_index == 0 else Segment(high_area_point, low_area_point)) if plr_1.area + triangle.area > requirement: triangle_requirement = requirement - plr_1.area t = splitter_point(requirement=triangle_requirement, pivot=pivot_point, low_area_point=low_area_point, high_area_point=high_area_point) triangle = Polygon(Contour([low_area_point, t, pivot_point])) a = to_subgraph(predecessors=plr_1.predecessors, old_root=polygon, new_root=plr_1.root | triangle, sites=right_sites, graph=graph) b = to_subgraph(predecessors=pll_1.predecessors, old_root=polygon, new_root=pll_1.root - triangle, sites=sites - right_sites, graph=graph) return a, b pred_by_line = graph.pred_poly_by_line(polygon, edge) plr_and_pred_by_line_area = plr_1.area + pred_by_line.area if plr_and_pred_by_line_area < requirement: requirement = requirement - plr_and_pred_by_line_area t = splitter_point(requirement=requirement, pivot=pivot_point, low_area_point=low_area_point, high_area_point=high_area_point) triangle = Polygon(Contour([low_area_point, t, pivot_point])) edge = (Segment(low_area_point, t) if pivot_index == 0 else Segment(t, low_area_point)) pred_by_line = graph.pred_poly_by_line(polygon, edge) a = to_subgraph(predecessors=plr_1.predecessors | pred_by_line, old_root=polygon, new_root=plr_1.root | triangle, sites=right_sites, graph=graph) b = to_subgraph(predecessors=pll_1.predecessors - pred_by_line, old_root=polygon, new_root=pll_1.root - triangle, sites=sites - right_sites, graph=graph) return a, b else: ps = Multipoint(low_area_point, high_area_point).centroid triangle = Polygon(Contour([low_area_point, ps, pivot_point])) edge = (Segment(low_area_point, high_area_point) if pivot_index == 0 else Segment(high_area_point, low_area_point)) pred_by_line = graph.pred_poly_by_line(polygon, edge) a = graph.subgraph(pred_by_line) b = to_subgraph(predecessors=plr_1.predecessors, old_root=polygon, new_root=plr_1.root | triangle, sites=right_sites, graph=graph) c = to_subgraph(predecessors=pll_1.predecessors - pred_by_line, old_root=polygon, new_root=pll_1.root - triangle, sites=sites - right_sites, graph=graph) return b, a, c else: t = Multipoint(vertices[-1], vertices[0]).centroid splitter = Segment(t, first_site_point) plr_1 = graph.plr(polygon, splitter) pll_1 = graph.pll(polygon, splitter) first_site_set = sites_per_vertex[0] a = to_subgraph(predecessors=plr_1.predecessors, old_root=polygon, new_root=plr_1.root, sites=first_site_set, graph=graph) b = to_subgraph(predecessors=pll_1.predecessors, old_root=polygon, new_root=pll_1.root, sites=sites - first_site_set, graph=graph) return b, a