znlgis 博客

GIS开发与技术分享

第21章:静态可视化(Matplotlib)

地图可视化是地理信息系统的核心功能之一。GeoPandas 基于 Matplotlib 提供了强大而灵活的静态地图绑制能力,使我们能够通过简洁的 Python 代码创建专业级的地理数据可视化作品。本章将系统介绍 GeoPandas 的静态可视化方法,从基础绑图到高级样式定制,帮助读者掌握地理数据可视化的完整工作流程。


21.1 GeoPandas 可视化概述

21.1.1 可视化在 GIS 中的重要性

地图可视化是将抽象的地理数据转化为直观图形表达的过程。在 GIS 分析工作流中,可视化承担着多重关键角色:

  • 数据探索:快速了解数据的空间分布特征
  • 质量检查:发现数据中的异常值和错误
  • 结果展示:将分析结果以直观方式呈现给决策者
  • 报告制作:生成用于出版和报告的高质量地图
# 导入核心库
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np

# 读取示例数据
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
print(f"数据集包含 {len(world)} 个国家/地区")
print(f"几何类型: {world.geom_type.unique()}")
print(f"坐标参考系: {world.crs}")

输出:

数据集包含 177 个国家/地区
几何类型: ['MultiPolygon' 'Polygon']
坐标参考系: EPSG:4326

21.1.2 GeoPandas 可视化架构

GeoPandas 的可视化功能建立在 Matplotlib 之上,采用分层架构设计:

层次 组件 说明
顶层 GeoDataFrame.plot() GeoPandas 提供的高级绑图接口
中间层 Matplotlib Axes 坐标轴对象,管理图形元素
底层 Matplotlib Figure 画布对象,控制整体布局
# 可视化架构示例
fig, ax = plt.subplots(figsize=(10, 6))

# GeoPandas 的 plot() 方法返回 matplotlib 的 Axes 对象
ax = world.plot(ax=ax, color='lightblue', edgecolor='gray')

# 可以继续使用 matplotlib 的方法自定义
ax.set_title('世界地图', fontsize=16)
ax.set_xlabel('经度')
ax.set_ylabel('纬度')

plt.tight_layout()
plt.savefig('world_map.png', dpi=150)
plt.show()

注意: GeoPandas 的 plot() 方法本质上是对 Matplotlib 的封装,因此所有 Matplotlib 的自定义功能都可以在 GeoPandas 图形上使用。


21.2 plot() 基础用法

21.2.1 基本绑图

GeoDataFrame.plot() 是 GeoPandas 中最常用的可视化方法,只需一行代码即可生成地图:

# 最简单的绑图方式
world.plot()
plt.title('基础世界地图')
plt.show()

plot() 方法的常用基础参数:

参数 类型 说明 默认值
color str 统一填充颜色 None
edgecolor str 边界线颜色 None
linewidth float 边界线宽度 None
alpha float 透明度 (0-1) None
figsize tuple 图形大小 (宽, 高) None
# 自定义基础样式
world.plot(
    color='#a8d8a8',     # 浅绿色填充
    edgecolor='#333333', # 深灰色边界
    linewidth=0.5        # 细边界线
)
plt.title('自定义样式的世界地图')
plt.show()

21.2.2 控制图形大小

通过 figsize 参数控制输出图形的尺寸,单位为英寸:

# 创建不同尺寸的地图
fig, axes = plt.subplots(1, 2, figsize=(16, 5))

# 小尺寸效果
world.plot(ax=axes[0], color='skyblue', edgecolor='white', linewidth=0.3)
axes[0].set_title('标准视图', fontsize=12)

# 使用 aspect 参数控制纵横比
world.plot(ax=axes[1], color='salmon', edgecolor='white', linewidth=0.3, aspect='equal')
axes[1].set_title('等比例视图', fontsize=12)

plt.tight_layout()
plt.show()
# 也可以直接在 plot() 中指定 figsize
ax = world.plot(figsize=(12, 8), color='wheat', edgecolor='gray')
ax.set_title('大尺寸地图 (12×8 英寸)')
plt.show()

