Python空间分析如何快速出图?python画空间图实战技巧(附:Matplotlib与GeoPandas脚本)

GIS基础理论
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

引言:很多同学做完缓冲区、叠加分析、空间连接或栅格统计后,最后卡在“怎么把结果快速画成一张能汇报的图”。这篇文章围绕《Python空间分析如何快速出图?python画空间图实战技巧(附:Matplotlib与GeoPandas脚本)》这个问题,重点讲清楚用 GeoPandas 处理矢量数据、用 Matplotlib 控制图面样式、用少量脚本快速输出空间分析结果图的实战流程。

本文适合已经会一点 Python、正在使用 Shapefile、GeoPackage、GeoJSON 等 GIS 数据的读者。你不需要先掌握复杂制图系统,只要理解坐标系、字段、图层叠加和颜色分级,就可以完成常见的 Python 空间分析出图。

Python空间分析快速出图 python画空间图GeoPandas脚本流程
Python 空间分析快速出图的典型流程:读数据、做分析、设样式、导出图片。

背景:为什么 Python空间分析 出图经常慢在最后一步

背景:在 GIS 项目里,Python 空间分析的计算环节往往并不难,真正耗时间的是把结果画清楚。常见场景包括:

  • 做完行政区统计后,需要按指标分级设色。
  • 做完 POI 缓冲区分析后,需要把点、缓冲区、边界叠加到一张图上。
  • 做完道路或地块筛选后,需要快速导出一张 PNG 给同事确认。
  • 批量分析多个城市或多个区县,需要自动出图而不是手动截图。

很多人会直接把结果导入 QGIS 或 ArcGIS Pro 再制图,这当然没问题。但如果你的分析流程本来就在 Python 里,使用 GeoPandas 和 Matplotlib 可以减少中间导入导出,尤其适合快速验证、批量出图和自动化报告。

原理:python画空间图的核心是 GeoDataFrame 加 Matplotlib 坐标轴

原理:GeoPandas 的核心数据结构是 GeoDataFrame,它可以理解为“带几何字段的表格”。每一行是一条空间要素,每个属性字段都可以用于筛选、分类和设色。

GeoPandas 的 plot() 方法底层调用 Matplotlib。也就是说,Python 空间分析出图通常分成两层:

  • GeoPandas:负责读取空间数据、管理坐标系、执行空间叠加、空间连接、缓冲区等操作。
  • Matplotlib:负责控制画布大小、标题、图例、颜色、字体、坐标轴、导出分辨率。

理解这一点后,python画空间图就不会停留在“能画出来”,而是可以进一步控制“图是否清楚、字段是否正确、图例是否可读、导出是否适合汇报”。

快速出图的关键不是把所有制图效果都堆上去,而是先保证坐标系正确、图层顺序正确、颜色表达正确、导出分辨率合适。

步骤:Python空间分析快速出图的完整脚本流程

步骤:下面用一个常见案例演示:读取行政区边界和 POI 点数据,统计每个行政区内的 POI 数量,并按数量分级设色出图。

1. 准备 Python 环境

建议使用 Conda 创建独立环境,避免 GeoPandas、Fiona、Shapely、PyProj 等依赖冲突。

conda create -n gisplot python=3.11
conda activate gisplot
conda install -c conda-forge geopandas matplotlib mapclassify

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

pip install geopandas matplotlib mapclassify

其中 mapclassify 用于自然断点、分位数等分级设色方法,在做统计专题图时很常用。

2. 读取空间数据并检查坐标系

假设有两个数据:

  • districts.gpkg:行政区面数据,字段中有 name
  • poi.gpkg:POI 点数据。
import geopandas as gpd
import matplotlib.pyplot as plt

districts = gpd.read_file("data/districts.gpkg")
poi = gpd.read_file("data/poi.gpkg")

print(districts.crs)
print(poi.crs)

如果两个图层坐标系不一致,必须先统一。空间连接、叠加分析、距离计算都依赖坐标系。这里把 POI 转成行政区的坐标系:

poi = poi.to_crs(districts.crs)

3. 做空间连接并统计每个区的 POI 数量

使用 sjoin 判断点落在哪个行政区内,然后按行政区名称统计数量。

