用 Python 把矢量等高线转成栅格网格

用 Python 把矢量等高线转成栅格网格——顺便聊聊乐高地形模型

空间数据主要有矢量栅格两种形式。矢量擅长精确描述点、线、面,比如城市边界、道路或等高线;栅格则用规则的网格像素来表示,每个像素带一个值,常用于高程、温度之类的连续数据。实际分析时,经常需要把两者结合起来,比如把矢量等高线转成高程栅格。

今天拿杭州为例,演示怎么用 Python 把等高线(矢量)转成规则网格(栅格),最后再离散化成几个高度级别——这样就能想象用不同颜色的乐高砖来搭一个迷你杭州地形模型了。整个过程主要靠 GeoPandas 和 OSMnx,代码简单,几分钟就能跑通。

数据准备

杭州公开的等高线数据不太好找,我这里用一个常见的替代方案:从公开 DEM(数字高程模型)生成等高线,或者直接用开源高程数据。这里为了演示方便,我们先用 OSMnx 下载杭州市行政边界,然后假设有一份等高线数据(实际项目可以从自然资源部门或开源平台获取 DEM 后生成)。

先看边界:

import osmnx as ox
import matplotlib.pyplot as plt

# 下载杭州市行政边界
city = ox.geocode_to_gdf('杭州市, 中国')
city = city.to_crs(epsg=4326)

# 快速看一眼
city.plot(figsize=(10, 10), edgecolor='black')
plt.title('杭州市行政边界')
plt.axis('off')
plt.show()

结果就是熟悉的杭州轮廓,西湖、钱塘江一览无余。

image-20260105012344427

生成规则网格

接下来,把这个不规则的行政区切成规则方格网格。这里写个小函数,根据边界框分成指定行列数的格子:

from shapely.geometry import box
import geopandas as gpd

def create_grid(bounds, rows, cols):
    minx, miny, maxx, maxy = bounds
    width = (maxx - minx) / cols
    height = (maxy - miny) / rows
    grid = []
    for i in range(cols):
        for j in range(rows):
            x1 = minx + i * width
            y1 = miny + j * height
            x2 = x1 + width
            y2 = y1 + height
            grid.append(box(x1, y1, x2, y2))
    return gpd.GeoDataFrame(grid, columns=['geometry'], crs='EPSG:4326')

bounds = city.total_bounds  # [minx, miny, maxx, maxy]
gdf_grid = create_grid(bounds, 30, 30)  # 30x30 的网格,可自行调整

# 叠加显示
ax = city.plot(figsize=(12, 10), color='none', edgecolor='black', linewidth=2)
gdf_grid.plot(ax=ax, color='none', edgecolor='gray', alpha=0.6)
plt.title('杭州边界 + 30x30 网格')
plt.axis('off')
plt.show()

网格均匀铺在杭州边界上,后续我们会把每个格子填充平均高程值。

把等高线值映射到网格

假设我们有一份等高线 GeoDataFrame(列名里有 ‘elevation’ 表示高度值),叫 gdf_contours

实际操作中可以用空间连接(sjoin)找出每个网格包含哪些等高线,然后取平均值:

# 空间连接:网格内包含的等高线部分
joined = gpd.sjoin(gdf_contours, gdf_grid, how='inner', predicate='intersects')

# 按网格索引聚合,取平均高程
aggregated = joined.groupby('index_right')['elevation'].mean().reset_index()

# 合并回网格
gdf_grid = gdf_grid.reset_index().rename(columns={'index': 'grid_id'})
gdf_grid = gdf_grid.merge(aggregated, left_on='grid_id', right_on='index_right', how='left')

gdf_grid.head()

现在每个格子都有一个平均高程值了。如果某些格子没交到等高线,会是 NaN,可以填 0 或附近值。

离散化高度级别(为乐高做准备)

乐高砖高度有限制,我们把连续高程转成几个离散级别,比如 0~6 级,方便用不同层数的砖表示:

import numpy as np

# 先填补空值
gdf_grid['elevation'] = gdf_grid['elevation'].fillna(0)

# 简单线性离散化到 0-8 级
min_val = gdf_grid['elevation'].min()
max_val = gdf_grid['elevation'].max()
gdf_grid['level'] = np.floor(8 * (gdf_grid['elevation'] - min_val) / (max_val - min_val + 1e-6))
gdf_grid['level'] = gdf_grid['level'].clip(0, 7)  # 限制在 0-7

print(gdf_grid['level'].min(), gdf_grid['level'].max())

可视化结果

最后画出来看看效果,用 terrain 色带更直观:

ax = gdf_grid.plot(column='level', figsize=(12, 10), cmap='terrain', 
                   edgecolor='gray', linewidth=0.3, legend=True)

city.plot(ax=ax, color='none', edgecolor='black', linewidth=1.5)

plt.title('杭州地形高度离散网格(乐高风格预览)')
plt.axis('off')
plt.show()

西部天目山方向颜色深(高),东部平原浅(低),西湖位置清晰可见。如果网格再密点,效果会更好。

用乐高搭出来?

有了这个离散网格,理论上就能按格子堆乐高:每个格子对应一块底板,高度用不同层数的砖表示,颜色也按级别换(绿色低地、棕色高山)。打印底板网格,动手搭一个迷你杭州地形模型,放在桌上多有意思!

这个过程其实展示了矢量到栅格的核心思路:空间叠加 + 统计聚合。全靠 GeoPandas,几行代码就搞定,比传统 GIS 软件轻量多了。以后做地形分析、洪水模拟、视线分析时,这个技巧经常能用上。

有兴趣的可以换成重庆(山城地形更夸张)或者成都试试,效果绝对不一样。欢迎留言交流你的结果~