注意: figsize 的单位是英寸,最终像素大小 = figsize × dpi。例如 figsize=(10, 6)dpi=150 下将生成 1500×900 像素的图片。


21.3 按属性分类着色

21.3.1 column 参数

column 参数是 GeoPandas 可视化中最强大的功能之一,它允许根据属性值对几何要素进行着色:

# 按大洲着色
world.plot(
    column='continent',
    figsize=(12, 6),
    edgecolor='white',
    linewidth=0.5,
    legend=True
)
plt.title('按大洲分类着色')
plt.show()
# 按人口数量着色
world.plot(
    column='pop_est',
    figsize=(12, 6),
    edgecolor='gray',
    linewidth=0.3,
    legend=True
)
plt.title('按人口数量着色')
plt.show()

21.3.2 分类数据与连续数据着色

GeoPandas 会自动根据数据类型选择不同的着色策略:

# 分类数据 - 使用离散色彩
fig, axes = plt.subplots(1, 2, figsize=(18, 6))

# 分类数据:每个类别对应一种颜色
world.plot(
    ax=axes[0],
    column='continent',
    categorical=True,
    edgecolor='white',
    linewidth=0.3,
    legend=True,
    legend_kwds={'fontsize': 8, 'loc': 'lower left'}
)
axes[0].set_title('分类数据着色(大洲)', fontsize=13)

# 连续数据:使用渐变色彩映射
world.plot(
    ax=axes[1],
    column='gdp_md_est',
    edgecolor='white',
    linewidth=0.3,
    legend=True,
    legend_kwds={'label': 'GDP(百万美元)', 'shrink': 0.6}
)
axes[1].set_title('连续数据着色(GDP)', fontsize=13)

plt.tight_layout()
plt.show()

注意: 对于分类数据,设置 categorical=True 可以确保 GeoPandas 使用离散色彩方案;对于连续数据,GeoPandas 会自动使用渐变色彩映射。


21.4 色彩方案(cmap)

21.4.1 常用色彩映射表

Matplotlib 提供了丰富的色彩映射表(colormap),通过 cmap 参数指定:

类别 色彩映射名称 适用场景
顺序型 viridis, plasma, inferno, magma 单变量连续数据
顺序型 Blues, Greens, Reds, Oranges 单色系渐变
顺序型 YlOrRd, YlGnBu, RdPu, BuGn 多色系渐变
发散型 RdYlGn, RdBu, coolwarm, seismic 有中心值的数据
定性型 Set1, Set2, Set3, Paired, tab10 分类数据
循环型 twilight, hsv 周期性数据
# 对比不同色彩映射的效果
cmaps = ['viridis', 'plasma', 'YlOrRd', 'Blues', 'RdYlGn', 'coolwarm']

fig, axes = plt.subplots(2, 3, figsize=(18, 10))
axes = axes.flatten()

for ax, cmap_name in zip(axes, cmaps):
    world.plot(
        ax=ax,
        column='pop_est',
        cmap=cmap_name,
        edgecolor='gray',
        linewidth=0.2,
        legend=True,
        legend_kwds={'shrink': 0.5}
    )
    ax.set_title(f'cmap="{cmap_name}"', fontsize=11)
    ax.set_axis_off()

plt.suptitle('不同色彩映射方案对比', fontsize=16, y=1.02)
plt.tight_layout()
plt.show()

21.4.2 自定义颜色

除了使用预定义的色彩映射,还可以自定义颜色方案:

from matplotlib.colors import LinearSegmentedColormap, ListedColormap

# 方法1:使用自定义颜色列表创建离散色彩映射
custom_colors = ['#2166ac', '#67a9cf', '#d1e5f0', '#fddbc7', '#ef8a62', '#b2182b']
custom_cmap = ListedColormap(custom_colors)

world.plot(
    column='continent',
    cmap=custom_cmap,
    figsize=(12, 6),
    edgecolor='white',
    linewidth=0.3
)
plt.title('自定义离散色彩映射')
plt.show()
# 方法2:创建自定义连续色彩映射
colors_list = ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8']
custom_continuous = LinearSegmentedColormap.from_list('custom', colors_list)

