前言

生成Voronoi圖,有2個庫是最常用的:scipygeovoronoi

scipy

可能是因為我用的是實際的地圖數據,邊界比較複雜,用這個庫老是會有很多小bug,比如邊緣會有部分面積沒有被劃入任何一塊區域。和AI對話了好多輪,還是沒有完全搞定。

和AI的部分對話截取如下:

Python也能繪製藝術畫?這裏有一個完整教程 -_ci


Python也能繪製藝術畫?這裏有一個完整教程 -_ci_02

最後AI都勸我用geovoronoi了。

Python也能繪製藝術畫?這裏有一個完整教程 -_ci_03

geovoronoi

這個庫是更專業的,事實證明和AI迭代了幾輪之後就滿足需求了,也是我最終採用的。

實際區域行政邊界數據

以汕尾市為例,數據來源。界面如下,可以直接下載json格式的文件。

Python也能繪製藝術畫?這裏有一個完整教程 -_數據_04

json文件直接餵給Deepseek就可以解讀出來:

"properties": {
    "adcode": 441500,           // 中國行政區劃代碼,441500 唯一代表汕尾市
    "name": "汕尾市",           // 行政區名稱
    "center": [115.364238, 22.774485], // 行政中心座標(經緯度)
    "centroid": [115.53778, 23.004558], // 幾何中心座標(經緯度)
    "childrenNum": 4,           // 下屬子區域數量(指市轄區、縣級市的數量)
    "level": "city",            // 行政區級別(城市)
    "acroutes": [100000, 440000], // 行政路徑(100000代表中國,440000代表廣東省)
    "parent": {                 // 上級行政區
        "adcode": 440000        // 上級行政區代碼(440000 是廣東省)
    }
}

"geometry": {
    "type": "MultiPolygon",     // 幾何類型:多重多邊形
    "coordinates": [ ... ]       // 定義多邊形輪廓的座標點數組
}

一個小問題

汕尾市的數據除了陸地上的主體,還有一些小島嶼,也包含在了json文件裏,我實際上用的時候把島嶼的數據(也就是coordinates裏面排在後面的幾個數組)給刪掉了。

截取AI回答如下:

Python也能繪製藝術畫?這裏有一個完整教程 -_ci_05

程序

採用geovoronoi庫的代碼如下。

核心邏輯:1、讀取json文件的邊界;2、隨機生成50個點;3、畫圖。其中第2步可以替換為給定的經緯值。

import json
import random
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry import shape, Point
from shapely.ops import unary_union
import geopandas as gpd
from geovoronoi import voronoi_regions_from_coords

# ---------------------------
# 1. 讀取汕尾市行政邊界 JSON
# ---------------------------
with open("shanwei.json", "r", encoding="utf-8") as f:
    data = json.load(f)

# 兼容多 Polygon 情況
geoms = []
for feature in data["features"]:
    geom = shape(feature["geometry"])
    geoms.append(geom)
boundary = unary_union(geoms)

# ---------------------------
# 2. 在邊界內隨機生成 50 個點
# ---------------------------
def generate_random_points_within(polygon, num_points):
    points = []
    minx, miny, maxx, maxy = polygon.bounds
    while len(points) < num_points:
        p = Point(random.uniform(minx, maxx), random.uniform(miny, maxy))
        if polygon.contains(p):
            points.append(p)
    return points

points = generate_random_points_within(boundary, 50)

# ---------------------------
# 3. 使用 geovoronoi 生成 Voronoi 區域
# ---------------------------
coords = np.array([(p.x, p.y) for p in points])
region_polys, region_pts = voronoi_regions_from_coords(coords, boundary)

# ---------------------------
# 4. 隨機生成顏色
# ---------------------------
colors = []
for _ in region_polys:
    # RGB 取值範圍 0~1
    colors.append((random.random(), random.random(), random.random()))

# ---------------------------
# 5. 繪製結果
# ---------------------------
fig, ax = plt.subplots(figsize=(8, 8))

# 繪製 Voronoi 區域,每個區域一個顏色
for poly, color in zip(region_polys.values(), colors):
    gpd.GeoSeries([poly]).plot(ax=ax, facecolor=color, edgecolor='black', alpha=0.7)

# 繪製隨機點
ax.plot(coords[:, 0], coords[:, 1], 'ro', markersize=3, label="Random Points")

# 繪製邊界
gpd.GeoSeries([boundary]).plot(ax=ax, edgecolor='blue', facecolor='none', linewidth=1)

plt.title("Voronoi Diagram within Shanwei Boundary (Random Colors)", fontsize=14)
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.axis('equal')
plt.legend()
plt.show()

print(points)

本來想讓AI再生成指定50個數據點而非隨機的版本,無奈對話次數達到上限了。

不過這個需求也不復雜,輸出程序裏的points,格式如下。直接給50對經緯值,然後讓AI按照下面的格式生成數組,就可以套用上面的程序了。

[<POINT (115.395 23.1)>, <POINT (115.675 23.207)>, <POINT (115.469 23.175)>, <POINT (115.72 23.296)>, <POINT (115.736 23.086)>, <POINT (116.056 22.853)>, <POINT (115.877 22.985)>, <POINT (114.987 22.784)>, <POINT (115.582 23.246)>, <POINT (115.841 22.763)>, <POINT (115.868 23.011)>, <POINT (115.299 22.843)>, <POINT (115.412 23.12)>, <POINT (115.388 23.011)>, <POINT (115.766 22.894)>, <POINT (114.969 22.836)>, <POINT (115.091 22.897)>, <POINT (115.633 22.989)>, <POINT (115.823 22.844)>, <POINT (115.773 23.124)>, <POINT (114.941 22.867)>, <POINT (115.467 23.112)>, <POINT (115.009 22.917)>, <POINT (115.534 22.678)>, <POINT (115.409 22.803)>, <POINT (115.359 22.982)>, <POINT (115.455 23.266)>, <POINT (115.334 23.141)>, <POINT (115.586 22.968)>, <POINT (115.747 23.206)>, <POINT (115.428 23.112)>, <POINT (115.817 23.257)>, <POINT (115.598 23.198)>, <POINT (115.374 23.035)>, <POINT (115.288 22.903)>, <POINT (115.829 23.05)>, <POINT (114.981 22.893)>, <POINT (115.899 22.806)>, <POINT (115.507 22.835)>, <POINT (115.984 22.878)>, <POINT (115.156 22.895)>, <POINT (115.561 23.259)>, <POINT (115.719 23.11)>, <POINT (115.566 23.194)>, <POINT (115.444 22.85)>, <POINT (115.362 22.801)>, <POINT (115.352 23.047)>, <POINT (115.721 23.037)>, <POINT (115.857 22.995)>, <POINT (115.988 22.832)>]

程序運行效果如下,看起來可以滿足需求,就先這樣吧。

如果有樣式上調整的需求,直接告訴AI就可以了,真的是相當方便了。

Python也能繪製藝術畫?這裏有一個完整教程 -_#python_06

後記

  • AI真的是太強大了,必須充分利用起來。
  • 感覺chatgpt還是要好用一點。
  • 好想有一個無限制的賬號…