GeoPandas读取Shapefile?GIS地图可视化怎么画?

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

很多同学第一次用 Python 做空间数据分析时,都会遇到“GeoPandas读取Shapefile?GIS地图可视化怎么画?”这个问题:文件明明在本地,为什么读不出来?读出来以后为什么中文乱码、坐标不对、地图空白?本文用一个可复现的 GeoPandas 工作流,讲清楚 Shapefile 读取、坐标检查、属性查看和 GIS 地图可视化的完整步骤。

GeoPandas读取Shapefile GIS地图可视化流程图
GeoPandas 读取 Shapefile 并完成 GIS 地图可视化的基本流程。

引言:用 GeoPandas 读取 Shapefile 并画出第一张 GIS 地图

GeoPandas 是 Python 生态中最常用的矢量 GIS 数据处理库之一。它把 pandas 的表格能力和 Shapely 的几何对象结合起来,可以直接读取 Shapefile、GeoJSON、GeoPackage 等空间数据,并用几行代码完成地图可视化。

如果你的目标是快速完成一张基础 GIS 地图,核心流程并不复杂:

  1. 准备完整的 Shapefile 文件组。
  2. 使用 geopandas.read_file() 读取数据。
  3. 检查坐标系、字段和几何类型。
  4. 使用 GeoDataFrame.plot() 绘制地图。
  5. 根据需要设置颜色、分类、图例、边界和导出图片。

下面的示例以行政区、道路、地块、采样点等常见 Shapefile 都适用。你只需要把路径和字段名替换成自己的数据即可。

背景:为什么 GeoPandas 读取 Shapefile 经常报错

Shapefile 不是一个单独文件,而是一组文件。最少通常包括:

  • .shp:存储几何图形。
  • .shx:空间索引文件。
  • .dbf:属性表文件。
  • .prj:坐标参考系统信息,虽然不是强制文件,但非常重要。

很多 GeoPandas 读取 Shapefile 失败,并不是代码写错,而是数据文件不完整、路径包含特殊字符、编码不匹配,或者当前 Python 环境缺少底层 GIS 依赖。

最常见的问题包括:

  • 只复制了 .shp 文件,没有复制 .dbf.shx
  • 路径中有中文、空格、云盘同步目录,导致读取异常。
  • Shapefile 属性表使用 GBK 编码,默认 UTF-8 读取后出现中文乱码。
  • .prj 缺失,读取后 crsNone
  • 数据坐标是经纬度,但拿来按米计算面积或距离。
  • 几何对象存在空几何、无效几何,绘图时结果异常。

原理:GeoPandas 读取 Shapefile 后得到的是什么

GeoPandas 读取 Shapefile 后,返回的是一个 GeoDataFrame。你可以把它理解为“带有空间几何列的 pandas DataFrame”。

普通表格数据只有字段和记录,而 GeoDataFrame 多了一个特殊列,通常叫 geometry。这个列中保存点、线、面等几何对象。

对象 作用 常用操作
GeoDataFrame 空间数据表 查看字段、筛选记录、投影转换、绘图
geometry 几何列 存储点、线、面对象
crs 坐标参考系统 判断数据是经纬度坐标还是投影坐标
plot() 基础地图绘制 按字段着色、设置图例、设置边界样式

GeoPandas 的地图可视化本质上是把几何对象交给 Matplotlib 绘制。因此它适合做静态地图、分析过程图、结果检查图。如果要做交互式 WebGIS 地图,则通常会结合 Folium、Leaflet、OpenLayers 或导出为 GeoJSON 后再发布。

步骤:GeoPandas 读取 Shapefile 的完整代码

1. 安装 GeoPandas 环境

推荐使用 conda 安装 GeoPandas,因为它依赖 GDAL、PROJ、Fiona、pyogrio 等 GIS 底层库,直接用 pip 在部分 Windows 环境中容易遇到依赖问题。

conda create -n gis python=3.11
conda activate gis
conda install -c conda-forge geopandas matplotlib pyogrio

如果你习惯使用 pip,也可以尝试:

pip install geopandas matplotlib pyogrio

环境安装后,先检查是否能正常导入:

import geopandas as gpd
import matplotlib.pyplot as plt

print(gpd.__version__)

2. 准备 Shapefile 文件路径

建议把 Shapefile 放在一个路径简单的目录中,例如:

