还在用ArcPy写脚本?PythonGIS自动化出图效率提升10倍(含:实战代码包)

ArcPy
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

《还在用ArcPy写脚本?PythonGIS自动化出图效率提升10倍(含:实战代码包)》这篇文章,重点解决一个很具体的问题:当你需要批量生成行政区专题图、项目范围图或统计制图成果时,如何用 Python GIS 工作流替代一部分传统 ArcPy 制图脚本,让出图流程更轻、更容易复用,也更适合跨平台部署。

引言:Python GIS 自动化出图到底适合解决什么问题

很多 GIS 同学最早接触自动化制图,往往是从 ArcPy 开始的。ArcPy 在 ArcGIS Pro 环境中非常强,尤其适合调用地理处理工具、批量处理工程文件和地图布局。但在一些场景里,你可能只是想把矢量数据、统计字段、底图边界和图例批量输出为 PNG 或 PDF,这时完整依赖 ArcGIS Pro 工程就显得比较重。

Python GIS 自动化出图更适合下面这类任务:

  • 批量按行政区、网格、项目区裁剪并出图。
  • 根据字段值生成分级设色图、点密度图、分类专题图。
  • 在服务器或 Linux 环境中定时生成地图图片。
  • 把 GeoPackage、Shapefile、GeoJSON、PostGIS 查询结果直接制图。
  • 希望减少对桌面 GIS 软件工程文件的依赖。

本文使用的核心工具是 GeoPandas、Matplotlib、Contextily 和 PyProj。它们不能完全替代 ArcPy,但可以覆盖大量“数据读取、投影转换、符号化、批量导出”的自动化出图需求。

PythonGIS自动化出图与ArcPy批量出图流程对比
Python GIS 自动化出图的典型流程:读取数据、检查坐标系、设置符号、批量导出地图成果。

背景:为什么很多 ArcPy 出图脚本越来越难维护

ArcPy 并不是不好用。相反,在 ArcGIS Pro 生态里,ArcPy 是非常专业的自动化接口。问题在于,不少单位的出图脚本经过多次复制、修改和临时加需求后,会变成下面这种状态:

  • 脚本强依赖某个 aprx 工程文件,换电脑后路径失效。
  • 地图布局、图层名称、字段名称写死在代码里。
  • 同一个制图逻辑被复制成多个脚本,难以统一修改。
  • 批量输出时需要启动 ArcGIS Pro 或依赖特定许可环境。
  • 脚本只适合当前项目,很难迁移到 WebGIS 后端或 Linux 服务器。

如果你的目标是“控制 ArcGIS Pro 工程里的布局、图层、标注、地图系列”,ArcPy 仍然是首选。但如果你的目标是“把空间数据按规则批量渲染成图片或 PDF”,Python GIS 自动化出图通常会更轻量。

原理:Python GIS 自动化出图的核心不是画图,而是标准化流程

Python GIS 自动化出图通常分为五步:读取数据、统一坐标系、计算制图范围、设置符号化、导出成果。只要这五步被封装好,后续换数据、换字段、换行政区时,就不需要重复写大量脚本。

1. 数据读取

GeoPandas 可以读取 Shapefile、GeoPackage、GeoJSON 等常见矢量数据。如果安装了相关依赖,也可以读取 PostGIS 数据。对于中小规模专题图,它足够完成大部分桌面 GIS 常见的数据读取任务。

2. 坐标系检查

自动化出图最容易翻车的地方是坐标系。数据如果没有正确的 CRS,也就是坐标参考系统,后面的比例尺、面积、叠加和底图加载都会出错。

常见原则是:用于叠加在线底图时,通常转为 Web Mercator,即 EPSG:3857;用于面积统计或工程制图时,应使用合适的投影坐标系,而不是直接使用经纬度坐标。

3. 制图范围计算

批量出图时,不能每张图手动缩放。脚本需要根据目标要素的边界自动计算地图范围,并增加一定留白,避免图形贴边。

4. 符号化

Matplotlib 负责把 GeoDataFrame 画出来。你可以按字段做分级设色,也可以按类别设置颜色。对于常规报告图、过程图和批量专题图,这种方式非常实用。

5. 导出成果

最终可以导出 PNG、JPG、SVG 或 PDF。用于报告插图时推荐 PNG;用于印刷或矢量后期编辑时可考虑 PDF 或 SVG。

步骤:用 GeoPandas 实现一次可复用的 PythonGIS 自动化出图