joined = gpd.sjoin(
    poi,
    districts[["name", "geometry"]],
    how="left",
    predicate="within"
)

poi_count = joined.groupby("name").size().reset_index(name="poi_count")

districts_stat = districts.merge(poi_count, on="name", how="left")
districts_stat["poi_count"] = districts_stat["poi_count"].fillna(0)

这一步完成后,districts_stat 就是一份带有统计字段的面图层,可以直接用于 Python 空间分析出图。

4. 用 GeoPandas 快速画专题图

最简版本如下:

fig, ax = plt.subplots(figsize=(8, 8))

districts_stat.plot(
    column="poi_count",
    ax=ax,
    legend=True,
    cmap="YlOrRd",
    edgecolor="#666666",
    linewidth=0.5
)

ax.set_title("各行政区 POI 数量分布", fontsize=16)
ax.set_axis_off()

plt.tight_layout()
plt.savefig("output/poi_count_map.png", dpi=300)
plt.show()

这段脚本已经可以完成一张基础的统计专题图。column 指定用于设色的字段,cmap 指定色带,legend=True 输出图例,dpi=300 适合常规报告或文章配图。

5. 叠加点图层,让结果更直观

如果只看面统计,有时读者不知道 POI 的空间分布细节。可以在面图层上叠加点图层:

fig, ax = plt.subplots(figsize=(9, 9))

districts_stat.plot(
    column="poi_count",
    ax=ax,
    legend=True,
    cmap="YlOrRd",
    edgecolor="#555555",
    linewidth=0.6,
    alpha=0.85
)

poi.plot(
    ax=ax,
    color="#1f78b4",
    markersize=8,
    alpha=0.6
)

ax.set_title("POI 数量分布与点位叠加图", fontsize=16)
ax.set_axis_off()

plt.tight_layout()
plt.savefig("output/poi_count_with_points.png", dpi=300, bbox_inches="tight")
plt.show()

图层顺序很重要。通常先画面,再画线,最后画点。否则点可能被面图层盖住。

6. 使用分级方法改善专题图效果

默认连续色带有时不适合汇报。可以使用 scheme 参数进行分级设色,例如分位数分级:

fig, ax = plt.subplots(figsize=(9, 9))

districts_stat.plot(
    column="poi_count",
    ax=ax,
    legend=True,
    cmap="OrRd",
    scheme="Quantiles",
    k=5,
    edgecolor="white",
    linewidth=0.6,
    legend_kwds={
        "title": "POI数量",
        "loc": "lower left"
    }
)

ax.set_title("各行政区 POI 数量分级图", fontsize=16)
ax.set_axis_off()

plt.tight_layout()
plt.savefig("output/poi_count_quantiles.png", dpi=300, bbox_inches="tight")
plt.show()

scheme="Quantiles" 表示按分位数分级,k=5 表示分成 5 级。对于空间分析成果图,这比默认连续渐变更容易解释。

7. 处理中文标题和中文图例乱码

Matplotlib 在部分系统中会出现中文乱码。可以设置中文字体:

import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False

如果是 macOS,可以尝试:

plt.rcParams["font.sans-serif"] = ["Arial Unicode MS"]
plt.rcParams["axes.unicode_minus"] = False

如果服务器没有中文字体,建议先安装字体,或者在出图脚本中指定字体文件。否则自动批量出图时很容易出现标题为空框或方块。

常见坑:Python空间分析出图不准、不美观、导出模糊怎么办

常见坑:下面这些问题在 python画空间图时非常常见,尤其是刚从桌面 GIS 转到 Python 的读者。

坑 1:经纬度坐标系下直接做距离和缓冲区

如果数据是 EPSG:4326,单位是度,不适合直接做米级缓冲区或面积计算。应该先投影到合适的平面坐标系,例如本地高斯克吕格、UTM 或项目指定投影。

print(districts.crs)

districts_proj = districts.to_crs(epsg=3857)
poi_proj = poi.to_crs(epsg=3857)

注意:EPSG:3857 适合 Web 地图显示,不一定适合严肃面积统计。正式项目应选择本地适用投影。

坑 2:图层坐标系不一致导致空间连接结果为空