D:/gis_data/city_boundary/city_boundary.shp

不要只放 .shp 文件,应该保证同名文件组都在同一目录下:

city_boundary.shp
city_boundary.shx
city_boundary.dbf
city_boundary.prj

在 Python 中读取时,推荐使用原始字符串,避免反斜杠转义问题:

import geopandas as gpd

shp_path = r"D:/gis_data/city_boundary/city_boundary.shp"
gdf = gpd.read_file(shp_path)

print(gdf.head())
print(gdf.crs)
print(gdf.geometry.geom_type.value_counts())

3. 处理中文字段或属性乱码

如果 GeoPandas 读取 Shapefile 后中文属性显示乱码,可以显式指定编码。国内很多旧 Shapefile 的属性表是 GBK 或 GB18030 编码。

gdf = gpd.read_file(shp_path, encoding="gbk")
print(gdf.head())

如果 gbk 仍然乱码,可以尝试:

gdf = gpd.read_file(shp_path, encoding="gb18030")

注意,编码只影响属性表文字,不会改变几何坐标。如果地图位置不对,应优先检查坐标系,而不是编码。

4. 检查坐标系 CRS

读取成功后,一定要检查 crs。这是 GIS 地图可视化和空间分析中最容易被忽略的一步。

print(gdf.crs)

常见结果示例:

  • EPSG:4326:经纬度坐标,单位是度。
  • EPSG:3857:Web Mercator,常用于在线地图底图。
  • EPSG:4490:CGCS2000 地理坐标系。
  • None:数据没有坐标系信息,需要根据数据来源确认。

如果数据没有 CRS,但你明确知道它是 WGS84 经纬度,可以设置 CRS:

gdf = gdf.set_crs(epsg=4326)

如果数据已有 CRS,只是需要转换到另一个坐标系,应使用 to_crs()

gdf_3857 = gdf.to_crs(epsg=3857)

注意:set_crs() 是“声明当前坐标系”,不会改变坐标值;to_crs() 是“坐标转换”,会改变坐标值。两者不能混用。

5. 用 GeoPandas plot 绘制基础地图

最简单的 GIS 地图可视化只需要一行代码:

import matplotlib.pyplot as plt

gdf.plot()
plt.show()

为了让地图更清晰,可以设置面颜色、边界颜色、线宽和图幅大小:

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

gdf.plot(
    ax=ax,
    facecolor="#d9ecff",
    edgecolor="#2b6cb0",
    linewidth=0.8
)

ax.set_title("Shapefile 地图可视化结果")
ax.set_axis_off()

plt.show()

如果是道路、河流等线数据,可以这样绘制:

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

gdf.plot(
    ax=ax,
    color="#444444",
    linewidth=0.6
)

ax.set_title("线状 Shapefile 可视化")
ax.set_axis_off()

plt.show()

如果是采样点、POI 等点数据,可以设置点大小和颜色:

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

gdf.plot(
    ax=ax,
    color="red",
    markersize=8
)

ax.set_title("点状 Shapefile 可视化")
ax.set_axis_off()

plt.show()

6. 按属性字段进行分级设色

GIS 地图可视化通常不只是把边界画出来,还需要根据属性字段表达差异。例如按人口、面积、等级、类型等字段着色。

先查看字段名:

print(gdf.columns)

假设有一个字段叫 population,可以按该字段绘制专题图:

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

gdf.plot(
    column="population",
    ax=ax,
    legend=True,
    cmap="YlOrRd",
    edgecolor="white",
    linewidth=0.5
)

ax.set_title("按人口字段绘制分级设色图")
ax.set_axis_off()

plt.show()

如果是类别字段,例如 type,可以使用分类图:

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

gdf.plot(
    column="type",
    ax=ax,
    legend=True,
    categorical=True,
    edgecolor="black",
    linewidth=0.3
)

ax.set_title("按类型字段绘制分类地图")
ax.set_axis_off()

plt.show()

7. 叠加多个 Shapefile 图层

实际 GIS 制图往往需要叠加多个图层,例如行政区边界、道路和点位。关键是确保所有图层在同一个坐标系中。

boundary = gpd.read_file(r"D:/gis_data/boundary.shp")
roads = gpd.read_file(r"D:/gis_data/roads.shp")
points = gpd.read_file(r"D:/gis_data/points.shp")