world.plot(
    column='gdp_md_est',
    cmap=custom_continuous,
    figsize=(12, 6),
    edgecolor='gray',
    linewidth=0.2,
    legend=True
)
plt.title('自定义连续色彩映射')
plt.show()
# 方法3:反转色彩映射(在名称后加 _r)
fig, axes = plt.subplots(1, 2, figsize=(16, 5))

world.plot(ax=axes[0], column='pop_est', cmap='YlOrRd',
           edgecolor='gray', linewidth=0.2, legend=True,
           legend_kwds={'shrink': 0.5})
axes[0].set_title('YlOrRd(正序)')

world.plot(ax=axes[1], column='pop_est', cmap='YlOrRd_r',
           edgecolor='gray', linewidth=0.2, legend=True,
           legend_kwds={'shrink': 0.5})
axes[1].set_title('YlOrRd_r(反转)')

plt.tight_layout()
plt.show()

21.5 分类方案(scheme)

21.5.1 使用 mapclassify 分类

对于连续数值数据,直接使用线性色彩映射可能无法有效展示数据分布。scheme 参数允许使用 mapclassify 库进行数据分类:

# 安装 mapclassify(如尚未安装)
# pip install mapclassify

import mapclassify

# 查看人口数据的分布
print(world['pop_est'].describe())

输出:

count    1.770000e+02
mean     3.979760e+07
std      1.380552e+08
min      1.400000e+02
25%      3.228608e+06
50%      8.776364e+06
75%      2.879504e+07
max      1.379302e+09
Name: pop_est, dtype: float64
# 使用分类方案进行可视化
world.plot(
    column='pop_est',
    scheme='NaturalBreaks',
    k=5,
    cmap='YlOrRd',
    figsize=(12, 6),
    edgecolor='gray',
    linewidth=0.3,
    legend=True,
    legend_kwds={'fontsize': 9, 'loc': 'lower left'}
)
plt.title('自然断点法分类(5类)')
plt.show()

21.5.2 常用分类方法

mapclassify 提供了多种常用的地图分类方法:

分类方法 scheme 名称 说明
自然断点法 NaturalBreaks 基于数据的自然聚类,最小化组内方差
分位数法 Quantiles 每个类别包含相同数量的要素
等间距法 EqualInterval 将数据范围等分为 k 个区间
Fisher-Jenks FisherJenks 类似自然断点,计算更精确
标准差法 StdMean 基于均值和标准差进行分类
手动分类 UserDefined 用户自定义断点值
# 对比不同分类方法的效果
schemes = ['NaturalBreaks', 'Quantiles', 'EqualInterval', 'FisherJenks']

fig, axes = plt.subplots(2, 2, figsize=(16, 10))
axes = axes.flatten()

for ax, scheme in zip(axes, schemes):
    world.plot(
        ax=ax,
        column='pop_est',
        scheme=scheme,
        k=5,
        cmap='YlOrRd',
        edgecolor='gray',
        linewidth=0.2,
        legend=True,
        legend_kwds={'fontsize': 7, 'loc': 'lower left'}
    )
    ax.set_title(f'分类方法: {scheme}', fontsize=12)
    ax.set_axis_off()

plt.suptitle('不同分类方法对比', fontsize=15)
plt.tight_layout()
plt.show()
# 使用自定义断点进行分类
world.plot(
    column='pop_est',
    scheme='UserDefined',
    classification_kwds={'bins': [1e6, 1e7, 5e7, 1e8, 5e8, 1.5e9]},
    cmap='RdPu',
    figsize=(12, 6),
    edgecolor='gray',
    linewidth=0.3,
    legend=True,
    legend_kwds={'fontsize': 9, 'loc': 'lower left'}
)
plt.title('自定义断点分类')
plt.show()

注意: 使用 scheme 参数时需要安装 mapclassify 库。参数 k 用于指定分类数量,默认为 5。


21.6 图例控制

21.6.1 legend 参数

通过 legend=True 启用图例显示:

