创建穿过所有给定点的非相交多边形

Posted

技术标签:

【中文标题】创建穿过所有给定点的非相交多边形【英文标题】:Create non-intersecting polygon passing through all given points 【发布时间】:2012-12-25 03:45:56 【问题描述】:

假设我有一个随机顺序的点数组,我需要找到一个多边形(通过对它们进行排序,使得每个相邻对代表一个边),它通过 所有 点,当然,它的边是不相交的。

我尝试通过选择一个点并将所有点添加到它下方的最终数组中,从左到右排序。然后,添加它上面的所有点,从右到左排序。

有人告诉我,我可以添加一个额外的点并自然地排序以避免自相交。但我无法弄清楚这一点。有什么简单的方法可以做到这一点?

【问题讨论】:

听起来像是“旅行推销员问题” @AxelKemper 除了 OP 似乎没有寻找最短路径,而是寻找任何非自相交的路径。我认为不需要优化。 我对我的答案进行了重大更改。如果你想要 Mathematica 代码,请给我发电子邮件。 @max 你解决了这个问题吗? 这不是很好(而且有点违背 SO 的精神)......其他来到这个页面的人呢?为什么不在这里发布更改后的答案? 【参考方案1】:

测试两个线段是否相交既简单又快速。例如,请参阅that。

这样你就可以迭代地构建你的多边形:

来源点:S = S0, ... Si, Sj,...

最终多边形:A = A0, ... Ai, Aj,...

您从S 满和A 空开始。

S 的前3 个点并将它们移动到A。这个三角形当然不是自相交的。

然后,直到S 为空,取其剩余的第一个点,我们将调用P,并在A 中寻找可以插入它的位置。

这个位置是i+1 用于第一个i 验证[Ai-P][Ai+1-P] 都不与任何其他段[Ak-Ak+1] 相交。

因此,您的新多边形 AA0, ... Ai, P, Ai+1, ...

【讨论】:

【参考方案2】:

相信你可以使用Graham scan算法来解决你的问题。

编辑:一般来说,Convex hull algorithms 是值得一看的。

【讨论】:

凸包在这里做不到,多边形应该通过所有个点。 我认为修改后的 Graham 扫描正是 OP 所需要的。选择一个点,然后按顺时针(或逆时针)顺序对其余点进行排序。按排序顺序连接点。对 Graham 扫描的修改是您无需担心“左转”或“右转”,因为您不会从船体上移除任何点。 @mbeckish 我相信甚至不需要提及格雷厄姆扫描 - 只需选择凸包内的某个位置(例如所有点的平均值)并围绕所选位置按顺时针顺序连接所有点。 【参考方案3】:

正如有人所说,最小长度解决方案正是旅行商问题。这是一种非最佳但可行的方法:

计算Delauney triangulation 的点数。连续删除边界线段,直到剩下的边界可以插值所有点或不能删除更多线段。如果使用该段的三角形的所有点都在边界上,则不要删除边界段。以此边界为路径。

我在 Mathematica 中使用 40 个随机点实现了这一点。这是一个典型的结果:

明显的反对意见是,您可能会遇到一个点,即并非所有点都是边界点,但是您不能在不使边界自相交的情况下删除边界段。这是一个有效的反对意见。我跑了几十次才看到发生这种情况的案例,但最终得到了这个案例:

您可能会看到一些使用本地拓扑解决此问题的明显方法,但我会将详细信息留给您!可能有帮助的一件事是“边缘翻转”,您取两个共享边的三角形,例如三角形 (p,q,r) 和 (q,p,s) 并将它们替换为 (r,p,s) 和 ( r,s,q)(围绕三角形逆时针方向的所有坐标)。只要此变换中生成的三角形也是逆时针排序的,就可以做到这一点。

为了减少修复的需要,您需要在每个步骤中对要删除的段做出正确的选择。我使用了边界线段的长度与候选三角形另一边的长度之和的比率(由可能进入的点与线段形成的三角形)。