roads = roads.to_crs(boundary.crs)
points = points.to_crs(boundary.crs)

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

boundary.plot(
    ax=ax,
    facecolor="#f7fbff",
    edgecolor="#2171b5",
    linewidth=0.8
)

roads.plot(
    ax=ax,
    color="#636363",
    linewidth=0.5
)

points.plot(
    ax=ax,
    color="#e31a1c",
    markersize=12
)

ax.set_title("行政区、道路和点位叠加图")
ax.set_axis_off()

plt.show()

如果叠加后看不到某个图层,首先检查 CRS 是否一致,其次检查数据范围是否重叠。

8. 导出地图图片

完成可视化后,可以保存为 PNG 或 PDF:

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

gdf.plot(
    ax=ax,
    facecolor="#deebf7",
    edgecolor="#3182bd",
    linewidth=0.6
)

ax.set_title("GeoPandas Shapefile 可视化")
ax.set_axis_off()

plt.savefig(
    "geopandas_shapefile_map.png",
    dpi=300,
    bbox_inches="tight"
)

plt.show()

如果要用于论文、报告或公众号配图,建议设置 dpi=300,并使用 bbox_inches="tight" 去掉多余空白。

常见坑:GeoPandas 读取 Shapefile 和地图可视化排错

1. 只复制了 .shp 文件

这是最常见的问题。Shapefile 必须作为文件组一起使用。如果缺少 .dbf,可能没有属性表;如果缺少 .shx,可能无法正常索引几何;如果缺少 .prj,就无法自动识别坐标系。

解决方法:

  • 从原始数据目录完整复制所有同名文件。
  • 不要手动改其中某一个文件名。
  • 压缩传输时,把整个 Shapefile 文件组一起压缩。

2. 中文路径或编码导致读取失败

新版本 GeoPandas 对中文路径支持已经比过去好很多,但在部分 Windows、旧 GDAL 或复杂环境中仍可能出问题。

建议排查:

  • 把数据移动到 D:/gis_data/ 这类简单路径。
  • 路径不要放在微信、QQ、OneDrive、网盘同步目录中。
  • 属性乱码时尝试 encoding="gbk"encoding="gb18030"

3. 坐标系不一致导致图层错位

如果多个 Shapefile 叠加后不重合,大概率是坐标系不一致。例如一个图层是 EPSG:4326,另一个是投影坐标系。

正确做法是先查看 CRS,再统一到同一个 CRS:

print(boundary.crs)
print(roads.crs)

roads = roads.to_crs(boundary.crs)

不要直接改坐标值,也不要在不知道原始坐标系的情况下随意 set_crs()

4. 地图显示为空白

GeoPandas 绘图空白通常有几种原因:

  • 数据本身为空,len(gdf) 为 0。
  • 几何列为空,gdf.geometry.is_empty 中存在大量 True
  • 坐标值异常,例如经纬度数据出现百万级坐标,或投影坐标被误认为经纬度。
  • 叠加图层时范围差异太大,小图层被挤在一个角落。

可以用下面的代码快速检查:

print(len(gdf))
print(gdf.total_bounds)
print(gdf.geometry.is_empty.sum())
print(gdf.geometry.is_valid.value_counts())

5. 面积和距离计算结果不对

如果 Shapefile 是经纬度坐标,例如 EPSG:4326,直接计算面积和距离通常不符合实际,因为单位是“度”,不是“米”。

应先转换到合适的投影坐标系,再计算面积或距离:

gdf_proj = gdf.to_crs(epsg=3857)
gdf_proj["area_m2"] = gdf_proj.area

对于正式项目,不建议无脑使用 EPSG:3857 计算面积。应根据研究区选择合适的等积投影或本地投影坐标系。

方法比较:GeoPandas、QGIS 和 ArcGIS Pro 该怎么选

方法 适合场景 优点 局限
GeoPandas 批量处理、自动化制图、Python 空间分析 代码可复现,适合和 pandas、NumPy、Matplotlib 结合 交互式编辑能力弱,复杂制图不如专业 GIS 软件直观
QGIS 可视化检查、投影转换、符号化制图、数据编辑 免费开源,图形界面友好,插件丰富 批量自动化需要额外学习表达式、模型构建器或 PyQGIS
ArcGIS Pro 企业级 GIS 项目、规范制图、地理处理模型 工具链完整,制图和空间分析能力强 商业软件,授权和环境成本较高
Folium 或 WebGIS 交互式网页地图展示 适合浏览器交互、点选、弹窗和在线发布 需要考虑数据量、切片、前端性能和服务部署