如果 gpd.sjoin() 后结果大量为空,优先检查:

  • 两个图层是否有 CRS。
  • 两个图层 CRS 是否一致。
  • 点是否真的落在面范围内。
  • 几何是否存在无效或空几何。
print(districts.crs)
print(poi.crs)
print(districts.total_bounds)
print(poi.total_bounds)

total_bounds 可以快速检查两个图层的空间范围是否明显错位。

坑 3:导出的图很模糊

导出模糊通常和 dpi、画布大小、保存参数有关。建议:

  • 报告插图使用 dpi=300
  • 需要大图时增大 figsize
  • 使用 bbox_inches="tight" 减少无效白边。
plt.savefig(
    "output/map_high_quality.png",
    dpi=300,
    bbox_inches="tight",
    facecolor="white"
)

坑 4:颜色好看但表达错误

专题图不是越鲜艳越好。颜色应匹配数据含义:

  • 数量、密度、强度:适合单色渐变,如 YlOrRd
  • 正负变化:适合发散色带,如 RdBu
  • 类别类型:适合离散颜色,而不是连续渐变。

如果用连续色带表达行政类别、土地类型、设施类型,读者会误以为类别之间存在大小顺序。

坑 5:边界线太粗导致小区域看不清

行政区很多时,边界线过粗会压住填充色。可以调低 linewidth,或者使用白色细线增强区分:

districts_stat.plot(
    column="poi_count",
    edgecolor="white",
    linewidth=0.3,
    cmap="YlOrRd",
    legend=True
)

方法比较:Matplotlib、GeoPandas、Contextily、QGIS 哪个更适合快速出图

方法比较:Python 空间分析出图并不只有一种方式。选择工具时,要看你的目标是快速验证、批量出图、论文制图,还是交互式展示。

方法 适合场景 优点 限制
GeoPandas plot 快速查看矢量结果、统计专题图 代码少,和空间分析流程衔接自然 高级制图能力有限
Matplotlib 控制标题、图例、布局、导出质量 可定制性强,适合自动化报告 地图专用元素需要手动处理
Contextily 叠加在线底图 适合城市尺度展示,空间参照更直观 通常需要 Web Mercator 坐标系,受网络和底图版权影响
QGIS 精细制图、地图整饰、人工排版 制图能力强,适合最终成果图 批量自动化需要额外配置
Folium 或 Kepler.gl 交互式地图展示 适合网页查看和探索分析 不一定适合静态论文或报告插图

如果只是 Python 空间分析快速出图,建议优先使用 GeoPandas 加 Matplotlib;如果要做正式地图版面,再把结果导入 QGIS 或 ArcGIS Pro 精修。

进阶脚本:叠加在线底图并导出空间分析结果图

如果需要让地图更容易被非 GIS 读者理解,可以叠加在线底图。常用库是 contextily

pip install contextily

使用在线底图时,通常需要把数据转换到 Web Mercator 坐标系 EPSG:3857

import geopandas as gpd
import matplotlib.pyplot as plt
import contextily as ctx

districts = gpd.read_file("data/districts.gpkg")
poi = gpd.read_file("data/poi.gpkg")

districts_3857 = districts.to_crs(epsg=3857)
poi_3857 = poi.to_crs(epsg=3857)

fig, ax = plt.subplots(figsize=(9, 9))

districts_3857.boundary.plot(
    ax=ax,
    color="red",
    linewidth=1
)

poi_3857.plot(
    ax=ax,
    color="blue",
    markersize=10,
    alpha=0.7
)

ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)

ax.set_title("POI 点位与行政区边界叠加图", fontsize=16)
ax.set_axis_off()

plt.tight_layout()
plt.savefig("output/poi_basemap.png", dpi=300, bbox_inches="tight")
plt.show()

需要注意,在线底图涉及网络访问、服务稳定性和版权条款。正式项目中应确认底图来源是否允许用于报告或公开发布。

检查清单:python画空间图前后要检查什么