下面示例演示一个常见任务:读取行政区面数据,根据字段 value 生成分级设色图,并批量导出每个区县的地图图片。你可以把它改造成自己的实战代码包。

步骤 1:准备 Python 环境

建议使用 Conda 创建独立环境,减少 GDAL、Fiona、PyProj 等 GIS 依赖冲突。

conda create -n pythongis-map python=3.11 -y
conda activate pythongis-map
conda install -c conda-forge geopandas matplotlib mapclassify contextily -y

如果你使用 pip,也可以安装:

pip install geopandas matplotlib mapclassify contextily

在 Windows 环境中,如果 pip 安装 GeoPandas 报错,优先改用 Conda Forge。GIS Python 包通常依赖底层空间库,Conda 对新手更友好。

步骤 2:准备数据目录

建议按下面结构组织项目,不要把输入数据、脚本和输出图片混在一起。

project/
  data/
    county.gpkg
  output/
  scripts/
    batch_map.py

示例数据需要至少包含以下字段:

  • name:行政区或项目区名称。
  • value:需要制图的统计值,例如人口密度、建设用地面积、监测指标。
  • geometry:面要素几何字段。

步骤 3:读取数据并检查坐标系

import geopandas as gpd
from pathlib import Path

data_path = Path("../data/county.gpkg")
out_dir = Path("../output")
out_dir.mkdir(parents=True, exist_ok=True)

gdf = gpd.read_file(data_path)

print(gdf.head())
print(gdf.crs)

if gdf.crs is None:
    raise ValueError("数据缺少坐标系信息,请先在 QGIS 或 ArcGIS Pro 中正确定义 CRS。")

这里要注意:定义坐标系和投影转换不是一回事。定义坐标系是告诉软件“这份数据原本是什么坐标系”;投影转换是把数据从一个坐标系转换到另一个坐标系。如果原始 CRS 写错了,直接转换只会把错误放大。

步骤 4:转换到适合出图的坐标系

如果你要叠加 Contextily 在线底图,一般需要转为 EPSG:3857。

gdf_3857 = gdf.to_crs(epsg=3857)

如果你不叠加在线底图,而是做严谨的工程图或面积表达,应根据所在地区选择合适的投影坐标系。例如中国地区常见情况可能涉及 CGCS2000 高斯克吕格投影,但具体 EPSG 编码要根据分带和数据来源确认。

步骤 5:生成一张基础专题图

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6), dpi=150)

gdf_3857.plot(
    column="value",
    ax=ax,
    cmap="YlOrRd",
    legend=True,
    scheme="Quantiles",
    k=5,
    edgecolor="#666666",
    linewidth=0.3
)

ax.set_title("区县指标分级设色图", fontsize=14)
ax.set_axis_off()

plt.tight_layout()
plt.savefig(out_dir / "county_thematic_map.png", dpi=300, bbox_inches="tight")
plt.close()

这段代码已经可以输出一张完整的分级设色图。参数 scheme="Quantiles" 表示按分位数分级,k=5 表示分成 5 类。使用前需要安装 mapclassify

步骤 6:叠加在线底图

如果你希望地图背景更直观,可以叠加 Contextily 底图。注意:在线底图通常要求数据为 EPSG:3857。

import contextily as cx
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6), dpi=150)

gdf_3857.plot(
    column="value",
    ax=ax,
    cmap="YlOrRd",
    alpha=0.75,
    legend=True,
    edgecolor="#333333",
    linewidth=0.4
)

cx.add_basemap(ax, source=cx.providers.CartoDB.Positron)

ax.set_title("叠加底图的指标分级设色图", fontsize=14)
ax.set_axis_off()

plt.tight_layout()
plt.savefig(out_dir / "county_with_basemap.png", dpi=300, bbox_inches="tight")
plt.close()

如果运行时底图加载失败,先检查网络环境、代理设置和底图服务可访问性。生产环境中建议使用本地瓦片或自建瓦片服务,不要把公开在线底图当作稳定依赖。

步骤 7:按行政区批量自动出图

下面代码会遍历每个行政区,以目标区为高亮,其余区域为灰色背景,并自动导出单独图片。

import geopandas as gpd
import matplotlib.pyplot as plt
from pathlib import Path

data_path = Path("../data/county.gpkg")
out_dir = Path("../output/batch")
out_dir.mkdir(parents=True, exist_ok=True)

gdf = gpd.read_file(data_path)

if gdf.crs is None:
    raise ValueError("数据缺少 CRS,请先正确定义坐标系。")

gdf = gdf.to_crs(epsg=3857)

