将多边形细分为较小多边形的算法

Posted

技术标签:

【中文标题】将多边形细分为较小多边形的算法【英文标题】:Algorithm to subdivide a polygon in smaller polygons 【发布时间】:2012-01-19 11:40:36 【问题描述】:

我有一个由平面上的连续边组成的多边形,并且想将其细分为三角形或矩形的子多边形。 我在哪里可以找到一个算法来做到这一点? 谢谢!

【问题讨论】:

您能否发布一张图片,并附上您正在尝试做的事情的示例?在绘制图片的过程中,您很有可能会提出一种可接受的算法。 抱歉不清楚。基本上我正在开发一个生成街道和地块的应用程序,地块是街道交叉口内的飞机。我想将地块细分为较小的地块,以便将房屋放在较小的地块上。 @LaurentCrivello:正如missigno 提到的,这是一个已知问题。如果您正在寻找示例代码,here's Haskell 中的一个简短实现(查看triangulatePolygon 你可能会觉得this Soulwire blog post很有趣。 【参考方案1】:

经过多次搜索偶然发现。

感谢@Aditya Chhabra 的提交,它工作得很好,但get_squares_from_rect 由于迭代剪辑,对于小边长来说非常慢。

如果我们将所有 LineStrings 合并到一个集合中,然后一步进行剪辑和多边形化,我们可以立即做到这一点,我在 this question 中找到了这一点。

以前边长 0.0001 (EPSG:4326) 需要 > 1 分钟,现在不需要时间。

from shapely.ops import unary_union, polygonize, linemerge
from shapely.geometry import LineString
import numpy as np


def get_squares_from_rect_faster(RectangularPolygon, side_length=0.0025):
    rect_coords = np.array(RectangularPolygon.boundary.coords.xy)
    y_list = rect_coords[1]
    x_list = rect_coords[0]
    y1 = min(y_list)
    y2 = max(y_list)
    x1 = min(x_list)
    x2 = max(x_list)
    width = x2 - x1
    height = y2 - y1

    xcells = int(np.round(width / side_length))
    ycells = int(np.round(height / side_length))

    yindices = np.linspace(y1, y2, ycells + 1)
    xindices = np.linspace(x1, x2, xcells + 1)

    horizontal_splitters = [
        LineString([(x, yindices[0]), (x, yindices[-1])]) for x in xindices
    ]
    vertical_splitters = [
        LineString([(xindices[0], y), (xindices[-1], y)]) for y in yindices
    ]

    lines = horizontal_splitters + vertical_splitters
    lines.append(RectangularPolygon.boundary)
    lines = unary_union(lines)
    lines = linemerge(lines)

    return list(polygonize(lines))

【讨论】:

【参考方案2】:

我自己也在寻找这个问题的答案,但找不到。尝试将几块拼接在一起,结果如下。

这不一定是最理想的例程,但它为我完成了工作。如果您想提高性能,请尝试使用代码。

算法的简要说明

    使用原始几何本身的边界、凸包的边界和最小旋转矩形,导出所有可能的矩形。

    将所有矩形分成指定边长的小方块

    删除重复项使用四舍五入的质心。 (r: 四舍五入参数)

    保留那些“在”几何体中的方格,或者那些“相交”几何体的方格,取决于哪个更接近所需方格的总数。

已编辑

新解决方案

#### Python script for dividing any shapely polygon into smaller equal sized polygons

import numpy as np
from shapely.ops import split
import geopandas
from shapely.geometry import MultiPolygon, Polygon

def rhombus(square):
    """
    Naively transform the square into a Rhombus at a 45 degree angle
    """
    coords = square.boundary.coords.xy
    xx = list(coords[0])
    yy = list(coords[1])
    radians = 1
    points = list(zip(xx, yy))
    Rhombus = Polygon(
        [
            points[0],
            points[1],
            points[3],
            ((2 * points[3][0]) - points[2][0], (2 * points[3][1]) - points[2][1]),
            points[4],
        ]
    )
    return Rhombus


