闭合多边形的算法

Posted

技术标签:

【中文标题】闭合多边形的算法【英文标题】:Algorithm to close a polygon 【发布时间】:2017-07-24 01:14:58 【问题描述】:

我有一个多边形周长的一部分,需要关闭它。请参考这张图片

正如我所见,只有一种独特的方法可以在不分割多边形且边缘不相交的情况下闭合多边形。

而闭合边是 b->c,d->e,f->g,h->a

有什么算法可以实现这个吗?

我只能想到一种蛮力方法,尝试所有可能的组合并检查它是否形成封闭多边形(有什么好的算法来检查它是否是封闭多边形?)

有没有更好的方法或已知的算法?

注意:顶点只能用单条直线连接,多边形不一定是凸的

另外,您可以放心地假设这些线段总是形成一个多边形,因为我从一个多边形中获取这些线段并且我试图重新创建多边形

【问题讨论】:

并且只在开放点之间使用单条直线? 是的。只有单条直线。将在问题中添加它 【参考方案1】:

我认为在“表现良好”(小间隙、不太规则的形状等)的情况下,可能会采用以下方法。这个想法是假设解决方案(输入线段的特定排列,然后假设与直线连接)最小化定义感兴趣多边形边界的结果 MultiLineString 的长度。

为了解决这个问题,下面的实现使用2-opt 启发式来解决旅行商问题。它按以下步骤进行:

    顶点集定义为所有输入线段端点的并集 它尝试连接这些点,以便在属于同一输入线段的点始终连接的约束下最小化生成的 MultiLineString 的总长度(即,允许 2-opt 算法仅分割边连接不同的线段 - 这由主双 for-loop 中的额外 if 条件处理。

那么结果是:

import logging
import random
import sys
from shapely.geometry import LineString, Polygon
from shapely.ops import polygonize, linemerge

#prevent shapely from showing an error message on is_valid tests  
logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#input lines (LineStrings)
lines = [
    [(3.15,3.94), (4.06,3.91), (4.27,3.49)],
    [(0.84,2.99), (0.97,3.67), (1.68,3.91), (2.68,3.92)],
    [(4.46,3.23), (5.12,2.97), (4.60,2.00)],
    [(4.13,1.44), (4.41,0.68), (1.14,1.99)]
]
random.shuffle(lines)

N, pnts = 0, []
pnt2line = 
for line_id, line in enumerate(lines):
    #for each line, save its endpoints and remember
    #to which line each point belongs
    for pnt in [line[0], line[-1]]:
        pnt2line[N] = line_id
        pnts.append(pnt)
        N += 1

#as initial guess, try to connect these points sequentially
route = [i for i in range(0, N)]

def nrm_idx(N, idx):
    return (N + idx) % N

def get_polygon(route):
    #for given route, attempt to construct the resulting polygon
    segments = []
    m = len(route)
    for idx in range(0, m):
        i, j = route[idx], route[nrm_idx(m, idx+1)]
        if pnt2line[i] == pnt2line[j]:
            #if two consecutive points belong to the same line, consider this line
            segments.append(lines[pnt2line[i]])
        else:
            #otherwise, connect these points with a straight line
            segments.append([pnts[i], pnts[j]])

    return Polygon(linemerge(segments))

def get_weight(route):
    P = get_polygon(route)
    return P.length if P.is_valid else sys.maxsize

def edge_is_fixed(pnt_i, pnt_j):
    #check if an edge specified by point pnt_i/pnt_j can be dissected or not
    #in the latter case, the points belong to the same line/line segment
    return (pnt2line[pnt_i] == pnt2line[pnt_j])

def opt_swap(route, i, k):
    #perform 2-opt swap
    return route[0:i] + route[i:k+1][::-1] + route[k+1:]

flag = True
while flag:
    flag = False
    best_weight = get_weight(route)

    for i in range(0, N-1):
        for k in range(i+1, N):

            if edge_is_fixed(route[nrm_idx(N, i-1)], route[i]) or edge_is_fixed(route[k], route[nrm_idx(N, k+1)]):
                continue

            new_route = opt_swap(route, i, k)
            weight = get_weight(new_route)

            if weight < best_weight:
                route = new_route[:]
                best_weight = weight
                flag = True

P = get_polygon(route)
for x, y in P.exterior.coords:
    print(x, y)

对于您的输入(近似值),结果确实是:

【讨论】:

【参考方案2】:

以下是可行的方法: - 制作一个仅包含开放点的集合(仅在一条边上的点,即图中标记的点) - 在该集合上运行凸包算法 - 使用凸包的边缘来完成具有现有边缘的多边形。 (即如果凸包包含 A->B,但 A 和 B 已经通过预先存在的一组边中的相邻边间接连接,则丢弃凸包中的边 A->B)

编辑 我之前建议使用凸包算法,但这种方法有缺点,包括点不会形成凸形的情况。

请注意,根据您的规定,有些集合不会有解决方案,例如: (不可能将其完成为没有交叉线的多边形,仅在开放点之间使用单条直线)

【讨论】:

多边形并不总是凸的。在那种情况下它不会失败吗? 你可以放心地假设这些线段总是形成一个多边形,因为我从一个多边形中得到这些线段并且我试图重新创建多边形 选择凸包算法是什么意思?你能解释一下吗?

以上是关于闭合多边形的算法的主要内容,如果未能解决你的问题,请参考以下文章

CSS 六边形边框hover闭合动画效果

5.套索选取工具的使用

Clipper:裁剪打开的多边形时崩溃

WPF动画

在无向图中查找多边形

确定一个点是不是位于传单多边形内