for idx, row in gdf.iterrows():
    name = str(row["name"]).replace("/", "_").replace("", "_")
    target = gdf[gdf.index == idx]

    fig, ax = plt.subplots(figsize=(8, 6), dpi=150)

    gdf.plot(
        ax=ax,
        color="#eeeeee",
        edgecolor="#999999",
        linewidth=0.3
    )

    target.plot(
        ax=ax,
        color="#e34a33",
        edgecolor="#111111",
        linewidth=1.0
    )

    minx, miny, maxx, maxy = target.total_bounds
    width = maxx - minx
    height = maxy - miny
    pad_x = width * 0.3
    pad_y = height * 0.3

    ax.set_xlim(minx - pad_x, maxx + pad_x)
    ax.set_ylim(miny - pad_y, maxy + pad_y)

    ax.set_title(f"{name} 区域位置图", fontsize=14)
    ax.set_axis_off()

    plt.tight_layout()
    plt.savefig(out_dir / f"{name}_位置图.png", dpi=300, bbox_inches="tight")
    plt.close()

print("批量出图完成。")

这就是 Python GIS 自动化出图效率提升的关键:不是某一行代码神奇变快,而是把“重复打开工程、缩放范围、导出图片、改文件名”的人工动作变成稳定循环。

步骤 8:封装成可复用函数

实际项目中,不建议把所有逻辑写在一个长脚本里。可以把出图函数封装出来,后续只改输入路径、字段名和输出目录。

def export_highlight_maps(
    gdf,
    name_field,
    output_dir,
    highlight_color="#e34a33",
    background_color="#eeeeee"
):
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    for idx, row in gdf.iterrows():
        name = str(row[name_field]).replace("/", "_").replace("", "_")
        target = gdf[gdf.index == idx]

        fig, ax = plt.subplots(figsize=(8, 6), dpi=150)

        gdf.plot(
            ax=ax,
            color=background_color,
            edgecolor="#999999",
            linewidth=0.3
        )

        target.plot(
            ax=ax,
            color=highlight_color,
            edgecolor="#111111",
            linewidth=1.0
        )

        minx, miny, maxx, maxy = target.total_bounds
        pad_x = (maxx - minx) * 0.3
        pad_y = (maxy - miny) * 0.3

        ax.set_xlim(minx - pad_x, maxx + pad_x)
        ax.set_ylim(miny - pad_y, maxy + pad_y)
        ax.set_title(f"{name} 区域位置图", fontsize=14)
        ax.set_axis_off()

        plt.tight_layout()
        plt.savefig(output_dir / f"{name}_位置图.png", dpi=300, bbox_inches="tight")
        plt.close()

常见坑:PythonGIS 自动化出图最容易出错的地方

坑 1:坐标系看起来没错,底图却对不上

最常见原因是原始数据 CRS 定义错误。例如数据本来是 CGCS2000 经纬度,却被错误定义成 WGS84 或 Web Mercator。此时即使 to_crs 成功执行,结果也可能偏移。

  • 先在 QGIS 或 ArcGIS Pro 中加载数据检查位置。
  • 确认数据提供方给出的坐标系说明。
  • 不要用“看起来差不多”判断坐标系。

坑 2:中文字段或中文文件名乱码

Shapefile 对中文字段名和编码支持较弱。长期项目建议优先使用 GeoPackage。它比 Shapefile 更适合保存中文属性、长字段名和多图层数据。

坑 3:导出的图例不符合制图规范

Matplotlib 默认图例适合快速预览,但不一定符合正式制图规范。正式项目中应手动设置分类边界、图例标题、字体大小和颜色方案。

坑 4:批量出图速度仍然慢

如果数据量很大,慢的原因通常不是绘图代码,而是几何过于复杂、每次循环重复绘制全量数据,或者读取数据方式不合理。

  • 出图前对边界数据做适度简化。
  • 把不变的背景图层预处理成缓存。
  • 只读取当前出图范围需要的数据。
  • PostGIS 场景中使用空间索引和范围过滤。

坑 5:地图很漂亮,但结果不可复现

自动化出图的价值不只是快,还包括可复现。建议把环境依赖、输入数据版本、脚本参数和输出目录规则写清楚。否则换一个人运行,很可能得到不同结果。

方法比较:ArcPy、GeoPandas、QGIS PyQGIS 怎么选

