在工程环境下,Python 是把"空间分析方法"落到"可复现脚本"的关键纽带。本章聚焦矢量与栅格双栈(GeoPandas/Shapely/Fiona/PyProj 与 Rasterio/rioxarray),通过一套一致的参数与输出目录约定,完成重投影、叠置与空间连接、栅格指数计算与窗口化处理、批处理管线化与结果可视化。目标是既能跑通端到端流程,又能写出可维护、可扩展的脚本。
13.1 学习目标
- 使用 Python 完成地理空间数据处理与分析,形成可复现脚本与报告。
- 掌握矢量与栅格常用库的协作方式与数据流组织。
- 能够统一 CRS、进行叠置/空间连接、指数计算与窗口化处理。
- 具备批处理脚本化、日志记录与性能初步优化能力。
13.2 先修要求
- Python 基础与包管理(conda/pip),能创建并激活环境。
- GIS 基础(矢量/栅格、CRS/SRID、常见投影)。
- 能够使用 QGIS/命令行进行基本数据浏览与检查。
13.3 核心知识点
- 矢量栈:
geopandas(读写/空间操作/可视化)、shapely(几何操作)、fiona(驱动与 I/O)、pyproj(坐标变换)。 - 栅格栈:
rasterio(读写/窗口化/掩膜/坐标系)、rioxarray(与 xarray 协作/坐标感知计算)。 - 管线化:
argparse(参数)、logging(日志)、统一outputs/输出目录与文件命名。 - 性能与工程:空间索引(sindex)、窗口化读取、并行与向量化、格式选择(GeoJSON/GPKG/Parquet)。
13.4 内容提纲
- 矢量与栅格处理的最小闭环:加载→检查→统一 CRS→分析→导出。
- 叠置(Intersect/Union)与空间连接(Spatial Join),栅格指数(NDVI/NDWI)。
- 参数化脚本与批处理(输入路径、目标 CRS、输出目录)。
- 可视化与报告生成(Matplotlib/Folium)。
13.4.1 矢量数据处理(GeoPandas/Shapely)
示例:加载两个矢量集(设施与网格),统一 CRS,完成叠置与空间连接,输出统计结果。
python
import geopandas as gpd
from shapely.geometry import Point
# 统一输出目录约定
OUT_DIR = "outputs"
fac = gpd.read_file("gis_examples/datasets/facilities_sample.geojson")
grid = gpd.read_file("gis_examples/datasets/population_grid_sample.geojson")
# 统一投影到 Web Mercator(按需选择目标)
fac = fac.to_crs("EPSG:3857")
grid = grid.to_crs("EPSG:3857")
# 叠置(设施点落入人口网格),计算每网格的设施数量
joined = gpd.sjoin(grid, fac, how="left", predicate="contains")
counts = joined.groupby("index_left").size().rename("facility_count")
result = grid.join(counts, how="left").fillna({"facility_count": 0})
result.to_file(f"{OUT_DIR}/grid_facilities.gpkg", layer="grid_facilities", driver="GPKG")
要点:
- 优先统一 CRS 再进行空间关系判断,避免度/米混用。
- 使用
sjoin前可检查gdf.sindex是否可用(依赖rtree或pygeos)。 - 导出优先选择 GPKG 以避免 Shapefile 的字段长度与编码限制。
13.4.2 坐标与投影(PyProj)
示例:使用 pyproj 在 CRS 间进行坐标转换,并验证与 GeoPandas 的一致性。
python
from pyproj import Transformer
import geopandas as gpd
# 单点坐标转换(WGS84 -> Web Mercator)
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
x3857, y3857 = transformer.transform(114.1, 22.3)
# 与 GeoPandas 对齐验证
gdf = gpd.GeoDataFrame(geometry=[gpd.points_from_xy([114.1], [22.3])[0]], crs="EPSG:4326")
gdf2 = gdf.to_crs("EPSG:3857")
assert abs(gdf2.geometry.iloc[0].x - x3857) < 1e-6
要点:
- 经纬度坐标默认
always_xy=True保证 (lon, lat) 顺序;避免轴序错误。 - 单点/批量转换都可通过
pyproj保持与服务端/数据库一致。
13.4.3 栅格处理(Rasterio/rioxarray)
示例:计算 NDVI,并在云掩膜或窗口化读取场景下提升效率。
python
import rasterio
import numpy as np
with rasterio.open("gis_examples/datasets/sentinel_sample.tif") as src:
# 假设:B04=Red, B08=NIR
red = src.read(4).astype(np.float32)
nir = src.read(8).astype(np.float32)
ndvi = (nir - red) / (nir + red + 1e-6)
profile = src.profile.copy()
profile.update(count=1, dtype=rasterio.float32)
with rasterio.open("outputs/ndvi.tif", "w", **profile) as dst:
dst.write(ndvi, 1)
窗口化读取(大影像):
python
with rasterio.open("gis_examples/datasets/sentinel_large.tif") as src:
win = rasterio.windows.Window(col_off=0, row_off=0, width=2048, height=2048)
red = src.read(4, window=win).astype(np.float32)
nir = src.read(8, window=win).astype(np.float32)
ndvi = (nir - red) / (nir + red + 1e-6)
# 保存或进一步处理
要点:
- 避免整幅影像一次性载入内存;优先窗口化或分块处理。
- 保留/更新影像 profile,确保坐标与仿射变换正确传递。
13.4.4 批处理与管线化(argparse/logging)
示例:将矢量与栅格处理脚本化,并统一输出目录。
python
import argparse, os, logging
def ensure_dir(p):
os.makedirs(p, exist_ok=True)
parser = argparse.ArgumentParser("Chapter 13 examples")
parser.add_argument("--vector_in", required=True)
parser.add_argument("--raster_in", required=True)
parser.add_argument("--crs", default="EPSG:3857")
parser.add_argument("--outputs_dir", default="outputs")
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
ensure_dir(args.outputs_dir)
logging.info("Start pipeline with %s / %s", args.vector_in, args.raster_in)
# ...后续处理并输出到 outputs/
13.4.5 可视化与报告(Matplotlib/Folium)
示例:矢量简易绘制与 Folium 交互可视化。
python
import matplotlib.pyplot as plt
import geopandas as gpd
import folium
fac = gpd.read_file("gis_examples/datasets/facilities_sample.geojson").to_crs("EPSG:4326")
plt.figure(figsize=(6, 6))
fac.plot(markersize=5)
plt.title("Facilities (EPSG:4326)")
plt.savefig("outputs/plot_facilities.png", dpi=150)
m = folium.Map(location=[22.3, 114.1], zoom_start=11)
for _, r in fac.iterrows():
folium.CircleMarker([r.geometry.y, r.geometry.x], radius=4, color="red", fill=True).add_to(m)
m.save("outputs/map_facilities.html")
13.4.6 处理管线流程图
下面的流程图总结了本章示例的端到端管线,包含输入、CRS统一、矢量与栅格处理、导出与报告,以及常见排障回环,便于在脚本化与规模化场景中快速把握步骤关系。
节点与脚本参数映射(建议):
输入对应--vector_in/--grid_path/--raster_in/--ndvi_*;统一--outputs_dir。CRS统一对应--crs与pyproj.Transformer(always_xy=True);矢量用to_crs。矢量操作对应--join_predicate(contains/intersects/within)与聚合口径选择。栅格操作对应窗口大小与波段编号;大影像优先窗口化与分块。导出与报告建议固定命名,落盘到outputs/以形成复现闭环。
13.4.7 数据流图(矢量/栅格:文件与内存链路)
此图区分矢量与栅格在"磁盘文件"与"内存结构"之间的主要链路,帮助理解数据在读入、处理、可视化与导出过程中的形态与接口。
栅格通道 矢量通道 内存: NDArray/Rasterio Dataset 磁盘: GeoTIFF/COG 处理: reproject/window/NDVI 导出: GeoTIFF/COG 可视化: Matplotlib 内存: GeoDataFrame 磁盘: Shapefile/GeoJSON/GPKG 处理: sjoin/overlay/aggregate 导出: GPKG/GeoJSON 可视化: Matplotlib/Folium CRS统一: to_crs/pyproj outputs/ 目录: 矢量 outputs/ 目录: 栅格 报告/图表
实践要点:
- 矢量数据读入后主要在
GeoDataFrame中以列式结构操作;栅格以NDArray为核心,辅以Dataset元数据(仿射、CRS)。 - CRS 统一是跨通道的关键步骤;矢量用
to_crs,栅格用rasterio.warp.reproject或构建 VRT。 - 大影像建议分块/窗口化处理;矢量的大规模运算建议构建空间索引并分区聚合。
- 导出优先固定命名并落盘至
outputs/,形成复现与对比的稳定基线。
13.5 实践任务与步骤
- 任务一:用 GeoPandas 实现叠置与空间连接,统计设施覆盖。
- 输入:设施点与人口网格(统一到
EPSG:3857)。 - 步骤:
sjoin+groupby统计,保存 GPKG。
- 输入:设施点与人口网格(统一到
- 任务二:用 Rasterio 计算 NDVI 并输出单波段 GeoTIFF。
- 输入:Sentinel 影像(有 Red/NIR 波段)。
- 步骤:窗口化/整幅读取 + 指数计算 + 保存 profile。
- 任务三:编写批处理脚本,统一
--vector_in/--raster_in/--crs/--outputs_dir参数。- 输出:
outputs/下图层与指标结果、图像/HTML 报告。
- 输出:
示例命令(按需替换路径):
bash
python gis_examples/modules/run_ch13_examples.py \
--vector_in gis_examples/datasets/facilities_sample.geojson \
--raster_in gis_examples/datasets/sentinel_sample.tif \
--crs EPSG:3857 \
--outputs_dir outputs
13.6 产出与评估标准
- 产出:叠置与空间连接结果(GPKG)、NDVI 栅格(GeoTIFF)、可视化图与交互地图(PNG/HTML)、脚本与运行日志。
- 评估:功能正确性(50%)/代码规范(30%)/文档与复现(20%)。
13.7 进阶示例与说明
- 叠置策略:大数据下可先
bbox预过滤,再做精确关系判断;使用prepared geometries(Shapely 2.x 默认)可提升速度。 - 空间连接结果质量:对多对多关系(一个设施被多个网格覆盖)明确聚合口径(计数或权重面积)。
- NDVI 掩膜:优先使用质量波段(如 Sentinel QA60/Fmask)掩膜云与阴影;无质量波段时可用简单阈值或波段间比值。
- 栅格到矢量:根据阈值将 NDVI 二值化后进行多边形化(
rasterio.features.shapes),注意平滑与简化参数。 - 输出格式选择:矢量优先 GPKG(字段支持好),栅格可做 COG(Cloud Optimized GeoTIFF)以支持分块访问。
13.8 数据质量与故障排查
- CRS 不一致:在
sjoin/叠置前统一投影(to_crs('EPSG:3857')或业务目标 CRS)。 - 路径与编码:确保文件路径与文件名编码一致;Windows 下注意反斜杠与中文路径。
- 字段与驱动:Shapefile 限制多,优先使用 GPKG;必要时指定驱动:
gdf.to_file(path, driver='GPKG')。 - 依赖与环境:
geopandas/fiona/rasterio依赖 GDAL/PROJ;优先使用conda安装以简化编译/链接问题。 - 影像波段顺序:确认 Red/NIR 实际波段索引;错误的索引会导致指数异常。
13.9 性能与规模化
- 索引与预过滤:使用
sindex与bbox预过滤,减少精确几何计算数量。 - 向量化优先:尽量避免 Python 层循环;利用 NumPy/xarray 对栅格进行向量化计算。
- 分块与窗口化:对大影像进行窗口化处理,并控制块大小以平衡内存与 I/O。
- 二进制列存:对中间结果使用 Parquet/Feather 提升读写效率(
gdf.to_parquet)。 - 日志与进度:标准化日志,结合进度条(如
tqdm)提升可观测性。
13.10 进阶实践(一套可参数化管线)
示例工作流(与示例工程参数风格一致):
bash
python gis_examples/modules/run_ch13_examples.py \
--vector_in /abs/path/facilities.geojson \
--raster_in /abs/path/sentinel.tif \
--crs EPSG:3857 \
--outputs_dir outputs \
--join_predicate contains \
--ndvi_red_band 4 --ndvi_nir_band 8 \
--grid_path gis_examples/datasets/population_grid_sample.geojson
脚本建议参数:
--vector_in、--grid_path:设施点与人口网格路径。--raster_in、--ndvi_red_band/--ndvi_nir_band:栅格路径与波段编号。--crs:统一投影。--join_predicate:空间连接谓词(contains/intersects/within)。--outputs_dir:统一输出目录。
输出约定:
outputs/grid_facilities.gpkg(网格覆盖统计)。outputs/ndvi.tif(指数栅格)。outputs/report_*.png|html(可视化报告)。
13.11 失败排查(统一格式)
- 本地路径与编码:
- 确认输入/输出路径存在且有读写权限;避免中文路径引起的编码问题。
- 在 Windows 上优先使用绝对路径与正斜杠,或加引号处理空格。
- CRS/SRID 一致性:
- 在进行
sjoin/叠置/缓冲前统一投影;检查gdf.crs是否为目标。 - 对经纬度坐标使用
always_xy=True的变换器避免轴序错误。
- 在进行
- 依赖安装与版本:
- 优先
conda install geopandas rasterio rioxarray -c conda-forge,避免 GDAL/PROJ 手工编译。 - 若
ImportError: GDAL,检查conda环境与which gdalinfo链接是否一致。
- 优先
- 栅格读写问题:
- 校验波段索引与数据类型;使用
profile.update(count=1, dtype=...)写出单波段结果。 - 大影像内存溢出时改用窗口化/分块处理。
- 校验波段索引与数据类型;使用
13.12 本章总结
- 本章以 Python 为主线,打通矢量与栅格处理、坐标统一与空间关系分析、指数计算与窗口化、批处理管线与可视化输出。
- 通过统一参数与
outputs/目录约定,形成可复现脚本与报告,便于规模化与团队协作。 - 建议基于示例脚本扩展:加入日志分级、错误处理与性能监控,并与数据库/服务端(第12/15章)联动形成端到端工程实践。