# 基本图例
world.plot(
    column='continent',
    figsize=(12, 6),
    edgecolor='white',
    linewidth=0.3,
    legend=True
)
plt.title('默认图例位置')
plt.show()
# 连续数据的色带图例
world.plot(
    column='gdp_md_est',
    figsize=(12, 6),
    cmap='viridis',
    edgecolor='gray',
    linewidth=0.2,
    legend=True,
    legend_kwds={
        'label': 'GDP(百万美元)',
        'orientation': 'horizontal',
        'shrink': 0.5,
        'pad': 0.05
    }
)
plt.title('水平色带图例')
plt.show()

21.6.2 legend_kwds 自定义图例样式

legend_kwds 参数接受一个字典,用于精细控制图例外观:

# 分类数据的图例自定义
world.plot(
    column='continent',
    figsize=(14, 7),
    cmap='Set2',
    edgecolor='white',
    linewidth=0.3,
    legend=True,
    legend_kwds={
        'loc': 'lower left',
        'fontsize': 9,
        'frameon': True,
        'framealpha': 0.8,
        'edgecolor': 'gray',
        'title': '大洲',
        'title_fontsize': 11
    }
)
plt.title('自定义图例样式')
plt.show()
# 连续数据色带图例的高级定制
fig, ax = plt.subplots(figsize=(12, 6))

world.plot(
    ax=ax,
    column='pop_est',
    cmap='YlOrRd',
    scheme='Quantiles',
    k=5,
    edgecolor='gray',
    linewidth=0.2,
    legend=True,
    legend_kwds={
        'loc': 'lower left',
        'fontsize': 8,
        'title': '人口数量',
        'title_fontsize': 10,
        'frameon': True,
        'fancybox': True,
        'shadow': True
    }
)
ax.set_title('带分类方案的自定义图例', fontsize=14)
ax.set_axis_off()
plt.tight_layout()
plt.show()

注意: 对于连续数据(无 scheme),图例为色带(colorbar),legend_kwds 传递给 fig.colorbar();对于分类数据或使用 scheme 时,图例为离散图例,legend_kwds 传递给 ax.legend()


21.7 多图层叠加显示

21.7.1 使用 ax 参数叠加图层

在 GIS 中,地图通常由多个图层叠加组成。GeoPandas 通过共享 ax 参数实现图层叠加:

from shapely.geometry import Point

# 准备多个图层
countries = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))

# 叠加绘制
fig, ax = plt.subplots(figsize=(12, 6))

# 第一层:国家面
countries.plot(ax=ax, color='lightyellow', edgecolor='gray', linewidth=0.5)

# 第二层:城市点
cities.plot(ax=ax, color='red', markersize=10, zorder=5)

ax.set_title('国家与城市叠加显示', fontsize=14)
ax.set_xlabel('经度')
ax.set_ylabel('纬度')
plt.tight_layout()
plt.show()

21.7.2 控制图层顺序与透明度

通过 zorderalpha 参数控制图层的叠加效果:

# 创建模拟的缓冲区图层
cities_buffered = cities.copy()
cities_buffered['geometry'] = cities.geometry.buffer(5)

fig, ax = plt.subplots(figsize=(12, 6))

# 底层:国家
countries.plot(ax=ax, color='#f0f0f0', edgecolor='#cccccc', linewidth=0.5, zorder=1)

# 中间层:城市缓冲区(半透明)
cities_buffered.plot(ax=ax, color='lightskyblue', alpha=0.4, edgecolor='steelblue',
                     linewidth=0.5, zorder=2)

# 顶层:城市点
cities.plot(ax=ax, color='darkred', markersize=15, zorder=3, label='城市')

ax.set_title('多图层叠加与透明度控制', fontsize=14)
ax.legend(loc='lower left')
plt.tight_layout()
plt.show()
# 使用不同颜色区分大洲,叠加河流或边界
fig, ax = plt.subplots(figsize=(14, 7))

# 按大洲着色
countries.plot(
    ax=ax,
    column='continent',
    cmap='Pastel2',
    edgecolor='white',
    linewidth=0.5,
    legend=True,
    legend_kwds={'loc': 'lower left', 'fontsize': 8}
)

# 叠加城市
cities.plot(
    ax=ax,
    color='black',
    markersize=8,
    zorder=5
)

ax.set_title('大洲分类着色与城市叠加', fontsize=14)
ax.set_axis_off()
plt.tight_layout()
plt.show()