def get_squares_from_rect(RectangularPolygon, side_length=0.0025):
    """
    Divide a Rectangle (Shapely Polygon) into squares of equal area.

    `side_length` : required side of square

    """
    rect_coords = np.array(RectangularPolygon.boundary.coords.xy)
    y_list = rect_coords[1]
    x_list = rect_coords[0]
    y1 = min(y_list)
    y2 = max(y_list)
    x1 = min(x_list)
    x2 = max(x_list)
    width = x2 - x1
    height = y2 - y1

    xcells = int(np.round(width / side_length))
    ycells = int(np.round(height / side_length))

    yindices = np.linspace(y1, y2, ycells + 1)
    xindices = np.linspace(x1, x2, xcells + 1)
    horizontal_splitters = [
        LineString([(x, yindices[0]), (x, yindices[-1])]) for x in xindices
    ]
    vertical_splitters = [
        LineString([(xindices[0], y), (xindices[-1], y)]) for y in yindices
    ]
    result = RectangularPolygon
    for splitter in vertical_splitters:
        result = MultiPolygon(split(result, splitter))
    for splitter in horizontal_splitters:
        result = MultiPolygon(split(result, splitter))
    square_polygons = list(result)

    return square_polygons


def split_polygon(G, side_length=0.025, shape="square", thresh=0.9):
    """
    Using a rectangular envelope around `G`, creates a mesh of squares of required length.
    
    Removes non-intersecting polygons. 
            

    Args:
    
    - `thresh` : Range - [0,1]

        This controls - the number of smaller polygons at the boundaries.
        
        A thresh == 1 will only create (or retain) smaller polygons that are 
        completely enclosed (area of intersection=area of smaller polygon) 
        by the original Geometry - `G`.
        
        A thresh == 0 will create (or retain) smaller polygons that 
        have a non-zero intersection (area of intersection>0) with the
        original geometry - `G` 

    - `side_length` : Range - (0,infinity)
        side_length must be such that the resultant geometries are smaller 
        than the original geometry - `G`, for a useful result.

        side_length should be >0 (non-zero positive)

    - `shape` : square/rhombus
        Desired shape of subset geometries. 


    """
    assert side_length>0, "side_length must be a float>0"
    Rectangle    = G.envelope
    squares      = get_squares_from_rect(Rectangle, side_length=side_length)
    SquareGeoDF  = geopandas.GeoDataFrame(squares).rename(columns=0: "geometry")
    Geoms        = SquareGeoDF[SquareGeoDF.intersects(G)].geometry.values
    if shape == "rhombus":
        Geoms = [rhombus(g) for g in Geoms]
        geoms = [g for g in Geoms if ((g.intersection(G)).area / g.area) >= thresh]
    elif shape == "square":
        geoms = [g for g in Geoms if ((g.intersection(G)).area / g.area) >= thresh]
    return geoms

# Reading geometric data

geo_filepath = "/data/geojson/pc_14.geojson"
GeoDF        = geopandas.read_file(geo_filepath)

# Selecting random shapely-geometry

G = np.random.choice(GeoDF.geometry.values)


squares   = split_polygon(G,shape='square',thresh=0.5,side_length=0.025)
rhombuses = split_polygon(G,shape='rhombus',thresh=0.5,side_length=0.025)


以前的解决方案:

import numpy as np
import geopandas
from shapely.ops import split
from shapely.geometry import MultiPolygon, Polygon, Point, MultiPoint

def get_rect_from_geom(G, r=2):
    """
    Get rectangles from a geometry.
    r = rounding factor.
    small r ==> more rounding off ==> more rectangles
    """
    coordinate_arrays = G.exterior.coords.xy
    coordinates = list(
        zip(
            [np.round(c, r) for c in coordinate_arrays[0]],
            [np.round(c, r) for c in coordinate_arrays[1]],
        )
    )
    Rectangles = []
    for c1 in coordinates:
        Coords1 = [a for a in coordinates if a != c1]
        for c2 in Coords1:
            Coords2 = [b for b in Coords1 if b != c2]
            x1, y1 = c1[0], c1[1]
            x2, y2 = c2[0], c2[1]
            K1 = [k for k in Coords2 if k == (x1, y2)]
            K2 = [k for k in Coords2 if k == (x2, y1)]
            if (len(K1) > 0) & (len(K2) > 0):
                rect = [list(c1), list(K1[0]), list(c2), list(K2[0])]
                Rectangles.append(rect)
    return Rectangles