如果你只是想检查 Shapefile 是否正确,QGIS 更直观;如果你要批量读取多个 Shapefile、统一坐标系、自动输出地图图片,GeoPandas 更适合;如果你要制作高质量版式地图,ArcGIS Pro 和 QGIS 的布局工具会更方便。

检查清单:GeoPandas 读取 Shapefile 前后必查项

  • Shapefile 文件组是否完整:.shp.shx.dbf.prj 是否在同一目录。
  • 文件路径是否简单,是否避免了奇怪符号、过长路径和同步盘目录。
  • 读取后 gdf.head() 是否能看到正常属性。
  • 中文字段或属性是否乱码,是否需要指定 encoding="gbk"
  • gdf.crs 是否为空,是否知道数据原始坐标系。
  • 多个图层叠加前,是否使用 to_crs() 统一坐标系。
  • gdf.total_bounds 是否符合研究区范围。
  • 几何对象是否存在空几何或无效几何。
  • 专题图字段是否为数值字段或合理的类别字段。
  • 导出图片时是否设置了合适的尺寸、分辨率和边界空白。

FAQ:GeoPandas读取Shapefile常见问题

1. GeoPandas 读取 Shapefile 必须安装 GDAL 吗?

GeoPandas 底层需要依赖 GDAL、PROJ、pyogrio 或 Fiona 等 GIS I/O 相关库。普通用户不一定要单独手动安装 GDAL,但安装 GeoPandas 时环境中需要有这些依赖。为了减少 Windows 环境问题,推荐使用 conda-forge 安装。

2. GeoPandas 读取 Shapefile 中文乱码怎么办?

优先尝试在 read_file() 中指定编码:

gdf = gpd.read_file(shp_path, encoding="gbk")

如果仍然乱码,再尝试 gb18030。如果数据来自较新的软件,也可能是 UTF-8 编码。编码问题只影响属性文字,不影响空间坐标。

3. GeoPandas 地图可视化为什么没有底图?

gdf.plot() 只绘制矢量数据本身,不会自动加载高德、OSM 或影像底图。如果需要底图,可以把数据转换到 EPSG:3857 后结合 contextily 使用在线瓦片底图。但这涉及网络访问、瓦片版权和坐标转换问题。

4. Shapefile 没有 .prj 文件还能读取吗?

通常可以读取几何和属性,但 GeoPandas 无法知道它的坐标系,gdf.crs 会是 None。这种情况下必须根据数据来源确认原始 CRS,再使用 set_crs() 声明。不要盲目猜测坐标系。

5. GeoPandas 能不能直接修改 Shapefile 并保存?

可以。修改属性、筛选要素或投影转换后,可以使用 to_file() 保存:

gdf.to_file(
    r"D:/gis_data/output/result.shp",
    encoding="utf-8"
)

但 Shapefile 对字段名长度、字段类型、编码支持都有历史限制。如果是新项目,建议优先考虑 GeoPackage,文件更稳定,也更适合保存中文字段和复杂数据。

6. GeoPandas 读取 Shapefile 后如何只显示某个区域?

可以先按属性筛选,再绘图。例如字段 name 中有城市名称:

sub = gdf[gdf["name"] == "杭州市"]
sub.plot()
plt.show()

也可以根据空间范围裁剪,但需要使用边界图层或指定裁剪框,这属于进一步的空间分析操作。

结论:先读对数据,再画好地图

GeoPandas读取Shapefile并完成GIS地图可视化,关键不在于代码有多复杂,而在于流程是否正确:文件组完整、编码正确、坐标系明确、几何有效、字段适合制图。

实际项目中,建议先用 read_file() 读取 Shapefile,再用 head()crstotal_boundsgeom_type 做基础检查。确认数据没有问题后,再用 plot() 做基础地图、分类图或分级设色图。

如果你是 GIS 初学者,可以把本文的代码保存成一个模板。以后遇到行政区、道路、点位、地块等 Shapefile 数据时,只要替换路径、字段名和符号参数,就能快速完成一张可用的 Python GIS 可视化地图。