【讨论】:

【参考方案4】:

好吧,如果您实际上并不关心最小化或类似的事情,您可以将新点放在凸包内,然后按角度对其他点进行排序。你会得到一个不相交的多边形。

【讨论】:

【参考方案5】:

您所寻求的在文献中被称为简单的多边形化。例如,请参阅有关该主题的 this web page。 正如 Miguel 所说,生成star-shaped 多边形很容易,但很难 例如,正如 Axel Kemper 所提到的,找到最小周长多边形化,即最小 TSP。对于给定的点集,通常存在指数级的不同多边形化。

对于星形多边形化,有一个问题需要注意:额外点x(在星的“内核”中)不能与现有点重合! 这是一种保证x的算法。找到最近的一对点 (a,b),让 x 为线段 ab 的中点。然后按照 Miguel 的帖子继续。

【讨论】:

【参考方案6】:

我们的策略是制定一个计划,确保多边形 包括所有点,我们可以找到连接它们的顺序 没有一条线相交的地方。

算法

    找到最左边的点 p 找到最右边的点q 将点划分为 A(pq 下方的点集)和 B(pq 上方的点集)[您可以使用 (p,q,?) 上的左转测试来 确定一个点是否在线上方]。 按 x 坐标对 A 进行排序(递增) 按 x 坐标(递减)对 B 进行排序。 按顺序返回由p定义的多边形,A中的点,q,B中的点。

运行时

步骤 1,2,3 需要 O(n) 时间。 步骤 4,5 需要 O(nlogn) 时间。 第 6 步需要 O(n) 时间。 总运行时间为 O(nlogn)。

正确性

通过构造,除了 p,q 之外的所有点都在集合 A 或 设置 B. 我们从第 6 行输出的多边形因此输出一个多边形 所有的点。我们现在需要论证的是,在 我们的输出多边形彼此相交。

考虑 输出多边形。从 p 到 A 中第一个点的第一条边不能 与任何线段相交(因为还没有线段)。随着我们继续 以 x 坐标通过 A 中的点的顺序,从每个点, 下一段向右,所有前面的段都向右 左边。因此,当我们从 p 出发,经过 A 的所有点,到达点 q,我们将没有交叉点。

我们从 q 往后走也是如此 通过 B 的点。这些线段不能相互交叉 因为它们是从右到左的。这些段也不能 与 A 中的任何东西相交,因为 A 中的所有点都低于 pq 线,并且 B 中的所有点都在这条线之上。

因此,没有段相交 其他,我们有一个简单的多边形。

来源:Broken link

【讨论】:

我做了一个脚本来实现这个算法。代码有点乱,我把cmets放在了必要的地方。 gist.github.com/Azeirah/75d44a6803b88e37ea8703a040e89353 该算法的 Python 3.6 实现:***.com/questions/14263284/… 很遗憾,PDF 的链接已失效。 Wayback 机器也没有帮助。 “您可以使用 (p,q,?) 上的左转测试来确定一个点是否在线上” - 什么是“左转测试”? left turn test 是点线定位函数 - 从同一顶点开始的两条线的叉积 - 当一条线在另一条线的右侧时,它给出加号 (>0),而对于相反,因此您可以使用它来确定点何时高于 pq-line 或低于 pq-line。【参考方案7】:

警告! 有时多边形相交,我不知道为什么。这可能是我的实施问题。有关交集示例,请参见 cmets。

这是基于bdean20 的answer 的python 3.6 代码(需要的库:matplotlib、numpy)。

图片说明:

左上角 - 预定义多边形,其他多边形随机生成。 虚线 - 连接绿色(最左边)和红色(最右边)多边形的 分。 黑点位于虚线上。 橙色点位于虚线下方。 蓝点位于虚线上方。

=========

import random
from operator import itemgetter
import numpy
import matplotlib
import matplotlib.pyplot

