第13-1章 Python地理空间开发

在工程环境下,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 是否可用(依赖 rtreepygeos)。
  • 导出优先选择 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统一、矢量与栅格处理、导出与报告,以及常见排障回环,便于在脚本化与规模化场景中快速把握步骤关系。

flowchart TD A[输入: 矢量/栅格/参数] --> B[环境检查与日志初始化] B --> C[CRS统一: to_crs/Transformer] C --> D[矢量操作: sjoin/叠置/聚合] C --> E[栅格操作: 读取/窗口化/指数计算] D --> F[导出矢量: GPKG/GeoJSON] E --> G[导出栅格: GeoTIFF/COG] F --> H[可视化: Matplotlib] F --> I[交互地图: Folium] G --> H H --> J[报告/日志/指标输出到 outputs/] I --> J J --> K{失败排查} K -->|CRS不一致| C K -->|路径编码| A K -->|依赖版本| B

节点与脚本参数映射(建议):

  • 输入 对应 --vector_in/--grid_path/--raster_in/--ndvi_*;统一 --outputs_dir
  • CRS统一 对应 --crspyproj.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 性能与规模化

  • 索引与预过滤:使用 sindexbbox 预过滤,减少精确几何计算数量。
  • 向量化优先:尽量避免 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章)联动形成端到端工程实践。
相关推荐
肥大毛7 小时前
C++入门学习---结构体
开发语言·c++·学习
小明记账簿7 小时前
JavaScript浮点数精度问题及解决方案
开发语言·javascript·ecmascript
南棱笑笑生7 小时前
20251213给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-6.1】系统时适配type-C0
linux·c语言·开发语言·rockchip
秋刀鱼 ..8 小时前
2026年电力电子与电能变换国际学术会议 (ICPEPC 2026)
大数据·python·计算机网络·数学建模·制造
月屯8 小时前
Pandoc 之--pdf-engine
java·开发语言·pdf
晨星3348 小时前
使用 IntelliJ IDEA 轻松连接 Java 与 MySQL 8 数据库
java·开发语言·数据库
znhy_238 小时前
day35打卡
python
古城小栈8 小时前
Java 在 Web3 时代的新定位
java·开发语言·web3
何中应8 小时前
【面试题-5】设计模式
java·开发语言·后端·设计模式·面试题