检查清单:每次用 Python 空间分析出图前,建议按下面顺序检查,能避免大多数低级错误。

  • 数据是否读入成功:检查 head()、字段名和几何类型。
  • 坐标系是否存在:检查 gdf.crs 是否为空。
  • 图层坐标系是否一致:叠加、空间连接前必须统一 CRS。
  • 空间范围是否合理:用 total_bounds 检查是否错位。
  • 统计字段是否为空:分级设色前检查字段是否全是空值或 0。
  • 分级方法是否适合:数量型字段可用分位数、自然断点或等距分级。
  • 图层顺序是否正确:一般先面、后线、最后点。
  • 中文字体是否正常:标题、图例、标注不能乱码。
  • 导出分辨率是否足够:报告图建议 dpi=300
  • 输出文件夹是否存在:批量出图时要提前创建输出目录。

批量出图时,可以加上目录创建逻辑:

from pathlib import Path

output_dir = Path("output")
output_dir.mkdir(exist_ok=True)

plt.savefig(output_dir / "map.png", dpi=300, bbox_inches="tight")

FAQ:Python空间分析快速出图常见问题

FAQ:下面整理几个读者在使用 GeoPandas 和 Matplotlib 出图时经常遇到的问题。

1. Python空间分析出图一定要用 GeoPandas 吗?

不一定。如果你处理的是栅格数据,可能会用 Rasterio、rioxarray 和 Matplotlib;如果做交互式 Web 地图,可能会用 Folium、Plotly 或 Kepler.gl。但对于 Shapefile、GeoPackage、GeoJSON 等矢量数据,GeoPandas 是最直接的选择。

2. python画空间图为什么图层叠不上?

最常见原因是坐标系不一致。先检查 crstotal_bounds。如果一个图层范围是经纬度数值,另一个是米级投影坐标,直接叠加就会错位。

3. GeoPandas plot 可以做专业制图吗?

可以完成基础专题图、分析结果图和批量报告图,但如果需要比例尺、指北针、复杂标注、地图整饰和印刷级版面,QGIS 或 ArcGIS Pro 更合适。实际工作中常见做法是 Python 自动分析和初步出图,桌面 GIS 做最终排版。

4. Matplotlib 图例太丑怎么办?

可以通过 legend_kwds 调整标题、位置和显示方式。对于更复杂的图例,可以用 Matplotlib 自定义图例对象。但快速出图阶段,建议先保证图例能解释字段含义,不必一开始追求复杂版式。

5. 为什么按字段设色后整张图颜色一样?

通常有三种原因:字段值全都一样、字段是字符串而不是数值、统计结果没有正确合并到空间图层。可以检查:

print(districts_stat["poi_count"].describe())
print(districts_stat["poi_count"].dtype)
print(districts_stat[["name", "poi_count"]].head())

6. 可以批量输出多个城市的空间分析图吗?

可以。把读取数据、空间分析、出图保存封装成函数,然后循环城市列表即可。批量出图时尤其要注意关闭画布,避免内存占用持续增加。

for city in city_list:
    fig, ax = plt.subplots(figsize=(8, 8))
    city_gdf = districts_stat[districts_stat["city"] == city]
    city_gdf.plot(column="poi_count", ax=ax, legend=True, cmap="YlOrRd")
    ax.set_title(f"{city} POI数量分布")
    ax.set_axis_off()
    plt.savefig(f"output/{city}_poi_count.png", dpi=300, bbox_inches="tight")
    plt.close(fig)

结论:用 GeoPandas 加 Matplotlib 建立可复用的出图模板

结论:Python空间分析快速出图的核心思路很简单:用 GeoPandas 管理空间数据和分析结果,用 Matplotlib 控制图面表达和导出质量。只要把坐标系、图层顺序、字段设色、图例和分辨率这几个点处理好,就能稳定产出清晰可用的空间分析结果图。

对于日常 GIS 学习和项目初稿,建议先建立一套自己的 python画空间图模板:读取数据、统一坐标系、执行分析、分级设色、叠加图层、导出 PNG。后续无论是 POI 分析、行政区统计、设施覆盖评估,还是批量城市对比,都可以在这个模板上快速改造。

如果最终成果需要发表、打印或交付甲方,可以把 Python 生成的统计结果继续导入 QGIS 或 ArcGIS Pro 做精细排版。这样既保留 Python 空间分析的自动化效率,也能获得更规范的地图制图效果。