LoginSignup
8
6

More than 3 years have passed since last update.

Pythonで有界な(閉じた)ボロノイ図を計算・描画する

Posted at

はじめに

Pythonでボロノイ図を計算・描画するライブラリとしては,scipy.spatial.Voronoiscipy.spatial.voronoi_plot_2dが挙げられます.
これらについての詳細は,以下のサイトを参照してください.

ただし,これらのライブラリで作られるボロノイ図は,非有界な(閉じていない)空間上で計算されるので,外側のボロノイ辺やボロノイ領域(多角形)の情報を得ることができません.
そこで今回は,閉じた領域をボロノイ分割する方法を紹介します.

実装

ボロノイ領域を有界にする部分は,こちらの記事を参考にさせていただきました.
大まかな流れは以下の通りです.

  1. すべてのボロノイ領域を有界にするために,ダミーの母点を3個追加する.
  2. scipy.spatial.Voronoiでボロノイ図を計算する.
  3. 分割する領域と各ボロノイ領域の共通部分をshapelyで計算する.
  4. 3で求めた多角形を描画する.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from scipy.spatial import Voronoi, voronoi_plot_2d
from shapely.geometry import Polygon

def bounded_voronoi(bnd, pnts):
    """
    有界なボロノイ図を計算・描画する関数.
    """

    # すべての母点のボロノイ領域を有界にするために,ダミー母点を3個追加
    gn_pnts = np.concatenate([pnts, np.array([[100, 100], [100, -100], [-100, 0]])])

    # ボロノイ図の計算
    vor = Voronoi(gn_pnts)

    # 分割する領域をPolygonに
    bnd_poly = Polygon(bnd)

    # 各ボロノイ領域をしまうリスト
    vor_polys = []

    # ダミー以外の母点についての繰り返し
    for i in range(len(gn_pnts) - 3):

        # 閉空間を考慮しないボロノイ領域
        vor_poly = [vor.vertices[v] for v in vor.regions[vor.point_region[i]]]
        # 分割する領域をボロノイ領域の共通部分を計算
        i_cell = bnd_poly.intersection(Polygon(vor_poly))

        # 閉空間を考慮したボロノイ領域の頂点座標を格納
        vor_polys.append(list(i_cell.exterior.coords[:-1]))


    # ボロノイ図の描画
    fig = plt.figure(figsize=(7, 6))
    ax = fig.add_subplot(111)

    # 母点
    ax.scatter(pnts[:,0], pnts[:,1])

    # ボロノイ領域
    poly_vor = PolyCollection(vor_polys, edgecolor="black",
                              facecolors="None", linewidth = 1.0)
    ax.add_collection(poly_vor)

    xmin = np.min(bnd[:,0])
    xmax = np.max(bnd[:,0])
    ymin = np.min(bnd[:,1])
    ymax = np.max(bnd[:,1])

    ax.set_xlim(xmin-0.1, xmax+0.1)
    ax.set_ylim(ymin-0.1, ymax+0.1)
    ax.set_aspect('equal')

    plt.show()

    return vor_polys

単位正方形を分割

以上の関数を使って単位正方形を分割すると,次のようになります.

# ボロノイ分割する領域
bnd = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])

# 母点の個数
n = 30
# 母点座標
pnts = np.random.rand(n, 2)

# ボロノイ図の計算・描画
vor_polys = bounded_voronoi(bnd, pnts)

voronoi_sq.png

一般の凸多角形を分割

正方形ではない一般の凸多角形も同様に分割できます.

from scipy.spatial import ConvexHull

def points_in_convex_polygon(bnd, n):
    """
    凸多角形の内部にn個の点をランダムに発生させる関数.
    """

    #領域の境界を表す行列の作成
    bndhull = ConvexHull(bnd)
    bndTmp = bndhull.equations
    bndMat = np.matrix(bndTmp)
    Abnd = np.array(bndMat[:,0:2])
    bbnd = np.array(bndMat[:,2])

    # 領域を囲む長方形
    xmin = np.min(bnd[:,0])
    xmax = np.max(bnd[:,0])
    ymin = np.min(bnd[:,1])
    ymax = np.max(bnd[:,1])

    # 繰り返し用
    i = 0
    pnts = []

    while i < n:

        # 点を生成
        pnt = np.random.rand(2)
        pnt[0] = xmin + (xmax - xmin) * pnt[0]
        pnt[1] = ymin + (ymax - ymin) * pnt[1]

        # 点が凸多角形内にあれば
        if (np.round(np.dot(Abnd,pnt.transpose()),7) <= np.round(-bbnd.transpose(),7)).all():

            pnts.append(pnt.tolist())
            i += 1

    return np.array(pnts)


# ボロノイ分割する領域
bnd = np.array([[0.1,0.4],[0.3,0.2],[0.8,0.3],[0.9,0.6],[0.7,0.7],[0.4,0.7],[0.2,0.6]])

# 母点の個数
n = 10
# 母点座標
pnts = points_in_convex_polygon(bnd, n)

# ボロノイ図の計算・描画
vor_polys = bounded_voronoi(bnd, pnts)

voronoi.png

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6