def get_squares_from_rect(rect, side_length=0.0025):
    """
    Divide a rectangle into equal area squares
    side_length = required side of square
    """
    y_list = [r[1] for r in rect]
    x_list = [r[0] for r in rect]
    y1 = min(y_list)
    y2 = max(y_list)
    x1 = min(x_list)
    x2 = max(x_list)
    width = x2 - x1
    height = y2 - y1

    xcells, ycells = int(np.round(width / side_length)), int(
        np.round(height / side_length)
    )

    yindices = np.linspace(y1, y2, ycells + 1)
    xindices = np.linspace(x1, x2, xcells + 1)
    horizontal_splitters = [
        LineString([(x, yindices[0]), (x, yindices[-1])]) for x in xindices
    ]
    vertical_splitters = [
        LineString([(xindices[0], y), (xindices[-1], y)]) for y in yindices
    ]
    result = Polygon(rect)
    for splitter in vertical_splitters:
        result = MultiPolygon(split(result, splitter))
    for splitter in horizontal_splitters:
        result = MultiPolygon(split(result, splitter))
    square_polygons = list(result)
    return [np.stack(SQPOLY.exterior.coords.xy, axis=1) for SQPOLY in square_polygons]


def round_centroid(g, r=10):
    """
    Get Centroids.
    Round off centroid coordinates to `r` decimal points.
    """
    C = g.centroid.coords.xy
    return (np.round(C[0][0], r), np.round(C[1][0], r))


def subdivide_polygon(g, side_length=0.0025, r=10):
    """
    1. Create all possible rectangles coordinates from the geometry, its minimum rotated rectangle, and its convex hull.
    2. Divide all rectangles into smaller squares.

    small r ==> more rounding off ==> fewer overlapping squares. (these are dropped as duplicates)
    large r ==> may lead to a few overlapping squares.
    """
    #   Number of squares required.
    num_squares_reqd = g.area // (side_length ** 2)

    # Some of these combinations can be dropped to improve performance.
    Rectangles = []
    Rectangles.extend(get_rect_from_geom(g))
    Rectangles.extend(get_rect_from_geom(g.minimum_rotated_rectangle))
    Rectangles.extend(get_rect_from_geom(g.convex_hull))

    Squares = []
    for r in range(len(Rectangles)):
        rect = Rectangles[r]
        Squares.extend(get_squares_from_rect(rect, side_length=side_length))
    SquarePolygons = [Polygon(square) for square in Squares]
    GDF = geopandas.GeoDataFrame(SquarePolygons).rename(columns=0: "geometry")
    GDF.loc[:, "centroid"] = GDF.geometry.apply(round_centroid, r=r)
    GDF = GDF.drop_duplicates(subset=["centroid"])

    wgeoms = GDF[GDF.within(g)].geometry.values
    igeoms = GDF[GDF.intersects(g)].geometry.values

    w = abs(num_squares_reqd - len(wgeoms))
    i = abs(num_squares_reqd - len(igeoms))

    print(w, i)
    if w <= i:
        return wgeoms
    else:
        return igeoms



geoms = subdivide(g)

【讨论】:

这看起来真不错!不过我有一个问题:当我将它用于我的数据 (POLYGON ((122.395 32.221, 122.395 32.221, 122.34 35.444, 193.444 35.000 ) 时,我收到错误 AttributeError: 'GeoSeries' object has no attribute 'coords'。您输入为g 的几何图形是什么样的? @SergedeGossondeVarennes,根据我的评论,您可能使用的是 Geoseries(geopandas 库的一个类),而不是 shapely 库中的几何图形。上述函数中的 g 采用单个 Shapely Polygon 对象。如果你能分享一些你的代码,我可以说更多。 我发现并纠正了它。它对某些几何图形非常有效。但对于某些人来说,它没有,我实际上为此创建了一个问题。 ***.com/q/67965664/5363686 。在某些情况下,get_rect_from_geom(G, r=1) 返回一个空字段。你经历过吗?顺便说一句....真的很棒的解决方案。 @SergedeGossondeVarennes - 我已经编辑了我的答案。这要简单得多,而且速度要快得多。希望这会有所帮助。 它就像一个魅力!各种形状! @Aditya Chhabra,纯粹的天才!【参考方案3】:

在computational geometry中,你要解决的问题叫做triangulation。

有一些算法可以解决这个问题,给出具有不同属性的三角剖分。您需要决定哪一个最合适。

【讨论】:

谢谢。然而,以三角形结尾并不是我的最终目标,因为矩形更符合我的定义。不过我还是看看吧,谢谢!

以上是关于将多边形细分为较小多边形的算法的主要内容,如果未能解决你的问题,请参考以下文章

如何计算多边形的圆角?

通过平铺三角形来镶嵌任意多边形

matlab实现封闭四边形网格的Catmull-Clark细分(CC细分)

求任意多边形的最大内接圆算法,c++编程

将圆连接成多边形的算法

裁剪算法——多边形裁剪/文字裁剪