






















缓冲区分析和几何简化是 GIS 空间分析中最常用的两类操作。缓冲区用于生成要素周围的影响范围区域,而简化用于降低几何复杂度以提高渲染和计算性能。本章将全面介绍 GeoPandas 中缓冲区与简化的相关方法。
缓冲区(Buffer)是指围绕几何对象一定距离内的所有点组成的区域。缓冲区是空间分析中最基础、最常用的操作之一。
常见应用场景:
| 应用场景 | 说明 |
|---|---|
| 服务区分析 | 医院、学校等设施的服务覆盖范围 |
| 影响范围分析 | 污染源、噪声源的影响区域 |
| 安全距离 | 道路、铁路两侧的安全隔离带 |
| 邻近分析 | 查找距离某要素一定范围内的其他要素 |
| 数据融合 | 通过缓冲区合并相近的要素 |
对于几何对象 G 和距离 d,缓冲区定义为:
Buffer(G, d) = { p | distance(p, G) ≤ d }
即所有与 G 的距离不超过 d 的点的集合。
GeoSeries.buffer(distance, resolution=16, cap_style='round',
join_style='round', mitre_limit=5.0, single_sided=False)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
distance |
float | 必需 | 缓冲距离,正值向外扩展,负值向内收缩 |
resolution |
int | 16 | 圆弧近似的线段数(每个四分之一圆弧) |
cap_style |
str | 'round' | 线端头样式:'round'、'flat'、'square' |
join_style |
str | 'round' | 连接处样式:'round'、'mitre'、'bevel' |
mitre_limit |
float | 5.0 | mitre 连接的最大延伸比例 |
single_sided |
bool | False | 是否生成单侧缓冲区 |
import geopandas as gpd
from shapely.geometry import Point, LineString, Polygon
# 点缓冲区 → 圆
point = Point(0, 0)
gs = gpd.GeoSeries([point])
buffer_point = gs.buffer(1.0)
print(f"点缓冲区面积: {buffer_point.iloc[0].area:.4f}") # ≈ π
# 线缓冲区 → 带状区域
line = LineString([(0, 0), (5, 0)])
gs_line = gpd.GeoSeries([line])
buffer_line = gs_line.buffer(1.0)
print(f"线缓冲区面积: {buffer_line.iloc[0].area:.4f}") # ≈ 5*2 + π
# 多边形缓冲区 → 扩大的多边形
poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
gs_poly = gpd.GeoSeries([poly])
buffer_poly = gs_poly.buffer(0.5)
print(f"多边形缓冲区面积: {buffer_poly.iloc[0].area:.4f}")
resolution 控制圆弧的平滑度:
import geopandas as gpd
from shapely.geometry import Point
point = Point(0, 0)
gs = gpd.GeoSeries([point])
for res in [1, 4, 8, 16, 32, 64]:
buf = gs.buffer(1.0, resolution=res)
vertices = len(buf.iloc[0].exterior.coords)
area = buf.iloc[0].area
print(f"resolution={res:2d}: 顶点数={vertices:3d}, 面积={area:.6f} (π≈3.141593)")
正缓冲距离使几何对象向外扩展:
import geopandas as gpd
from shapely.geometry import Polygon
poly = Polygon([(0, 0), (4, 0), (4, 3), (0, 3)])
gs = gpd.GeoSeries([poly])
# 不同距离的正缓冲区
for d in [0.5, 1.0, 2.0]:
buf = gs.buffer(d)
print(f"缓冲距离 {d}: 面积从 {poly.area:.1f} 增加到 {buf.iloc[0].area:.2f}")
负缓冲距离使多边形向内收缩:
poly = Polygon([(0, 0), (10, 0), (10, 8), (0, 8)])
gs = gpd.GeoSeries([poly])
for d in [-0.5, -1.0, -2.0, -5.0]:
buf = gs.buffer(d)
if buf.iloc[0].is_empty:
print(f"缓冲距离 {d}: 几何消失(收缩过度)")
else:
print(f"缓冲距离 {d}: 面积从 {poly.area:.1f} 减少到 {buf.iloc[0].area:.2f}")
cap_style 控制线段端头的缓冲区形状:
| 值 | 说明 |
|---|---|
'round' |
半圆形端头(默认) |
'flat' |
平头,缓冲区在端点处截断 |
'square' |
方形端头,延伸距离等于缓冲距离 |
import geopandas as gpd
from shapely.geometry import LineString
line = LineString([(0, 0), (5, 0)])
gs = gpd.GeoSeries([line])
for style in ['round', 'flat', 'square']:
buf = gs.buffer(1.0, cap_style=style)
print(f"cap_style='{style}': 面积={buf.iloc[0].area:.4f}")
# round: 面积 ≈ 13.14 (5×2 + π)
# flat: 面积 ≈ 10.00 (5×2)
# square: 面积 ≈ 14.00 (7×2)
join_style 控制缓冲区在折角处的连接方式:
| 值 | 说明 |
|---|---|
'round' |
圆角连接(默认) |
'mitre' |
尖角连接 |
'bevel' |
斜面连接 |
import geopandas as gpd
from shapely.geometry import LineString
# 直角折线
line = LineString([(0, 0), (2, 0), (2, 2)])
gs = gpd.GeoSeries([line])
for style in ['round', 'mitre', 'bevel']:
buf = gs.buffer(0.5, join_style=style)
print(f"join_style='{style}': 面积={buf.iloc[0].area:.4f}")
当使用 join_style='mitre' 时,mitre_limit 控制尖角的最大延伸距离。如果尖角超过限制,会自动退化为 bevel 连接。
# 锐角折线
sharp_line = LineString([(0, 0), (2, 0), (0, 0.5)])
gs = gpd.GeoSeries([sharp_line])
for limit in [1.0, 2.0, 5.0, 10.0]:
buf = gs.buffer(0.5, join_style='mitre', mitre_limit=limit)
print(f"mitre_limit={limit}: 面积={buf.iloc[0].area:.4f}")
single_sided=True 生成线的单侧缓冲区。正距离在线的左侧生成缓冲区,负距离在右侧。
import geopandas as gpd
from shapely.geometry import LineString
line = LineString([(0, 0), (5, 0)])
gs = gpd.GeoSeries([line])
# 左侧缓冲区(正距离)
left_buf = gs.buffer(1.0, single_sided=True)
print(f"左侧缓冲区: {left_buf.iloc[0].bounds}") # y 范围 0-1
# 右侧缓冲区(负距离)
right_buf = gs.buffer(-1.0, single_sided=True)
print(f"右侧缓冲区: {right_buf.iloc[0].bounds}") # y 范围 -1-0
# 生成道路两侧的人行道区域
road = LineString([(0, 0), (100, 0), (100, 50)])
gs = gpd.GeoSeries([road])
# 左侧人行道(宽2米)
left_sidewalk = gs.buffer(2, single_sided=True)
# 右侧人行道(宽2米)
right_sidewalk = gs.buffer(-2, single_sided=True)
simplify() 使用 Douglas-Peucker 算法简化几何对象,减少顶点数量同时尽量保持原始形状。
GeoSeries.simplify(tolerance, preserve_topology=True)
| 参数 | 说明 |
|---|---|
tolerance |
简化容差,距离阈值 |
preserve_topology |
是否保持拓扑有效性 |
import geopandas as gpd
from shapely.geometry import LineString
# 复杂的曲线
import numpy as np
t = np.linspace(0, 4*np.pi, 200)
x = t
y = np.sin(t)
complex_line = LineString(zip(x, y))
gs = gpd.GeoSeries([complex_line])
print(f"原始顶点数: {len(complex_line.coords)}")
for tol in [0.01, 0.05, 0.1, 0.5, 1.0]:
simplified = gs.simplify(tol)
vertices = len(simplified.iloc[0].coords)
print(f"容差 {tol}: 顶点数={vertices}")
from shapely.geometry import Polygon
# 复杂多边形
poly = Polygon([(0,0),(1,0),(1,0.5),(2,0.5),(2,0),(3,0),(3,2),(0,2)])
gs = gpd.GeoSeries([poly])
# 保持拓扑(默认)
simplified_topo = gs.simplify(0.6, preserve_topology=True)
print(f"保持拓扑: 有效={simplified_topo.iloc[0].is_valid}")
# 不保持拓扑(可能产生自交叉)
simplified_no_topo = gs.simplify(0.6, preserve_topology=False)
print(f"不保持拓扑: 有效={simplified_no_topo.iloc[0].is_valid}")
simplify_coverage() 在简化几何的同时保持相邻多边形之间的拓扑关系(共享边界一致性)。这对于行政区划等覆盖数据特别重要。
import geopandas as gpd
from shapely.geometry import Polygon
# 两个相邻多边形
gs = gpd.GeoSeries([
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
])
# 覆盖简化
simplified = gs.simplify_coverage(tolerance=0.1)
# 共享边会被一致地简化,不会产生间隙或重叠
offset_curve() 生成与原始线平行偏移的线。正距离向左偏移,负距离向右偏移。
import geopandas as gpd
from shapely.geometry import LineString
line = LineString([(0, 0), (5, 0), (5, 5)])
gs = gpd.GeoSeries([line])
# 左偏移
left_offset = gs.offset_curve(1.0)
print(f"左偏移: {left_offset.iloc[0]}")
# 右偏移
right_offset = gs.offset_curve(-1.0)
print(f"右偏移: {right_offset.iloc[0]}")
# join_style 控制折角处理
for style in ['round', 'mitre', 'bevel']:
offset = gs.offset_curve(1.0, join_style=style)
print(f"join_style='{style}': {offset.iloc[0].geom_type}")
# 生成道路标线
road_center = LineString([(0, 0), (100, 0)])
gs = gpd.GeoSeries([road_center])
# 车道分割线
lane_marking = gs.offset_curve(1.75) # 单车道宽3.5m,中心偏移1.75m
segmentize() 在几何对象上添加额外的顶点,确保任意两个相邻顶点之间的距离不超过指定值。
import geopandas as gpd
from shapely.geometry import LineString
# 只有两个顶点的长线段
line = LineString([(0, 0), (100, 0)])
gs = gpd.GeoSeries([line])
print(f"原始顶点数: {len(line.coords)}") # 2
# 最大间距 10
segmented = gs.segmentize(max_segment_length=10)
print(f"分段后顶点数: {len(segmented.iloc[0].coords)}") # 11
# 最大间距 25
segmented2 = gs.segmentize(max_segment_length=25)
print(f"分段后顶点数: {len(segmented2.iloc[0].coords)}") # 5
# 坐标转换前增加顶点密度以提高精度
import geopandas as gpd
# 大范围线(如国界线)在投影转换时可能变形
boundaries = gpd.read_file("boundaries.shp")
# 在转换前增加顶点密度
dense = boundaries.copy()
dense['geometry'] = boundaries.segmentize(max_segment_length=0.1) # 度
# 然后进行投影转换
dense_proj = dense.to_crs(epsg=32650)
remove_repeated_points() 移除几何对象中距离在指定容差内的重复顶点。
import geopandas as gpd
from shapely.geometry import LineString
# 包含近似重复点的线
line = LineString([
(0, 0), (1, 0), (1.001, 0.001), # 第3个点与第2个点很近
(2, 0), (3, 0), (3, 0.0001), # 第6个点与第5个点几乎重合
(4, 0)
])
gs = gpd.GeoSeries([line])
print(f"原始顶点数: {len(line.coords)}") # 7
# 移除容差 0.01 内的重复点
cleaned = gs.remove_repeated_points(tolerance=0.01)
print(f"清理后顶点数: {len(cleaned.iloc[0].coords)}")
# 清理 GPS 轨迹中的停留点(几乎不动的点)
tracks = gpd.read_file("gps_tracks.shp")
# 移除 1 米内的重复点
cleaned = tracks.copy()
cleaned['geometry'] = tracks.remove_repeated_points(tolerance=1.0)
original_count = sum(len(g.coords) for g in tracks.geometry)
cleaned_count = sum(len(g.coords) for g in cleaned.geometry)
print(f"从 {original_count} 个点减少到 {cleaned_count} 个点")
import geopandas as gpd
from shapely.geometry import Point
# 医院位置
hospitals = gpd.GeoDataFrame({
'name': ['市第一医院', '市第二医院', '市第三医院'],
'level': ['三甲', '三乙', '二甲'],
'geometry': [Point(500000, 3500000), Point(502000, 3501000), Point(498000, 3499000)]
}, crs='EPSG:32650')
# 不同等级医院不同服务半径
service_radius = {'三甲': 5000, '三乙': 3000, '二甲': 2000}
hospitals['service_area'] = hospitals.apply(
lambda row: row.geometry.buffer(service_radius[row['level']]),
axis=1
)
# 计算服务覆盖率
service_gdf = hospitals.set_geometry('service_area')
total_coverage = service_gdf.union_all()
print(f"总服务覆盖面积: {total_coverage.area/1e6:.2f} 平方公里")
# 生成多环缓冲区(等值线效果)
center = Point(500000, 3500000)
rings = []
distances = [1000, 2000, 3000, 5000, 8000]
for i, d in enumerate(distances):
if i == 0:
ring = center.buffer(d)
else:
ring = center.buffer(d).difference(center.buffer(distances[i-1]))
rings.append({'distance': d, 'geometry': ring})
ring_gdf = gpd.GeoDataFrame(rings, crs='EPSG:32650')
# 分析工厂污染影响范围
factories = gpd.read_file("factories.shp")
residential = gpd.read_file("residential.shp")
# 500米影响范围
impact_zones = factories.buffer(500)
# 查找受影响的居民区
affected = residential[residential.intersects(impact_zones.union_all())]
print(f"受影响居民区: {len(affected)} 个")
import geopandas as gpd
from shapely.geometry import Polygon
import numpy as np
poly = Polygon([(0,0),(1,0.1),(2,0),(2.1,1),(2,2),(1,1.9),(0,2),(-0.1,1)])
gs = gpd.GeoSeries([poly])
for tol in [0.05, 0.1, 0.2, 0.5]:
simplified = gs.simplify(tol)
orig = gs.iloc[0]
simp = simplified.iloc[0]
# 面积变化率
area_change = abs(simp.area - orig.area) / orig.area * 100
# Hausdorff 距离
hausdorff = orig.hausdorff_distance(simp)
# 顶点减少率
orig_pts = len(orig.exterior.coords)
simp_pts = len(simp.exterior.coords)
reduction = (1 - simp_pts / orig_pts) * 100
print(f"容差 {tol}: 面积变化={area_change:.2f}%, "
f"Hausdorff={hausdorff:.4f}, 顶点减少={reduction:.1f}%")
| 应用场景 | 建议容差 | 说明 |
|---|---|---|
| 高精度分析 | 数据精度的 1-2 倍 | 最小简化 |
| Web 地图显示 | 像素大小的 1-2 倍 | 根据缩放级别调整 |
| 缩略图/概览 | 较大容差 | 大幅简化 |
| 面积计算 | 小容差 | 保持面积精度 |
本章全面介绍了 GeoPandas 中的缓冲区与简化操作。主要内容回顾:
| 操作 | 说明 |
|---|---|
buffer(d) |
基本缓冲区,d>0 膨胀,d<0 收缩 |
cap_style |
线端头样式:round、flat、square |
join_style |
折角连接:round、mitre、bevel |
single_sided |
单侧缓冲区 |
| 操作 | 说明 |
|---|---|
simplify() |
Douglas-Peucker 简化 |
simplify_coverage() |
保持覆盖关系的简化 |
offset_curve() |
平行偏移线 |
segmentize() |
增加顶点密度 |
remove_repeated_points() |
移除重复坐标 |
preserve_topology=True下一章我们将学习空间索引与查询优化,探索如何提高大数据集上空间操作的性能。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。