21.8 自定义样式

21.8.1 edgecolor 与 linewidth

边界线的颜色和宽度是地图样式的重要元素:

# 不同边界线样式对比
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 无边界线
world.plot(ax=axes[0], color='lightblue', edgecolor='none')
axes[0].set_title('无边界线 (edgecolor="none")')

# 细边界线
world.plot(ax=axes[1], color='lightblue', edgecolor='gray', linewidth=0.5)
axes[1].set_title('细边界线 (linewidth=0.5)')

# 粗边界线
world.plot(ax=axes[2], color='lightblue', edgecolor='black', linewidth=2)
axes[2].set_title('粗边界线 (linewidth=2)')

for ax in axes:
    ax.set_axis_off()

plt.tight_layout()
plt.show()

21.8.2 alpha 透明度

alpha 参数控制要素的透明度,范围从 0(完全透明)到 1(完全不透明):

# 透明度对比
fig, axes = plt.subplots(1, 4, figsize=(20, 4))
alphas = [0.2, 0.4, 0.7, 1.0]

for ax, a in zip(axes, alphas):
    world.plot(ax=ax, color='steelblue', edgecolor='navy',
               linewidth=0.5, alpha=a)
    ax.set_title(f'alpha={a}')
    ax.set_axis_off()

plt.suptitle('不同透明度效果对比', fontsize=14)
plt.tight_layout()
plt.show()

21.8.3 markersize 点大小

对于点类型的几何数据,markersize 参数控制点的大小:

cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 固定大小
cities.plot(ax=axes[0], markersize=5, color='red')
axes[0].set_title('固定大小 (markersize=5)')

# 较大的点
cities.plot(ax=axes[1], markersize=30, color='blue', alpha=0.6)
axes[1].set_title('较大的点 (markersize=30)')

# 根据属性设置动态大小
cities['marker_col'] = np.random.randint(10, 100, len(cities))
cities.plot(ax=axes[2], markersize=cities['marker_col'], color='green', alpha=0.5)
axes[2].set_title('动态大小(基于属性)')

for ax in axes:
    world.plot(ax=ax, color='lightyellow', edgecolor='gray', linewidth=0.3, zorder=0)

plt.tight_layout()
plt.show()
# 使用 marker 参数改变点的形状
fig, ax = plt.subplots(figsize=(12, 6))

world.plot(ax=ax, color='#f5f5f5', edgecolor='gray', linewidth=0.3)
cities.plot(ax=ax, marker='^', markersize=20, color='crimson',
            edgecolor='black', linewidth=0.5)

ax.set_title('使用三角形标记的城市', fontsize=13)
ax.set_axis_off()
plt.show()

21.9 标注与注记

21.9.1 添加文字标注

在地图上添加文字标注可以增强信息表达:

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

# 绘制底图
world.plot(ax=ax, color='lightyellow', edgecolor='gray', linewidth=0.5)

# 为主要城市添加标注
cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))

cities.plot(ax=ax, color='red', markersize=15, zorder=5)

# 使用 annotate 添加标注
for idx, row in cities.head(10).iterrows():
    ax.annotate(
        text=row['name'],
        xy=(row.geometry.x, row.geometry.y),
        xytext=(5, 5),
        textcoords='offset points',
        fontsize=7,
        color='darkblue',
        fontweight='bold'
    )

ax.set_title('城市标注示例', fontsize=14)
ax.set_axis_off()
plt.tight_layout()
plt.show()
# 使用 ax.text 方法添加自由文字
fig, ax = plt.subplots(figsize=(12, 6))

world.plot(ax=ax, column='continent', cmap='Set3', edgecolor='white', linewidth=0.3)

# 在指定坐标添加文字
ax.text(0, 0, '赤道', fontsize=9, ha='center', va='bottom',
        color='red', fontstyle='italic')

# 添加水平参考线表示赤道
ax.axhline(y=0, color='red', linewidth=0.5, linestyle='--', alpha=0.5)

ax.set_title('添加参考线和文字标注', fontsize=14)
ax.set_axis_off()
plt.show()

21.9.2 添加标题和坐标轴标签