class Create_random_polygon:
    
    def __init__(self, array, min_rand_coord = None, max_rand_coord = None, points_num = None):        
        self.array = array
        self.min_rand_coord = min_rand_coord 
        self.max_rand_coord = max_rand_coord
        self.points_num = points_num

    def generate_random_points(self):
        random_coords_list = []
        for x in range(self.points_num):
            coords_tuple = (random.randint(self.min_rand_coord, self.max_rand_coord),
                            random.randint(self.min_rand_coord, self.max_rand_coord))
            random_coords_list.append(coords_tuple)
        self.array = random_coords_list
        return random_coords_list
    
    def close_line_to_polygon(self):
        a = self.array[0]
        b = self.array[len(self.array)-1]
        if a == b:
            pass
        else:
            self.array.append(a)    

    def find_leftmost_point(self):
        leftmost_point = None
        leftmost_x = None
        for point in self.array:
            x = point[0]
            if leftmost_x == None or x < leftmost_x:
                leftmost_x = x
                leftmost_point = point
        return leftmost_point

    def find_rightmost_point(self):
        rightmost_point = None
        rightmost_x = None
        for point in self.array:
            x = point[0]
            if rightmost_x == None or x > rightmost_x:
                rightmost_x = x
                rightmost_point = point
        return rightmost_point

    def is_point_above_the_line(self, point, line_points):
        """return 1 if point is above the line
           return -1 if point is below the line
           return  0 if point is lays on the line"""
        px, py = point
        P1, P2 = line_points
        P1x, P1y = P1[0], P1[1]
        P2x, P2y = P2[0], P2[1]
        array = numpy.array([
            [P1x - px, P1y - py],
            [P2x - px, P2y - py],
            ])
        det = numpy.linalg.det(array)
        sign = numpy.sign(det)
        return sign
    
    def sort_array_into_A_B_C(self, line_points):
        [(x_lm, y_lm), (x_rm, y_rm)] = line_points
        A_array, B_array, C_array = [], [], []
        for point in self.array:
            x, y = point
            sing = self.is_point_above_the_line( (x, y), line_points)
            if sing == 0:
                C_array.append(point)
            elif sing == -1:
                A_array.append(point)
            elif sing == 1:
                B_array.append(point)
        return A_array, B_array, C_array

    def sort_and_merge_A_B_C_arrays(self, A_array, B_array, C_array):
        A_C_array = [*A_array, *C_array]
        A_C_array.sort(key=itemgetter(0))
        B_array.sort(key=itemgetter(0), reverse=True)        
        merged_arrays = [*A_C_array, *B_array]
        self.array = merged_arrays

    def show_image(self, array, line_points, A_array, B_array, C_array):
        [(x_lm, y_lm), (x_rm, y_rm)] = line_points        
        x = [x[0] for x in array]
        y = [y[1] for y in array]
        Ax = [x[0] for x in A_array]
        Ay = [y[1] for y in A_array]
        Bx = [x[0] for x in B_array]
        By = [y[1] for y in B_array]
        Cx = [x[0] for x in C_array]
        Cy = [y[1] for y in C_array]          
        matplotlib.pyplot.plot(Ax, Ay, 'o', c='orange') # below the line
        matplotlib.pyplot.plot(Bx, By, 'o', c='blue') # above the line
        matplotlib.pyplot.plot(Cx, Cy, 'o', c='black') # on the line
        matplotlib.pyplot.plot(x_lm, y_lm, 'o', c='green') # leftmost point
        matplotlib.pyplot.plot(x_rm, y_rm, 'o', c='red') # rightmost point
        x_plot = matplotlib.pyplot.plot([x_lm, x_rm], [y_lm, y_rm], linestyle=':', color='black', linewidth=0.5) # polygon's division line
        x_plot = matplotlib.pyplot.plot(x, y, color='black', linewidth=1) # connect points by line in order of apperiance        
        matplotlib.pyplot.show()

    def main(self, plot = False):
        'First output is random polygon coordinates array (other stuff for ploting)'
        print(self.array)
        if self.array == None:
            if not all(
                [isinstance(min_rand_coord, int),
                 isinstance(max_rand_coord, int),
                 isinstance(points_num, int),]
                ):
                print('Error! Values must be "integer" type:', 'min_rand_coord =',min_rand_coord, ', max_rand_coord =',max_rand_coord, ', points_num =',points_num)
            else:                
                self.array = self.generate_random_points()            

        print(self.array)
        x_lm, y_lm = self.find_leftmost_point()
        x_rm, y_rm = self.find_rightmost_point()
        line_points = [(x_lm, y_lm), (x_rm, y_rm)]

        A_array, B_array, C_array = self.sort_array_into_A_B_C(line_points)
        self.sort_and_merge_A_B_C_arrays(A_array, B_array, C_array)
        self.close_line_to_polygon()
        if plot:
            self.show_image(self.array, line_points, A_array, B_array, C_array)
        return self.array

