web-dev-qa-db-ja.com

指定されたすべてのポイントを通過する交差しないポリゴンを作成します

ランダムな順序で点の配列があり、点のallとその辺を通過するポリゴンを(隣接するすべてのペアが辺を表すように並べ替えることによって)見つける必要があるとします。もちろん、交差していません。

ポイントを選択し、その下にある最終的な配列にすべてのポイントを追加して、左から右に並べ替えてみました。次に、その上にあるすべてのポイントを追加し、右から左に並べ替えます。

自己交差を避けるために、ポイントを追加して自然に並べ替えることができると言われました。しかし、それを理解することはできません。これを行う簡単な方法は何ですか?

22
Max

誰かが言ったように、最小の長さの解決策はまさに巡回セールスマン問題です。これは、最適ではありませんが実行可能なアプローチです。

ポイントの Delauney三角形分割 を計算します。すべてのポイントを補間する境界が残るか、それ以上セグメントを削除できなくなるまで、境界セグメントを連続して削除します。そのセグメントを使用する三角形のすべてのポイントが境界上にある場合は、境界セグメントを削除しないでください。この境界をあなたの道としてください。

私はこれをMathematicaで40個のランダムな点を使って実装しました。典型的な結果は次のとおりです。 enter image description here

明らかな反対意見は、すべてのポイントが境界ポイントであるとは限らないポイントに到達する可能性があることですが、境界を自己交差させずに境界セグメントを削除することはできません。これは正当な異議です。これが発生したケースを確認するのに数十回の実行が必要でしたが、最終的にこのケースが発生しました。 enter image description here

ローカルトポロジを使用してこれを修正するいくつかの明白な方法をおそらく見ることができますが、詳細はあなたに任せます!役立つかもしれない1つのことは、辺を共有する2つの三角形、たとえば三角形(p、q、r)と(q、p、s)を取り、それらを(r、p、s)と( r、s、q)(すべての座標は三角形の周りを反時計回りに)。これは、この変換で結果として得られる三角形も反時計回りに順序付けられている限り実行できます。

修正の必要性を減らすために、各ステップで削除するセグメントを適切に選択する必要があります。境界セグメントの長さと、候補三角形(セグメントとの潜在的な入力ポイントによって形成される三角形)の反対側の長さの合計の比率を使用しました。

8

私たちの戦略は、ポリゴンにすべてのポイントが含まれていることを確認し、どの線も交差しない場所でそれらを接続する順序を見つけることができる計画を立てることです。

アルゴリズム

  1. 左端の点を見つけるp
  2. 右端の点を見つけるq
  3. ポイントをA(pqより下のポイントのセット)とB(pqより上のポイントのセット)に分割します[(p、q、?)の左折テストを使用して、ポイントが線より上にあるかどうかを判断できます]。
  4. Aをx座標で並べ替える(増加)
  5. Bをx座標(減少)で並べ替えます。
  6. 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の最初のポイントまでの最初のエッジは、どのセグメントとも交差できません(セグメントがまだないため)。 Aのポイントをx座標で順番に進むと、各ポイントから次のセグメントが右に移動し、前のすべてのセグメントが左に移動します。したがって、pからAのすべての点を通り、点qに移動するとき、交差はありません。

QからBのポイントを経由して戻る場合も同じです。これらのセグメントは右から左に進むため、互いに交差することはできません。 Aのすべての点が線pqの下にあり、Bのすべての点がこの線より上にあるため、これらのセグメントもAの何とも交差できません。

したがって、セグメントが互いに交差することはなく、単純なポリゴンがあります。

ソース: リンク切れ

25
bdean20

これがpython 3.6コード(必要なライブラリ:matplotlib、numpy) bdean2 's answerhttps://i.stack.imgur.com/ilfFA.jpg

写真の説明:

  • 左上-事前定義されたポリゴン、他のポリゴンはランダムに生成されます。
  • 点線-緑(左端)と赤(右端)のポリゴンのポイントを接続します。
  • 黒い点は点線上にあります。
  • オレンジ色の点が点線の下にあります。
  • 青い点は点線の上にあります。

=========

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)    

==========

5
Mr. Che

あなたが探しているものは、文献では単純な多角化と呼ばれています。たとえば、このトピックについては このWebページ を参照してください。 Miguelが言うように、 星型 多角形を生成するのは簡単ですが、たとえば、Axel Kemperが言及するように、最小のTSPである最小の周囲の多角形を見つけるのは困難です。一般に、特定のポイントセットには指数関数的な数の異なるポリゴン化があります。


 Four point polygonization

星型のポリゴン化の場合、注意が必要な問題が1つあります。余分な点x(星の「カーネル」内)はいけません。既存のポイントと一致します!これがxを保証する1つのアルゴリズムです。最も近い点のペア(a、b)を見つけ、xセグメントabの中点になります。次に、Miguelの投稿に従って続行します。

3
Joseph O'Rourke

さて、実際に最小性などを気にしない場合は、凸包の内側に新しいポイントを配置してから、この新しいポイントに対して角度で他のポイントを並べ替えることができます。交差しないポリゴンを取得します。

2
Miguel

私はこれとまったく同じ問題を抱えていて、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]))
1
Pawel Pieczul

2つのセグメントが交差するかどうかのテストは簡単で高速です。たとえば、 that を参照してください。

これで、ポリゴンを繰り返し作成できます。

ソースポイント:S = {S0, ... Si, Sj,...}

最終ポリゴン:A = {A0, ... Ai, Aj,...}

SfullとAemptyから始めます。

Sの最初の3ポイントを取り、それらをAに移動します。もちろん、この三角形は自己交差していません。

次に、Sが空になるまで、最初の残りのポイントを取得します。これをPと呼び、Aで挿入できる位置を探します。

この位置はi+1最初のiは、どちらも[Ai-P]または[Ai+1-P]他のセグメントと交差します[Ak-Ak+1]

したがって、新しいポリゴンA{A0, ... Ai, P, Ai+1, ...}

1
Denys Séguret

私はあなたがあなたの問題を解決するために グラハムスキャン アルゴリズムを使うことができると信じています。

編集:一般的に、 凸包アルゴリズム は注目すべきものです。

0

これが私のTypeScript実装です Pawel Pieczul 's answer これは単純なポリゴンを含む私のユースケースで完全に機能しました:

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[]) => {
    let center = getCentroid(points)
    return points.slice().sort((a: Point, b: Point) => {
        let angleA = Math.atan2(a.y - center.y, a.x - center.x)
        let angleB = Math.atan2(b.y - center.y, b.x - center.x)
        return angleA - angleB
    })
}
0
Damien