fig, ax = plt.subplots(figsize=(12, 7))

world.plot(ax=ax, column='pop_est', cmap='YlOrRd',
           edgecolor='gray', linewidth=0.3,
           legend=True, legend_kwds={'shrink': 0.6, 'label': '人口数量'})

# 设置标题(支持多级标题)
ax.set_title('世界人口分布图', fontsize=16, fontweight='bold', pad=15)

# 设置坐标轴标签
ax.set_xlabel('经度 (°)', fontsize=12)
ax.set_ylabel('纬度 (°)', fontsize=12)

# 添加数据来源注释
ax.text(0.99, 0.01, '数据来源: Natural Earth',
        transform=ax.transAxes,
        fontsize=8, ha='right', va='bottom',
        fontstyle='italic', color='gray')

plt.tight_layout()
plt.show()
# 设置坐标轴范围(聚焦特定区域)
fig, ax = plt.subplots(figsize=(10, 8))

world.plot(ax=ax, color='lightyellow', edgecolor='gray', linewidth=0.5)
cities.plot(ax=ax, color='red', markersize=10)

# 聚焦东亚地区
ax.set_xlim([70, 150])
ax.set_ylim([10, 55])

ax.set_title('东亚地区', fontsize=14)
ax.set_xlabel('经度')
ax.set_ylabel('纬度')
ax.grid(True, linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()

21.10 底图叠加(contextily)

21.10.1 安装与基础用法

contextily 库可以为 GeoPandas 地图添加在线瓦片底图:

# 安装 contextily
# pip install contextily

import contextily as ctx

# 注意:contextily 需要 Web Mercator 投影 (EPSG:3857)
world_mercator = world.to_crs(epsg=3857)

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

# 绘制面数据(半透明)
world_mercator.plot(ax=ax, alpha=0.4, edgecolor='black', linewidth=0.5)

# 添加底图
ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)

ax.set_title('叠加 OpenStreetMap 底图', fontsize=14)
ax.set_axis_off()
plt.tight_layout()
plt.show()

注意: 使用 contextily 之前必须将数据投影转换为 EPSG:3857(Web Mercator),否则底图将无法正确对齐。

21.10.2 底图提供商选择

contextily 支持多种底图提供商:

提供商 代码 特点
OpenStreetMap ctx.providers.OpenStreetMap.Mapnik 通用地图,信息丰富
Stamen Toner ctx.providers.Stamen.Toner 黑白色调,适合叠加数据
Stamen Terrain ctx.providers.Stamen.Terrain 地形底图
CartoDB Positron ctx.providers.CartoDB.Positron 浅色底图,适合数据展示
CartoDB DarkMatter ctx.providers.CartoDB.DarkMatter 深色底图,视觉冲击强
Esri WorldImagery ctx.providers.Esri.WorldImagery 卫星影像
# 使用不同底图提供商
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

providers = [
    (ctx.providers.CartoDB.Positron, 'CartoDB Positron'),
    (ctx.providers.CartoDB.DarkMatter, 'CartoDB DarkMatter')
]

for ax, (provider, name) in zip(axes, providers):
    world_mercator.plot(ax=ax, alpha=0.3, edgecolor='red', linewidth=0.5)
    ctx.add_basemap(ax, source=provider)
    ax.set_title(name, fontsize=12)
    ax.set_axis_off()

plt.tight_layout()
plt.show()
# 控制底图的缩放级别
fig, ax = plt.subplots(figsize=(10, 8))

# 筛选中国区域
china = world_mercator[world_mercator['name'] == 'China']
china.plot(ax=ax, alpha=0.3, edgecolor='red', linewidth=1)

# zoom 参数控制底图详细程度
ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, zoom=4)

ax.set_title('中国区域底图叠加', fontsize=14)
ax.set_axis_off()
plt.tight_layout()
plt.show()

21.11 子图与组合图

21.11.1 plt.subplots 创建子图

使用 Matplotlib 的子图功能创建多面板地图:

# 创建 2x2 子图布局
fig, axes = plt.subplots(2, 2, figsize=(16, 12))

