前言
生成Voronoi图,有2个库是最常用的:scipy和geovoronoi。
scipy
可能是因为我用的是实际的地图数据,边界比较复杂,用这个库老是会有很多小bug,比如边缘会有部分面积没有被划入任何一块区域。和AI对话了好多轮,还是没有完全搞定。
和AI的部分对话截取如下:


最后AI都劝我用geovoronoi了。

geovoronoi
这个库是更专业的,事实证明和AI迭代了几轮之后就满足需求了,也是我最终采用的。
实际区域行政边界数据
以汕尾市为例,数据来源。界面如下,可以直接下载json格式的文件。

json文件直接喂给Deepseek就可以解读出来:
json
"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回答如下:

程序
采用geovoronoi库的代码如下。
核心逻辑:1、读取json文件的边界;2、随机生成50个点;3、画图。其中第2步可以替换为给定的经纬值。
python
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按照下面的格式生成数组,就可以套用上面的程序了。
python
[<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就可以了,真的是相当方便了。

后记
-
AI真的是太强大了,必须充分利用起来。
-
感觉chatgpt还是要好用一点。
-
好想有一个无限制的账号...