if __name__ == "__main__":
    # predefined polygon
    array = [ 
        (0, 0),
        (2, 2),
        (4, 4),
        (5, 5),
        (0, 5),        
        (1, 4),
        (4, 2),
        (3, 3),
        (2, 1),
        (5, 0),
        ]    
    array = None # no predefined polygon
    min_rand_coord = 1
    max_rand_coord = 10000
    points_num = 30
    
    crt = Create_random_polygon(array, min_rand_coord, max_rand_coord, points_num)
    polygon_array = crt.main(plot = True)    

===========

【讨论】:

这正是我需要的。你能用 javascript 重新定义代码吗? @Harish 不幸的是,我只知道如何使用 Python 编写代码。 好的@Mr.车。感谢您的回复。 不,[(10, 20), (17, 5), (1, 16), (1, 14), (20, 8), (4, 7), (6, 9)] 创建相交多边形 [(1, 19), (12, 18), (10, 1), (1, 9), (5, 16), (10, 18), (2, 1)], [(13, 17), (15, 3), (14, 13), (11, 8), (7, 16), (7, 7), (10, 15)] 也失败了【参考方案8】:

我刚刚遇到了同样的问题,并想出了一些非常简单的解决方案,也是 n*log(n) 复杂度。

首先取图形内部的某个点,不管哪个,它是中心点是有意义的,无论是在最远点的中间还是在所有点的平均值(如重心) )。

然后根据从中心点看它们的角度对所有点进行排序。对于一个点和中心,排序键相当于 atan2。

就是这样。假设 p 是一个点数组 (x, y),这就是 Python 代码。

center = reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), p, (0, 0))
center = (center[0] / len(p), (center[1] / len(p)))
p.sort(key = lambda a: math.atan2(a[1] - center[1], a[0] - center[0]))

【讨论】:

【参考方案9】:

这是我对 Pawel Pieczul 的 answer 的 Typescript 实现,它非常适合我涉及简单多边形的用例:

interface Point 
    x: number,
    y: number,
    z?: number,


const getCentroid = (points: Point[]) => 
    let centroid =  x: 0, y: 0 
    for (let i = 0; i < points.length; i++) 
        centroid.x += points[i].x
        centroid.y += points[i].y
    

    centroid.x /= points.length
    centroid.y /= points.length
    return centroid


export const sortNonIntersecting = (points: Point[]) => 
    const center = getCentroid(points)
    return points.slice().sort((a: Point, b: Point) => 
        const angleA = Math.atan2(a.y - center.y, a.x - center.x)
        const angleB = Math.atan2(b.y - center.y, b.x - center.x)
        return angleA - angleB
    )

【讨论】:

【参考方案10】:

我修改了Comrade Che中的代码 的answer 以避免在存在多个最左边或最右边的点时生成相交多边形(例如,[(10, 20), (17, 5), (1, 16), (1, 14), (20 , 8), (4, 7), (6, 9)])。主要变化是如果存在多个最左边或最右边的点,则与它们的y坐标进行比较并选择底部的一个作为最左边 或最右边的点。 以下是代码:

import random
from operator import itemgetter
import numpy
import matplotlib
import matplotlib.pyplot

class Create_random_polygon:

def __init__(self, array, min_rand_coord = None, max_rand_coord = None, points_num = None):        
    self.array = array
    self.min_rand_coord = min_rand_coord 
    self.max_rand_coord = max_rand_coord
    self.points_num = points_num

def generate_random_points(self):
    random_coords_list = []
    for x in range(self.points_num):
        coords_tuple = (random.randint(self.min_rand_coord, self.max_rand_coord),
                        random.randint(self.min_rand_coord, self.max_rand_coord))
        random_coords_list.append(coords_tuple)
    self.array = random_coords_list
    return random_coords_list

def close_line_to_polygon(self):
    a = self.array[0]
    b = self.array[len(self.array)-1]
    if a == b:
        pass
    else:
        self.array.append(a)    

def find_leftmost_point(self):
    leftmost_point = None
    leftmost_x = None
    leftmost_y = None
    for point in self.array:
        x = point[0]
        y = point[1]
        if (leftmost_x == None) or (x < leftmost_x) or (x == leftmost_x and y < leftmost_y):
            leftmost_x = x
            leftmost_y = y
            leftmost_point = point
    return leftmost_point

def find_rightmost_point(self):
    rightmost_point = None
    rightmost_x = None
    rightmost_y = None
    for point in self.array:
        x = point[0]
        y = point[1]
        if (rightmost_x == None) or (x > rightmost_x) or (x == rightmost_x and y < rightmost_y ):
            rightmost_x = x
            rightmost_y = y
            rightmost_point = point
    return rightmost_point

def is_point_above_the_line(self, point, line_points):
    """return 1 if point is above the line
       return -1 if point is below the line
       return  0 if point is lays on the line"""
    px, py = point
    P1, P2 = line_points
    P1x, P1y = P1[0], P1[1]
    P2x, P2y = P2[0], P2[1]
    array = numpy.array([
        [P1x - px, P1y - py],
        [P2x - px, P2y - py],
        ])
    det = numpy.linalg.det(array)
    sign = numpy.sign(det)
    return sign

def sort_array_into_A_B_C(self, line_points):
    [(x_lm, y_lm), (x_rm, y_rm)] = line_points
    A_array, B_array, C_array = [], [], []
    for point in self.array:
        x, y = point
        sing = self.is_point_above_the_line( (x, y), line_points)
        if sing == 0:
            C_array.append(point)
        elif sing == -1:
            A_array.append(point)
        elif sing == 1:
            B_array.append(point)
    return A_array, B_array, C_array

def sort_and_merge_A_B_C_arrays(self, A_array, B_array, C_array):
    A_C_array = [*A_array, *C_array]
    A_C_array.sort(key=itemgetter(0))
    B_array.sort(key=itemgetter(0), reverse=True)        
    merged_arrays = [*A_C_array, *B_array]
    self.array = merged_arrays

def show_image(self, array, line_points, A_array, B_array, C_array):
    [(x_lm, y_lm), (x_rm, y_rm)] = line_points        
    x = [x[0] for x in array]
    y = [y[1] for y in array]
    Ax = [x[0] for x in A_array]
    Ay = [y[1] for y in A_array]
    Bx = [x[0] for x in B_array]
    By = [y[1] for y in B_array]
    Cx = [x[0] for x in C_array]
    Cy = [y[1] for y in C_array]          
    matplotlib.pyplot.plot(Ax, Ay, 'o', c='orange') # below the line
    matplotlib.pyplot.plot(Bx, By, 'o', c='blue') # above the line
    matplotlib.pyplot.plot(Cx, Cy, 'o', c='black') # on the line
    matplotlib.pyplot.plot(x_lm, y_lm, 'o', c='green') # leftmost point
    matplotlib.pyplot.plot(x_rm, y_rm, 'o', c='red') # rightmost point
    x_plot = matplotlib.pyplot.plot([x_lm, x_rm], [y_lm, y_rm], linestyle=':', color='black', linewidth=0.5) # polygon's division line
    x_plot = matplotlib.pyplot.plot(x, y, color='black', linewidth=1) # connect points by line in order of apperiance        
    matplotlib.pyplot.show()