# 亚洲
asia = world[world['continent'] == 'Asia']
asia.plot(ax=axes[0, 0], color='#ff9999', edgecolor='white', linewidth=0.5)
axes[0, 0].set_title('亚洲', fontsize=13)

# 欧洲
europe = world[world['continent'] == 'Europe']
europe.plot(ax=axes[0, 1], color='#99ccff', edgecolor='white', linewidth=0.5)
axes[0, 1].set_title('欧洲', fontsize=13)

# 非洲
africa = world[world['continent'] == 'Africa']
africa.plot(ax=axes[1, 0], color='#99ff99', edgecolor='white', linewidth=0.5)
axes[1, 0].set_title('非洲', fontsize=13)

# 南美洲
south_america = world[world['continent'] == 'South America']
south_america.plot(ax=axes[1, 1], color='#ffcc99', edgecolor='white', linewidth=0.5)
axes[1, 1].set_title('南美洲', fontsize=13)

for ax in axes.flatten():
    ax.set_axis_off()

plt.suptitle('各大洲地图', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

21.11.2 不同属性对比展示

# 对比不同属性的空间分布
fig, axes = plt.subplots(1, 3, figsize=(20, 6))

# 人口分布
world.plot(
    ax=axes[0], column='pop_est', cmap='YlOrRd',
    scheme='Quantiles', k=5,
    edgecolor='gray', linewidth=0.2,
    legend=True, legend_kwds={'fontsize': 7, 'loc': 'lower left'}
)
axes[0].set_title('人口分布', fontsize=13)

# GDP 分布
world.plot(
    ax=axes[1], column='gdp_md_est', cmap='Blues',
    scheme='Quantiles', k=5,
    edgecolor='gray', linewidth=0.2,
    legend=True, legend_kwds={'fontsize': 7, 'loc': 'lower left'}
)
axes[1].set_title('GDP 分布', fontsize=13)

# 人均 GDP
world_copy = world.copy()
world_copy['gdp_per_cap'] = world_copy['gdp_md_est'] / (world_copy['pop_est'] / 1e6)
world_copy.plot(
    ax=axes[2], column='gdp_per_cap', cmap='Greens',
    scheme='Quantiles', k=5,
    edgecolor='gray', linewidth=0.2,
    legend=True, legend_kwds={'fontsize': 7, 'loc': 'lower left'}
)
axes[2].set_title('人均 GDP', fontsize=13)

for ax in axes:
    ax.set_axis_off()

plt.suptitle('经济与人口指标空间对比', fontsize=15, fontweight='bold')
plt.tight_layout()
plt.show()
# 使用 gridspec 实现不规则子图布局
import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(16, 10))
gs = gridspec.GridSpec(2, 3, figure=fig)

# 主图占据左侧 2/3
ax_main = fig.add_subplot(gs[:, :2])
world.plot(ax=ax_main, column='continent', cmap='Set2',
           edgecolor='white', linewidth=0.3)
ax_main.set_title('世界地图', fontsize=14)
ax_main.set_axis_off()

# 右上小图
ax_top = fig.add_subplot(gs[0, 2])
europe.plot(ax=ax_top, color='#99ccff', edgecolor='white', linewidth=0.5)
ax_top.set_title('欧洲', fontsize=11)
ax_top.set_axis_off()

# 右下小图
ax_bottom = fig.add_subplot(gs[1, 2])
asia.plot(ax=ax_bottom, color='#ff9999', edgecolor='white', linewidth=0.5)
ax_bottom.set_title('亚洲', fontsize=11)
ax_bottom.set_axis_off()

plt.tight_layout()
plt.show()

21.12 导出地图

21.12.1 保存为图片

GeoPandas 生成的地图可以通过 Matplotlib 导出为多种格式:

fig, ax = plt.subplots(figsize=(12, 6))

world.plot(
    ax=ax,
    column='continent',
    cmap='Set2',
    edgecolor='white',
    linewidth=0.3,
    legend=True,
    legend_kwds={'loc': 'lower left', 'fontsize': 8}
)
ax.set_title('世界地图 - 按大洲着色', fontsize=14)
ax.set_axis_off()

# 保存为 PNG 格式
fig.savefig('world_map.png', dpi=300, bbox_inches='tight',
            facecolor='white', edgecolor='none')

