第22章:交互式地图(Folium)
在前面的章节中,我们学习了如何使用 Matplotlib 创建静态地图。然而,在实际的 GIS 应用中,交互式地图往往能提供更好的用户体验——用户可以缩放、平移、点击要素查看属性信息。本章将介绍如何使用 GeoPandas 的 explore() 方法和 Folium 库创建功能丰富的交互式 Web 地图。
22.1 交互式地图概述
22.1.1 静态地图 vs 交互式地图
静态地图和交互式地图各有其适用场景,了解它们的区别有助于我们做出正确的选择。
| 特性 | 静态地图 | 交互式地图 |
|---|---|---|
| 缩放平移 | 不支持 | 支持 |
| 属性查询 | 不支持 | 点击/悬停查看 |
| 底图切换 | 不支持 | 支持多种底图 |
| 输出格式 | PNG/SVG/PDF | HTML |
| 适用场景 | 论文、报告、印刷 | Web 应用、数据探索 |
| 渲染方式 | 服务端渲染 | 客户端(浏览器)渲染 |
| 数据量限制 | 较大 | 受浏览器性能限制 |
| 依赖库 | Matplotlib | Folium / Leaflet.js |
# 静态地图示例
import geopandas as gpd
import matplotlib.pyplot as plt
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world.plot(figsize=(12, 6))
plt.title("静态世界地图")
plt.show()
# 交互式地图示例
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
m = world.explore() # 返回一个交互式地图对象
m
22.1.2 Folium 简介与安装
Folium 是一个基于 Leaflet.js 的 Python 库,能够将数据可视化为交互式 Web 地图。GeoPandas 从 0.10 版本开始内置了对 Folium 的支持,通过 explore() 方法即可快速创建交互式地图。
# 安装 Folium
# pip install folium
# 安装 mapclassify(用于分类着色)
# pip install mapclassify
# 验证安装
import folium
import geopandas as gpd
print(f"Folium 版本: {folium.__version__}")
print(f"GeoPandas 版本: {gpd.__version__}")
输出:
Folium 版本: 0.14.0
GeoPandas 版本: 0.14.1
注意:
explore()方法需要额外安装folium和mapclassify两个包。如果只使用基本的交互式地图功能,只需安装folium即可;如果需要按属性分类着色,则还需要mapclassify。
22.2 explore() 基础用法
22.2.1 GeoDataFrame.explore() 方法
explore() 是 GeoPandas 为 GeoDataFrame 提供的快捷方法,可以一行代码生成交互式地图。
import geopandas as gpd
# 读取示例数据
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 最简单的用法——一行代码创建交互式地图
m = world.explore()
m # 在 Jupyter Notebook 中直接显示
# explore() 返回的是 folium.Map 对象
print(type(m))
输出:
<class 'folium.folium.Map'>
# 也可以对筛选后的数据使用 explore()
asia = world[world['continent'] == 'Asia']
asia.explore()
22.2.2 基本参数设置
explore() 方法支持多种参数来定制地图的外观和行为。
# 常用参数一览
m = world.explore(
column='pop_est', # 按人口着色
cmap='YlOrRd', # 颜色映射
legend=True, # 显示图例
legend_kwds={
'caption': '人口数量' # 图例标题
},
style_kwds={
'fillOpacity': 0.7, # 填充透明度
'weight': 1, # 边框宽度
'color': 'gray' # 边框颜色
},
tooltip=['name', 'continent', 'pop_est'], # 悬停提示字段
popup=True, # 点击弹出所有属性
tiles='CartoDB positron', # 底图样式
zoom_start=2, # 初始缩放级别
width='100%', # 地图宽度
height='500px' # 地图高度
)
m
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
column |
str | 用于着色的属性列名 | None |
cmap |
str | Matplotlib 颜色映射名称 | None |
color |
str | 统一填充颜色 | None |
legend |
bool | 是否显示图例 | True |
tooltip |
bool/list/str | 悬停时显示的字段 | True |
popup |
bool/list/str | 点击时弹出的字段 | False |
tiles |
str | 底图瓦片样式 | ‘OpenStreetMap’ |
style_kwds |
dict | 样式参数字典 | None |
zoom_start |
int | 初始缩放级别 | 10 |
m |
folium.Map | 已有地图对象(用于叠加) | None |
22.3 底图选择(tiles)
22.3.1 内置底图选项
Folium 提供了多种内置底图样式,可以通过 tiles 参数指定。
| 底图名称 | 说明 | 适用场景 |
|---|---|---|
OpenStreetMap |
默认底图,信息丰富 | 通用场景 |
CartoDB positron |
浅色简洁底图 | 数据可视化、专题地图 |
CartoDB dark_matter |
深色底图 | 夜景效果、亮色数据展示 |
Stamen Terrain |
地形底图 | 地形分析、自然地理 |
Stamen Toner |
黑白风格 | 简约设计、印刷风格 |
Stamen Watercolor |
水彩风格 | 艺术效果展示 |
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
asia = world[world['continent'] == 'Asia']
# 使用 CartoDB 浅色底图
m1 = asia.explore(
tiles='CartoDB positron',
color='steelblue',
tooltip='name'
)
# 使用 CartoDB 暗色底图
m2 = asia.explore(
tiles='CartoDB dark_matter',
color='#ff6b6b',
tooltip='name'
)
# 使用 Stamen 地形底图
m3 = asia.explore(
tiles='Stamen Terrain',
style_kwds={'fillOpacity': 0.3},
tooltip='name'
)
22.3.2 自定义瓦片服务
除了内置底图,还可以使用自定义瓦片服务 URL。
import folium
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
china = world[world['name'] == 'China']
# 使用自定义瓦片 URL
# 天地图示例(需要申请 token)
tianditu_url = (
'http://t0.tianditu.gov.cn/vec_w/wmts?'
'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0'
'&LAYER=vec&STYLE=default&TILEMATRIXSET=w'
'&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}'
'&tk=YOUR_TOKEN'
)
m = china.explore(
tiles=tianditu_url,
attr='天地图', # 底图版权信息
tooltip='name'
)
m
# 也可以在 folium.Map 中直接设置自定义瓦片
m = folium.Map(
location=[35, 105],
zoom_start=4,
tiles=None # 不使用默认底图
)
# 添加多个底图图层
folium.TileLayer('OpenStreetMap', name='OSM 标准').add_to(m)
folium.TileLayer('CartoDB positron', name='CartoDB 浅色').add_to(m)
folium.TileLayer('CartoDB dark_matter', name='CartoDB 暗色').add_to(m)
# 添加图层控制器以切换底图
folium.LayerControl().add_to(m)
m
注意: 使用第三方瓦片服务时,请遵守其使用条款和访问限制。部分服务(如天地图)需要注册获取 API Token。
22.4 按属性着色
22.4.1 column 和 cmap 参数
通过 column 参数指定着色字段,cmap 参数指定颜色映射方案。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 按 GDP 估值着色
m = world.explore(
column='gdp_md_est', # 使用 GDP 字段着色
cmap='Greens', # 绿色系颜色映射
legend=True,
legend_kwds={
'caption': 'GDP(百万美元)' # 图例标题
},
tiles='CartoDB positron',
tooltip=['name', 'gdp_md_est'],
style_kwds={
'fillOpacity': 0.8,
'weight': 0.5
}
)
m
# 常用颜色映射方案
cmap_options = {
'连续型': ['viridis', 'plasma', 'inferno', 'magma', 'cividis'],
'顺序型': ['Blues', 'Greens', 'Reds', 'YlOrRd', 'YlGnBu'],
'发散型': ['RdYlGn', 'RdBu', 'coolwarm', 'BrBG', 'PiYG'],
'定性型': ['Set1', 'Set2', 'Set3', 'Paired', 'tab10']
}
for category, cmaps in cmap_options.items():
print(f"{category}: {', '.join(cmaps)}")
输出:
连续型: viridis, plasma, inferno, magma, cividis
顺序型: Blues, Greens, Reds, YlOrRd, YlGnBu
发散型: RdYlGn, RdBu, coolwarm, BrBG, PiYG
定性型: Set1, Set2, Set3, Paired, tab10
22.4.2 分类着色与连续着色
GeoPandas 的 explore() 方法支持分类着色和连续着色两种模式。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 分类着色——按大洲着色(分类变量)
m_categorical = world.explore(
column='continent',
cmap='Set2', # 定性颜色映射
legend=True,
tooltip=['name', 'continent'],
tiles='CartoDB positron'
)
# 连续着色——按人口着色(连续变量)
m_continuous = world.explore(
column='pop_est',
cmap='YlOrRd',
scheme='NaturalBreaks', # 自然断点分类法
k=5, # 分为 5 类
legend=True,
legend_kwds={
'caption': '人口数量',
'fmt': '{:.0f}' # 数字格式
},
tiles='CartoDB positron'
)
# 支持的分类方案(需要 mapclassify)
classification_schemes = [
'BoxPlot', # 箱线图分类
'EqualInterval', # 等间隔分类
'FisherJenks', # Fisher-Jenks 分类
'HeadTailBreaks', # 头尾断裂分类
'JenksCaspall', # Jenks-Caspall 分类
'MaximumBreaks', # 最大断裂分类
'NaturalBreaks', # 自然断点分类
'Quantiles', # 分位数分类
'StdMean', # 标准差分类
]
for scheme in classification_schemes:
print(f" - {scheme}")
输出:
- BoxPlot
- EqualInterval
- FisherJenks
- HeadTailBreaks
- JenksCaspall
- MaximumBreaks
- NaturalBreaks
- Quantiles
- StdMean
注意: 使用
scheme参数进行分类着色时,需要安装mapclassify库。连续着色适合数值型数据,分类着色适合类别型数据。
22.5 弹出框与提示
22.5.1 tooltip 参数 (hover)
tooltip 控制鼠标悬停时显示的信息。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# tooltip=True 时显示所有属性
m1 = world.explore(
tooltip=True,
tiles='CartoDB positron'
)
# tooltip 指定显示特定字段
m2 = world.explore(
tooltip=['name', 'continent', 'pop_est'],
tiles='CartoDB positron'
)
# tooltip=False 禁用悬停提示
m3 = world.explore(
tooltip=False,
tiles='CartoDB positron'
)
# 使用 GeoJsonTooltip 自定义提示样式
import folium
m = world.explore(
tooltip=folium.GeoJsonTooltip(
fields=['name', 'continent', 'pop_est'],
aliases=['国家名称:', '所在大洲:', '人口数量:'], # 字段别名
localize=True, # 本地化数字格式
sticky=True, # 跟随鼠标
style="""
background-color: #F0EFEF;
border: 2px solid #333;
border-radius: 5px;
padding: 8px;
font-size: 13px;
"""
),
tiles='CartoDB positron'
)
m
22.5.2 popup 参数 (click)
popup 控制鼠标点击时弹出的信息窗口。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# popup=True 显示所有属性
m1 = world.explore(
popup=True,
tooltip='name',
tiles='CartoDB positron'
)
# popup 指定显示特定字段
m2 = world.explore(
popup=['name', 'pop_est', 'gdp_md_est'],
tooltip='name',
tiles='CartoDB positron'
)
# 使用 GeoJsonPopup 自定义弹出内容
import folium
m = world.explore(
popup=folium.GeoJsonPopup(
fields=['name', 'continent', 'pop_est', 'gdp_md_est'],
aliases=['国家:', '大洲:', '人口:', 'GDP(百万$):'],
localize=True,
labels=True,
style='background-color: white; font-size: 12px;'
),
tooltip='name',
tiles='CartoDB positron'
)
m
22.5.3 自定义弹出内容
可以通过创建新列或使用 HTML 来自定义弹出框中的内容。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 创建格式化的信息列
world['info'] = world.apply(
lambda row: f"国家:{row['name']}<br>"
f"大洲:{row['continent']}<br>"
f"人口:{row['pop_est']:,.0f}<br>"
f"GDP:${row['gdp_md_est']:,.0f}M",
axis=1
)
# 使用自定义信息列作为弹出内容
m = world.explore(
popup=['info'],
tooltip='name',
tiles='CartoDB positron',
style_kwds={'fillOpacity': 0.6}
)
m
# 计算人均 GDP 并在弹出框中展示
world['gdp_per_capita'] = (
world['gdp_md_est'] * 1e6 / world['pop_est']
).round(0)
m = world.explore(
column='gdp_per_capita',
cmap='RdYlGn',
legend=True,
legend_kwds={'caption': '人均 GDP(美元)'},
popup=['name', 'gdp_per_capita'],
tooltip=['name', 'gdp_per_capita'],
tiles='CartoDB positron'
)
m
22.6 多图层叠加
22.6.1 使用 m 参数叠加图层
通过 m 参数可以将多个 GeoDataFrame 叠加到同一张地图上。
import geopandas as gpd
from shapely.geometry import Point
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
# 第一层:世界国家边界
m = world.explore(
color='lightblue',
style_kwds={
'fillOpacity': 0.3,
'weight': 1,
'color': 'gray'
},
tooltip='name',
name='国家边界', # 图层名称
tiles='CartoDB positron'
)
# 第二层:城市点(叠加到已有地图上)
cities.explore(
m=m, # 关键参数:传入已有地图对象
color='red',
marker_kwds={
'radius': 5,
'fill': True
},
tooltip='name',
name='主要城市' # 图层名称
)
m
# 叠加不同类型的几何要素
import geopandas as gpd
from shapely.geometry import LineString
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 创建一些示例航线数据
routes = gpd.GeoDataFrame(
{
'route': ['北京-东京', '北京-新加坡', '上海-悉尼'],
'geometry': [
LineString([(116.4, 39.9), (139.7, 35.7)]),
LineString([(116.4, 39.9), (103.8, 1.35)]),
LineString([(121.5, 31.2), (151.2, -33.9)])
]
},
crs='EPSG:4326'
)
# 底图:国家
m = world.explore(
color='lightyellow',
style_kwds={'weight': 0.5, 'fillOpacity': 0.2},
tiles='CartoDB positron',
name='国家'
)
# 叠加航线
routes.explore(
m=m,
color='red',
style_kwds={'weight': 3, 'dashArray': '10 5'},
tooltip='route',
name='航线'
)
m
22.6.2 图层控制 (LayerControl)
添加图层控制器允许用户在浏览器中切换图层的显示与隐藏。
import geopandas as gpd
import folium
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
# 创建多图层地图
m = world.explore(
column='continent',
cmap='Set3',
tooltip='name',
name='国家(按大洲着色)',
tiles='CartoDB positron'
)
cities.explore(
m=m,
color='darkred',
marker_kwds={'radius': 4},
tooltip='name',
name='主要城市'
)
# 添加图层控制器
folium.LayerControl(
collapsed=False # 默认展开图层列表
).add_to(m)
m
# 按大洲分别创建图层
continents = ['Asia', 'Europe', 'Africa', 'North America', 'South America']
colors = ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00']
m = folium.Map(location=[20, 0], zoom_start=2, tiles='CartoDB positron')
for continent, color in zip(continents, colors):
subset = world[world['continent'] == continent]
subset.explore(
m=m,
color=color,
style_kwds={'fillOpacity': 0.5, 'weight': 1},
tooltip='name',
name=continent # 每个大洲作为独立图层
)
folium.LayerControl().add_to(m)
m
22.7 使用 Folium 直接创建地图
22.7.1 folium.Map 基础
有时需要更精细地控制地图,可以直接使用 Folium API。
import folium
# 创建基础地图(以北京为中心)
m = folium.Map(
location=[39.9042, 116.4074], # 中心点坐标 [纬度, 经度]
zoom_start=10, # 初始缩放级别
tiles='OpenStreetMap', # 底图
width='100%', # 宽度
height='500px', # 高度
min_zoom=2, # 最小缩放级别
max_zoom=18, # 最大缩放级别
control_scale=True # 显示比例尺
)
m
# folium.Map 的常用属性和方法
print(f"地图中心: {m.location}")
print(f"缩放级别: {m.options.get('zoom', 'N/A')}")
# 获取地图的 HTML 表示
html = m._repr_html_()
print(f"HTML 长度: {len(html)} 字符")
输出:
地图中心: [39.9042, 116.4074]
缩放级别: N/A
HTML 长度: 5832 字符
22.7.2 添加标记 (Marker)
folium.Marker 用于在地图上添加位置标记。
import folium
m = folium.Map(location=[39.9, 116.4], zoom_start=11)
# 添加基本标记
folium.Marker(
location=[39.9042, 116.4074],
popup='天安门广场',
tooltip='点击查看详情'
).add_to(m)
# 添加带自定义弹出窗口的标记
popup_html = """
<div style="font-family: sans-serif; width: 200px;">
<h4 style="color: #333;">故宫博物院</h4>
<p>世界上现存规模最大的宫殿建筑群</p>
<p><b>开放时间:</b>8:30-17:00</p>
</div>
"""
folium.Marker(
location=[39.9163, 116.3972],
popup=folium.Popup(popup_html, max_width=250),
tooltip='故宫博物院'
).add_to(m)
# 添加多个标记
landmarks = [
{'name': '颐和园', 'lat': 39.9998, 'lon': 116.2755},
{'name': '天坛', 'lat': 39.8822, 'lon': 116.4066},
{'name': '鸟巢', 'lat': 39.9929, 'lon': 116.3966},
]
for lm in landmarks:
folium.Marker(
location=[lm['lat'], lm['lon']],
popup=lm['name'],
tooltip=lm['name']
).add_to(m)
m
22.7.3 添加圆形标记 (CircleMarker)
CircleMarker 和 Circle 用于在地图上绘制圆形。
import folium
m = folium.Map(location=[35, 105], zoom_start=4)
# 城市数据(名称、纬度、经度、人口-万)
cities = [
('北京', 39.90, 116.41, 2189),
('上海', 31.23, 121.47, 2428),
('广州', 23.13, 113.26, 1868),
('深圳', 22.54, 114.06, 1756),
('成都', 30.57, 104.07, 2094),
]
for name, lat, lon, pop in cities:
# CircleMarker —— 半径以像素为单位(不随缩放变化)
folium.CircleMarker(
location=[lat, lon],
radius=pop / 200, # 半径与人口成正比
color='#3186cc', # 边框颜色
fill=True,
fill_color='#3186cc', # 填充颜色
fill_opacity=0.6,
popup=f'{name}:{pop}万人',
tooltip=name
).add_to(m)
m
# Circle —— 半径以米为单位(随缩放变化)
m = folium.Map(location=[39.9, 116.4], zoom_start=11)
# 在天安门广场周围画同心圆,表示距离范围
for radius_km in [3, 6, 9]:
folium.Circle(
location=[39.9042, 116.4074],
radius=radius_km * 1000, # 半径(米)
color='red',
weight=2,
fill=True,
fill_opacity=0.05,
popup=f'{radius_km}公里范围'
).add_to(m)
m
22.8 GeoJSON 与 Choropleth
22.8.1 folium.GeoJson
folium.GeoJson 允许在地图上叠加 GeoJSON 数据并自定义样式。
import folium
import geopandas as gpd
import json
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
asia = world[world['continent'] == 'Asia']
m = folium.Map(location=[35, 90], zoom_start=3, tiles='CartoDB positron')
# 使用 GeoJson 添加矢量数据
folium.GeoJson(
data=asia.to_json(), # GeoJSON 字符串
name='亚洲国家',
style_function=lambda feature: {
'fillColor': '#3186cc',
'color': 'white',
'weight': 1,
'fillOpacity': 0.5,
},
highlight_function=lambda feature: {
'weight': 3,
'color': '#666',
'fillOpacity': 0.8,
},
tooltip=folium.GeoJsonTooltip(
fields=['name', 'pop_est'],
aliases=['国家:', '人口:'],
localize=True
)
).add_to(m)
folium.LayerControl().add_to(m)
m
# 根据属性动态设置样式
import branca.colormap as cm
# 创建颜色映射
colormap = cm.LinearColormap(
colors=['green', 'yellow', 'red'],
vmin=asia['pop_est'].min(),
vmax=asia['pop_est'].max(),
caption='人口数量'
)
m = folium.Map(location=[35, 90], zoom_start=3, tiles='CartoDB positron')
folium.GeoJson(
data=asia.to_json(),
style_function=lambda feature: {
'fillColor': colormap(feature['properties']['pop_est']),
'color': 'gray',
'weight': 1,
'fillOpacity': 0.7,
},
tooltip=folium.GeoJsonTooltip(
fields=['name', 'pop_est'],
aliases=['国家:', '人口:']
)
).add_to(m)
colormap.add_to(m)
m
22.8.2 folium.Choropleth
folium.Choropleth 是创建分级统计图的便捷方法。
import folium
import geopandas as gpd
import pandas as pd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
m = folium.Map(location=[20, 0], zoom_start=2, tiles='CartoDB positron')
# 创建 Choropleth 地图
folium.Choropleth(
geo_data=world.to_json(), # GeoJSON 数据
data=world, # 属性数据
columns=['name', 'pop_est'], # [关联字段, 值字段]
key_on='feature.properties.name', # GeoJSON 中的关联键
fill_color='YlOrRd', # 填充颜色方案
fill_opacity=0.7,
line_opacity=0.3,
nan_fill_color='lightgray', # 无数据区域颜色
legend_name='世界各国人口数量', # 图例名称
bins=6 # 分类数
).add_to(m)
m
# 结合外部 CSV 数据创建 Choropleth
# 假设有一个包含各国指标的 CSV 文件
import pandas as pd
import geopandas as gpd
import folium
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 模拟外部数据(互联网普及率)
internet_data = pd.DataFrame({
'country': ['China', 'India', 'United States of America',
'Brazil', 'Russia', 'Japan'],
'internet_rate': [73.0, 43.0, 87.0, 74.0, 80.0, 92.0]
})
m = folium.Map(location=[20, 0], zoom_start=2, tiles='CartoDB positron')
folium.Choropleth(
geo_data=world.to_json(),
data=internet_data,
columns=['country', 'internet_rate'],
key_on='feature.properties.name',
fill_color='BuGn',
fill_opacity=0.7,
line_opacity=0.3,
nan_fill_color='#f0f0f0',
legend_name='互联网普及率(%)'
).add_to(m)
m
注意: 使用
Choropleth时,columns中的关联字段必须与key_on指定的 GeoJSON 属性字段匹配。如果名称不一致会导致部分区域无法着色。
22.9 标记与图标
22.9.1 自定义图标 (Icon)
Folium 支持多种图标样式来美化地图标记。
import folium
m = folium.Map(location=[39.9, 116.4], zoom_start=12)
# 使用默认图标(蓝色标记)
folium.Marker(
location=[39.9042, 116.4074],
popup='默认图标',
icon=folium.Icon()
).add_to(m)
# 使用 Font Awesome 图标
folium.Marker(
location=[39.9163, 116.3972],
popup='故宫',
icon=folium.Icon(
color='red',
icon='university',
prefix='fa' # 使用 Font Awesome 图标库
)
).add_to(m)
# 使用 Glyphicon 图标
folium.Marker(
location=[39.9929, 116.3966],
popup='鸟巢体育场',
icon=folium.Icon(
color='green',
icon='flag',
prefix='glyphicon' # 使用 Glyphicon 图标库
)
).add_to(m)
# 自定义颜色的图标
folium.Marker(
location=[39.8822, 116.4066],
popup='天坛',
icon=folium.Icon(
color='purple',
icon_color='white',
icon='star',
prefix='fa'
)
).add_to(m)
m
# 可用的图标颜色
icon_colors = [
'red', 'blue', 'green', 'purple', 'orange',
'darkred', 'lightred', 'beige', 'darkblue', 'darkgreen',
'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue',
'lightgreen', 'gray', 'black', 'lightgray'
]
print(f"可用图标颜色(共 {len(icon_colors)} 种):")
print(', '.join(icon_colors))
输出:
可用图标颜色(共 19 种):
red, blue, green, purple, orange, darkred, lightred, beige, darkblue, darkgreen, cadetblue, darkpurple, white, pink, lightblue, lightgreen, gray, black, lightgray
22.9.2 标记聚类 (MarkerCluster)
当地图上有大量标记时,使用 MarkerCluster 可以自动将邻近标记聚类显示。
import folium
from folium.plugins import MarkerCluster
import random
m = folium.Map(location=[35, 105], zoom_start=4)
# 创建标记聚类对象
marker_cluster = MarkerCluster(name='城市标记').add_to(m)
# 模拟大量城市数据
cities_data = [
('北京', 39.90, 116.41), ('上海', 31.23, 121.47),
('广州', 23.13, 113.26), ('深圳', 22.54, 114.06),
('成都', 30.57, 104.07), ('杭州', 30.27, 120.15),
('武汉', 30.59, 114.31), ('西安', 34.26, 108.94),
('南京', 32.06, 118.80), ('重庆', 29.56, 106.55),
('天津', 39.13, 117.20), ('苏州', 31.30, 120.62),
('郑州', 34.75, 113.65), ('长沙', 28.23, 112.94),
('青岛', 36.07, 120.38), ('大连', 38.91, 121.60),
('厦门', 24.48, 118.09), ('昆明', 25.04, 102.71),
('哈尔滨', 45.75, 126.65), ('沈阳', 41.80, 123.43),
]
for name, lat, lon in cities_data:
folium.Marker(
location=[lat, lon],
popup=name,
tooltip=name,
icon=folium.Icon(color='blue', icon='info-sign')
).add_to(marker_cluster) # 添加到聚类对象而非地图
folium.LayerControl().add_to(m)
m
# 使用 FastMarkerCluster 处理更大量的数据
from folium.plugins import FastMarkerCluster
import numpy as np
m = folium.Map(location=[35, 105], zoom_start=4)
# 生成大量随机点(模拟海量数据)
np.random.seed(42)
n_points = 1000
lats = np.random.uniform(20, 50, n_points)
lons = np.random.uniform(80, 130, n_points)
# FastMarkerCluster 接受坐标列表,性能更优
callback = """
function (row) {
var marker = L.marker(new L.LatLng(row[0], row[1]));
marker.bindPopup('位置: ' + row[0].toFixed(2) + ', ' + row[1].toFixed(2));
return marker;
}
"""
FastMarkerCluster(
data=list(zip(lats, lons)),
callback=callback,
name='随机点聚类'
).add_to(m)
folium.LayerControl().add_to(m)
m
22.10 导出与嵌入
22.10.1 保存为 HTML
交互式地图可以保存为独立的 HTML 文件,在任何浏览器中打开。
import folium
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 创建交互式地图
m = world.explore(
column='continent',
cmap='Set2',
legend=True,
tooltip=['name', 'continent'],
tiles='CartoDB positron'
)
# 保存为 HTML 文件
m.save('world_interactive_map.html')
print("地图已保存为 world_interactive_map.html")
输出:
地图已保存为 world_interactive_map.html
# 获取 HTML 字符串(用于动态生成)
html_string = m._repr_html_()
print(f"HTML 字符串长度: {len(html_string)} 字符")
print(f"前 200 个字符:\n{html_string[:200]}...")
输出:
HTML 字符串长度: 158432 字符
前 200 个字符:
<div style="width:100%;"><div style="position:relative;width:100%;height:0;padding-bottom:60%;"><span style="color:#565656">Make this Notebook Trusted to load map: File -> Trust No...
22.10.2 在 Jupyter 中显示
在 Jupyter Notebook/Lab 中,交互式地图会自动渲染。
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# 方式 1:在 Cell 末尾直接引用变量
m = world.explore(tiles='CartoDB positron')
m # 自动渲染地图
# 方式 2:使用 display() 函数
from IPython.display import display
display(m)
# 方式 3:使用 HTML 组件
from IPython.display import HTML
HTML(m._repr_html_())
# 控制地图在 Notebook 中的尺寸
m = world.explore(
tiles='CartoDB positron',
width='100%', # 宽度占满容器
height='400px' # 固定高度 400 像素
)
m
# 在 Jupyter Lab 中使用 IFrame 嵌入保存的 HTML 文件
from IPython.display import IFrame
# 先保存为文件
m.save('my_map.html')
# 再用 IFrame 嵌入
IFrame('my_map.html', width=800, height=500)
22.10.3 嵌入网页
将 Folium 地图嵌入到现有网页中有多种方式。
# 方式 1:使用 iframe 嵌入
import geopandas as gpd
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
m = world.explore(tiles='CartoDB positron', tooltip='name')
m.save('map_embed.html')
# 在 HTML 页面中使用 iframe 引用
iframe_code = """
<!-- 在你的网页中添加以下代码 -->
<iframe
src="map_embed.html"
width="100%"
height="600"
style="border: 1px solid #ccc; border-radius: 4px;"
loading="lazy"
></iframe>
"""
print(iframe_code)
输出:
<!-- 在你的网页中添加以下代码 -->
<iframe
src="map_embed.html"
width="100%"
height="600"
style="border: 1px solid #ccc; border-radius: 4px;"
loading="lazy"
></iframe>
# 方式 2:直接嵌入 HTML 代码
html_content = m.get_root().render()
# 提取地图部分的 HTML(去掉完整页面包装)
map_div = m._repr_html_()
page_template = f"""
<!DOCTYPE html>
<html>
<head>
<title>我的交互式地图</title>
<style>
body
h1
.map-container
</style>
</head>
<body>
<h1>世界地图展示</h1>
<p>以下是使用 GeoPandas 和 Folium 生成的交互式地图:</p>
<div class="map-container">
{map_div}
</div>
<p>数据来源:Natural Earth</p>
</body>
</html>
"""
with open('full_page_map.html', 'w', encoding='utf-8') as f:
f.write(page_template)
print("完整网页已保存为 full_page_map.html")
输出:
完整网页已保存为 full_page_map.html
注意: 嵌入地图时请注意文件大小。如果 GeoDataFrame 包含大量几何数据,生成的 HTML 文件可能很大。建议在嵌入前简化几何(使用
simplify()方法)或筛选必要的字段。
22.11 本章小结
本章介绍了使用 GeoPandas 和 Folium 创建交互式 Web 地图的方法。从 explore() 快速生成地图到直接使用 Folium API 进行精细控制,涵盖了交互式地图开发的各个方面。
| 主题 | 方法/参数 | 关键要点 |
|---|---|---|
| explore() 基础 | gdf.explore() |
一行代码创建交互式地图,返回 folium.Map 对象 |
| 底图选择 | tiles='CartoDB positron' |
内置多种底图,支持自定义瓦片 URL |
| 按属性着色 | column, cmap, scheme |
支持分类着色和连续着色,需要 mapclassify |
| 弹出与提示 | tooltip, popup |
tooltip 悬停显示,popup 点击弹出 |
| 多图层叠加 | m=existing_map |
将多个 GeoDataFrame 叠加到同一张地图 |
| 图层控制 | folium.LayerControl() |
允许用户切换图层显示/隐藏 |
| Folium Map | folium.Map() |
直接创建地图对象,精细控制参数 |
| 标记 | Marker, CircleMarker |
Marker 固定图标,CircleMarker 可缩放 |
| GeoJson | folium.GeoJson() |
自定义样式函数、高亮和提示 |
| Choropleth | folium.Choropleth() |
快速创建分级统计地图 |
| 自定义图标 | folium.Icon() |
支持 Font Awesome 和 Glyphicon 图标 |
| 标记聚类 | MarkerCluster |
大量标记自动聚类,提升性能 |
| 导出 HTML | m.save('map.html') |
独立 HTML 文件,可在浏览器打开 |
| 网页嵌入 | <iframe> / 内联 HTML |
通过 iframe 或直接嵌入代码集成到网页 |
核心要诀:
- 快速探索用
explore():GeoPandas 内置的explore()方法是最快捷的交互式地图创建方式 - 精细控制用 Folium API:需要自定义标记、图标、弹出框时直接使用 Folium
- 多图层叠加靠
m参数:通过传递已有地图对象实现图层叠加 - 大数据集注意性能:使用
simplify()简化几何、FastMarkerCluster处理海量点 - 底图选择影响效果:浅色底图适合数据可视化,深色底图适合亮色数据展示
- 导出与分享用 HTML:
save()方法生成独立 HTML 文件,便于分享和嵌入
下一章我们将学习 第23章 的内容,继续深入探索 GeoPandas 的高级功能。