方法 适合场景 优势 限制
ArcPy ArcGIS Pro 工程自动化、地图布局、地图系列、地理处理工具链 与 ArcGIS Pro 集成深,制图布局能力强,适合正式生产流程 依赖 ArcGIS 环境和许可,跨平台部署不如开源 Python GIS 灵活
GeoPandas + Matplotlib 批量专题图、统计图、报告插图、服务器端自动出图 轻量、可脚本化、容易集成数据分析流程 复杂标注、专业制图整饰和交互式布局能力有限
PyQGIS 复用 QGIS 样式、布局模板和处理工具 可以调用 QGIS 制图和处理能力,适合开源桌面 GIS 工作流 环境配置比普通 Python 脚本更复杂,部署时需要 QGIS 运行环境
PostGIS + Python 大数据量空间查询、按范围动态出图、WebGIS 后端制图 数据库侧过滤和索引能力强,适合服务化 需要数据库设计、SQL 优化和空间索引经验

简单判断:如果你必须控制 ArcGIS Pro 里的布局元素,选 ArcPy;如果你要快速批量导出专题图,选 GeoPandas;如果你已经有 QGIS 样式和布局模板,考虑 PyQGIS;如果数据量大且要服务化,优先考虑 PostGIS 加 Python。

检查清单:交付前必须确认这些问题

  • 数据坐标系:每个图层是否都有正确 CRS?是否需要投影转换?
  • 字段名称:代码中的 namevalue 是否和真实字段一致?
  • 分类方法:分位数、自然断点、等距分级是否符合业务含义?
  • 颜色方案:是否适合表达指标大小?是否存在红绿不友好问题?
  • 输出分辨率:报告插图一般可用 300 dpi,网页展示可按实际尺寸优化。
  • 文件命名:行政区名称中是否包含斜杠、空格等不适合作为文件名的字符?
  • 图例和标题:是否说明指标单位、统计年份和数据来源?
  • 可复现性:是否保存了脚本、依赖版本、输入数据和参数说明?
  • 性能:是否对大数据做了简化、过滤或缓存?
  • 版权:如果使用在线底图,是否符合底图服务的使用条款?

FAQ:PythonGIS 自动化出图常见问题

Q1:Python GIS 自动化出图能完全替代 ArcPy 吗?

不能。Python GIS 自动化出图可以替代一部分批量专题图、报告插图和数据分析制图任务,但 ArcPy 在 ArcGIS Pro 工程控制、地图布局、地图系列和 Esri 工具链调用方面仍然更强。

Q2:GeoPandas 出图适合正式项目交付吗?

适合一部分正式项目,尤其是批量报告图、过程图、监测图和统计专题图。但如果项目要求严格的制图整饰、复杂标注、比例尺控件、图框模板和审图规范,建议结合 ArcGIS Pro、QGIS Layout 或专业制图软件完成最终排版。

Q3:为什么我的 PythonGIS 自动化出图比 ArcPy 还慢?

通常是因为每次循环都重复绘制大范围数据,或者几何太复杂。可以先简化边界、按范围过滤数据、缓存背景图层,或者把数据放入 PostGIS 后通过空间索引查询。

Q4:批量出图时如何保证每张图比例一致?

如果要求比例一致,不要按每个要素自动缩放范围,而应固定地图范围或固定缓冲距离。对于行政区位置图,可以使用统一的上级行政区范围作为背景,再高亮目标区域。

Q5:Python GIS 自动化出图应该用 Shapefile 还是 GeoPackage?

新项目优先推荐 GeoPackage。Shapefile 文件多、字段名长度有限、中文编码容易出问题;GeoPackage 是单文件数据库,更适合保存多图层、中文属性和较复杂的数据结构。

Q6:能不能把这套流程接到 WebGIS 系统里?

可以。常见做法是后端使用 Python 接收参数,从 PostGIS 查询数据,使用 GeoPandas 或其他渲染工具生成图片,再返回给前端。但如果需要高并发动态地图服务,应考虑 GeoServer、MapServer、矢量瓦片或专门的地图渲染服务。

结论:别纠结是不是替代 ArcPy,先把出图流程标准化

Python GIS 自动化出图的真正价值,不是简单宣称比 ArcPy 快多少,而是把重复的制图动作变成标准化、可复用、可检查的流程。对于批量专题图、报告插图、监测图和轻量化服务端制图,GeoPandas、Matplotlib、Contextily 这一套工具非常值得掌握。

如果你的工作强依赖 ArcGIS Pro 布局和地图系列,继续使用 ArcPy 是合理选择;如果你的任务主要是读取空间数据、按字段符号化、批量导出图片,那么可以从本文的代码结构开始,逐步封装成自己的 PythonGIS 自动化出图实战代码包。