# 保存为 SVG 矢量格式(适合出版)
fig.savefig('world_map.svg', bbox_inches='tight',
            facecolor='white', edgecolor='none')

# 保存为 PDF 格式(适合打印)
fig.savefig('world_map.pdf', bbox_inches='tight',
            facecolor='white', edgecolor='none')

plt.show()
print("地图已导出为 PNG、SVG 和 PDF 格式")

输出:

地图已导出为 PNG、SVG 和 PDF 格式

常用导出格式对比:

格式 扩展名 类型 适用场景
PNG .png 位图 网页展示、演示文稿
JPEG .jpg 位图 照片类地图、文件较小
SVG .svg 矢量 网页嵌入、可缩放
PDF .pdf 矢量 出版、打印
TIFF .tiff 位图 高质量印刷

21.12.2 控制分辨率

dpi(dots per inch)参数决定了导出图片的分辨率:

# 不同分辨率对比
fig, ax = plt.subplots(figsize=(10, 5))
world.plot(ax=ax, color='lightblue', edgecolor='gray', linewidth=0.5)
ax.set_title('分辨率测试')
ax.set_axis_off()

# 低分辨率(适合快速预览)
fig.savefig('map_72dpi.png', dpi=72, bbox_inches='tight')

# 标准分辨率(屏幕显示)
fig.savefig('map_150dpi.png', dpi=150, bbox_inches='tight')

# 高分辨率(出版印刷)
fig.savefig('map_300dpi.png', dpi=300, bbox_inches='tight')

plt.show()
DPI 用途 10×5英寸图片大小(约)
72 快速预览 720×360 像素
150 屏幕展示 1500×750 像素
300 出版印刷 3000×1500 像素
600 高质量印刷 6000×3000 像素
# 导出透明背景的地图
fig, ax = plt.subplots(figsize=(10, 5))
world.plot(ax=ax, color='steelblue', edgecolor='white', linewidth=0.5)
ax.set_axis_off()

# transparent=True 导出透明背景
fig.savefig('map_transparent.png', dpi=300,
            bbox_inches='tight', transparent=True)

print("已导出透明背景地图")
plt.show()

输出:

已导出透明背景地图

注意: 高分辨率图片文件较大,建议根据实际用途选择合适的 DPI 值。出版物通常要求 300 DPI 以上,网页展示 150 DPI 即可。


21.13 本章小结

本章系统介绍了 GeoPandas 基于 Matplotlib 的静态可视化方法。以下是各节核心内容的回顾:

主题 方法/参数 关键要点
基础绘图 gdf.plot() 一行代码即可生成地图,支持 figsize 控制大小
属性着色 column 参数 支持分类数据和连续数据的自动着色
色彩方案 cmap 参数 丰富的预定义色彩映射,支持自定义和反转
分类方案 scheme 参数 配合 mapclassify 实现多种数据分类方法
图例控制 legend, legend_kwds 灵活定制图例位置、样式和标签
图层叠加 ax 参数 通过共享 Axes 对象实现多图层叠加
样式定制 edgecolor, alpha, markersize 精细控制边界线、透明度和点大小
标注注记 annotate, text 为地图添加文字标注和标题信息
底图叠加 contextily 添加在线瓦片底图,需转换为 EPSG:3857
子图组合 plt.subplots 创建多面板对比地图
导出地图 savefig 支持 PNG/SVG/PDF 多种格式,dpi 控制分辨率

核心要诀:

  • 选择合适的色彩方案:顺序型用于连续数据,发散型用于有中心值的数据,定性型用于分类数据
  • 使用分类方案:通过 scheme 参数配合 mapclassify 可以更有效地展示数据分布差异
  • 图层叠加是关键:通过共享 ax 对象,可以将多个数据集叠加在同一张地图上
  • 注意坐标参考系:使用 contextily 底图时,务必先将数据转换为 EPSG:3857 投影
  • 导出时关注分辨率:根据用途选择合适的 DPI 和文件格式

在下一章(第22章)中,我们将学习交互式可视化方法,使用 Folium 和 Plotly 等库创建可以在浏览器中交互操作的动态地图。