def main(self, plot = False):
    'First output is random polygon coordinates array (other stuff for ploting)'
    print(self.array)
    if self.array == None:
        if not all(
            [isinstance(min_rand_coord, int),
             isinstance(max_rand_coord, int),
             isinstance(points_num, int),]
            ):
            print('Error! Values must be "integer" type:', 'min_rand_coord =',min_rand_coord, ', max_rand_coord =',max_rand_coord, ', points_num =',points_num)
        else:                
            self.array = self.generate_random_points()            

    print(self.array)
    x_lm, y_lm = self.find_leftmost_point()
    x_rm, y_rm = self.find_rightmost_point()
    line_points = [(x_lm, y_lm), (x_rm, y_rm)]

    A_array, B_array, C_array = self.sort_array_into_A_B_C(line_points)
    self.sort_and_merge_A_B_C_arrays(A_array, B_array, C_array)
    self.close_line_to_polygon()
    if plot:
        self.show_image(self.array, line_points, A_array, B_array, C_array)
    return self.array

if __name__ == "__main__":
# predefined polygon
 array = [ 
    (0, 0),
    (2, 2),
    (4, 4),
    (5, 5),
    (0, 5),        
    (1, 4),
    (4, 2),
    (3, 3),
    (2, 1),
    (5, 0),
    ]    
 #array = [(10, 20), (17, 5), (1, 16), (1, 14), (20, 8), (4, 7), (6, 9)]
 #array = [(1, 19), (12, 18), (10, 1), (1, 9), (5, 16), (10, 18), (2, 1)]
 #array = [(13, 17), (15, 3), (14, 13), (11, 8), (7, 16), (7, 7), (10, 15)] 
 array = None # no predefined polygon
 min_rand_coord = 1
 max_rand_coord = 10000
 points_num = 30

 crt = Create_random_polygon(array, min_rand_coord, max_rand_coord, points_num)
 polygon_array = crt.main(plot = True)  

【讨论】:

【参考方案11】:

Flutter 和 Dart 开发人员可以使用它。我正在使用它来修复用户选择的点以创建多边形。用户在地图上绘制多边形时,一般不会按顺序标注点。

示例结果: 左一用此法修正,右一未修正。

这是 Pawel 答案的 dart 实现;

      LatLng findCentroid(List<LatLng> points) 
        double x = 0;
        double y = 0;
        for (LatLng p in points) 
          x += p.latitude;
          y += p.longitude;
        
        LatLng center = new LatLng(0, 0);
        center.latitude = x / points.length;
        center.longitude = y / points.length;
        return center;
      
    
      List<LatLng> sortVerticies(List<LatLng> points) 
        // get centroid
        LatLng center = findCentroid(points);
    
        points.sort((a, b)
          double a1 = (radsToDegrees(Math.atan2(a.latitude - center.latitude, a.longitude - center.longitude)) + 360) % 360;
          double a2 = (radsToDegrees(Math.atan2(b.latitude - center.latitude, b.longitude - center.longitude)) + 360) % 360;
          return (a1 - a2).toInt();
        );
        return points;
      
    
      num radsToDegrees(num rad) 
        return (rad * 180.0) / Math.pi;
      

【讨论】:

以上是关于创建穿过所有给定点的非相交多边形的主要内容,如果未能解决你的问题,请参考以下文章

多边形相交的简单算法

不相交多边形中的点

用于查找从点到多边形的最小距离的 Javascript 代码(由 html 区域定义)

如何重绘一个完全自相交的多边形?

高效提取 MultiPolygon 中自相交特征生成的所有子多边形

如何在 Elasticsearch 中查找包含给定点的多边形