GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)
引言
《GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)》这篇文章,解决的是一个很具体的问题:手里有 GeoJSON、Shapefile 或空间分析结果,但不想从零搭建 WebGIS 前后端,怎样快速做出一个可交互、可分享、可演示的地图应用。
很多 GISer 会写 Python,也会用 QGIS、ArcGIS Pro 做数据处理,但一到“把结果做成网页地图”,就会被前端框架、地图 SDK、部署环境卡住。Streamlit 的价值就在这里:它可以用很少的 Python 代码,把数据表、筛选控件、图表和交互式地图组合成一个轻量级 GIS 数据可视化应用。
顺便说一句,Streamlit 常见读音可以近似读作“斯特림利特”或“stream-lit”,重点不是读得多标准,而是理解它的定位:它不是专业 WebGIS 引擎,而是一个适合快速构建数据应用的 Python 框架。

背景
在 GIS 项目中,地理数据可视化通常有三类需求:
- 把 GeoJSON、Shapefile、CSV 点位数据快速展示出来。
- 给业务人员提供筛选、缩放、点击查看属性等交互能力。
- 把空间分析结果做成一个可演示、可复现的小应用。
传统做法可能需要 Leaflet、OpenLayers、Vue、React、后端接口、瓦片服务等组件。对于正式 WebGIS 项目,这些技术很重要;但对于教学演示、数据质检、项目汇报和内部工具,完整前后端架构往往过重。
Streamlit 更适合以下场景:
- 你已经用 Python 处理了 GIS 数据,希望直接可视化结果。
- 你需要一个快速原型,而不是复杂生产系统。
- 你希望通过侧边栏控件筛选地图内容。
- 你要把 GeoJSON 加载到交互式地图中,并保留基本属性查看能力。
原理
Streamlit 的核心思路是:用 Python 脚本描述页面,脚本从上到下执行,用户调整控件后页面自动刷新。对于 GIS 数据可视化,可以把它理解为三个环节:
- 使用 GeoPandas 读取 GeoJSON、Shapefile 等空间数据。
- 使用 Folium 或 PyDeck 构建交互式地图对象。
- 使用 Streamlit 把地图、筛选器、统计表和说明文字展示到浏览器中。
其中 GeoJSON 是 WebGIS 中非常常见的矢量数据格式,结构直观,适合点、线、面要素展示。它的优点是易读、兼容性好;缺点是文件较大时加载会变慢,复杂面数据尤其明显。
本文示例采用 Streamlit、GeoPandas、Folium 和 streamlit-folium。这个组合比较适合 GIS 初学者和 Python GIS 用户,因为它接近常规 GIS 数据处理流程,不需要先掌握完整前端工程体系。
步骤
1. 准备 Python 环境
建议使用独立虚拟环境,避免和已有 ArcGIS Pro、QGIS 或其他 Python 环境冲突。
python -m venv streamlit-gis-env
# Windows
streamlit-gis-envScriptsactivate
# macOS / Linux
source streamlit-gis-env/bin/activate
安装本文需要的库:
pip install streamlit geopandas folium streamlit-folium
如果 GeoPandas 安装失败,通常和 GDAL、Fiona、PyProj 等底层依赖有关。初学者也可以优先使用 Conda:
conda create -n streamlit-gis python=3.11
conda activate streamlit-gis
conda install -c conda-forge streamlit geopandas folium streamlit-folium
2. 准备 GeoJSON 数据
假设你的项目目录如下:
streamlit-gis-demo/
app.py
data/
sample.geojson
GeoJSON 文件最好满足以下条件:
- 坐标系为 WGS84,经纬度坐标,即 EPSG:4326。
- 属性字段不要过多,避免弹窗过长。
- 面数据不要过于复杂,必要时先做简化。
- 文件名和路径不要包含过多特殊字符。
3. 编写最小可运行的 Streamlit 地图应用
在项目目录中新建 app.py,写入以下代码:
import streamlit as st
import geopandas as gpd
import folium
from streamlit_folium import st_folium
st.set_page_config(
page_title="Streamlit GIS地图应用",
layout="wide"
)
st.title("Streamlit GeoJSON交互式地图应用")
@st.cache_data
def load_geojson(path):
gdf = gpd.read_file(path)
if gdf.crs is None:
st.warning("数据没有坐标系信息,示例中假定为 EPSG:4326。")
gdf = gdf.set_crs(epsg=4326)
if gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)
return gdf
geojson_path = "data/sample.geojson"
gdf = load_geojson(geojson_path)
st.sidebar.header("图层筛选")
fields = [col for col in gdf.columns if col != "geometry"]
selected_field = st.sidebar.selectbox("选择用于查看的属性字段", fields)
if selected_field:
values = sorted(gdf[selected_field].dropna().astype(str).unique().tolist())
selected_values = st.sidebar.multiselect(
"选择要显示的属性值",
values,
default=values
)
if selected_values:
show_gdf = gdf[gdf[selected_field].astype(str).isin(selected_values)]
else:
show_gdf = gdf.iloc[0:0]
else:
show_gdf = gdf
st.write("要素总数:", len(gdf))
st.write("当前显示要素数:", len(show_gdf))
if len(show_gdf) > 0:
center = [
show_gdf.geometry.centroid.y.mean(),
show_gdf.geometry.centroid.x.mean()
]
else:
center = [35.0, 105.0]
m = folium.Map(location=center, zoom_start=5, tiles="OpenStreetMap")
folium.GeoJson(
show_gdf,
name="GeoJSON图层",
tooltip=folium.GeoJsonTooltip(fields=fields[:5]),
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=None, height=650)
with st.expander("查看属性表"):
st.dataframe(gdf.drop(columns="geometry"))
4. 运行应用
在命令行进入项目目录,执行:
streamlit run app.py
浏览器会自动打开本地地址,通常是:
http://localhost:8501
如果页面正常出现,你就完成了一个最小版本的 Streamlit 地理数据可视化应用。侧边栏可以选择字段和属性值,主区域显示交互式地图和属性表。
5. 处理点、线、面数据的显示差异
GeoJSON 可以包含点、线、面,不同几何类型在地图上的表达重点不同:
- 点数据适合展示监测站、兴趣点、事故点、采样点。
- 线数据适合展示道路、河流、管网、轨迹。
- 面数据适合展示行政区、地块、规划范围、缓冲区。
如果你的面数据边界很复杂,浏览器渲染会变慢。可以在 QGIS 中使用“简化几何图形”工具,或用 GeoPandas 进行简化:
gdf_simple = gdf.copy()
gdf_simple["geometry"] = gdf_simple.geometry.simplify(
tolerance=0.001,
preserve_topology=True
)
简化前一定要备份原始数据,并检查边界是否出现明显变形。
6. 增加分类颜色渲染
如果希望根据字段值设置颜色,可以给 folium.GeoJson 增加样式函数。以下示例按某个字段进行简单分类:
color_field = selected_field
unique_values = show_gdf[color_field].dropna().astype(str).unique().tolist()
color_list = ["#1f78b4", "#33a02c", "#e31a1c", "#ff7f00", "#6a3d9a"]
color_map = {
value: color_list[i % len(color_list)]
for i, value in enumerate(unique_values)
}
def style_function(feature):
value = str(feature["properties"].get(color_field, ""))
return {
"fillColor": color_map.get(value, "#999999"),
"color": "#333333",
"weight": 1,
"fillOpacity": 0.6
}
folium.GeoJson(
show_gdf,
name="分类渲染图层",
style_function=style_function,
tooltip=folium.GeoJsonTooltip(fields=fields[:5]),
).add_to(m)
这个方法适合类别数量不多的字段,例如行政级别、用地类型、风险等级等。如果类别太多,建议先在数据处理中合并分类。
常见坑
1. GeoJSON 加载后位置偏到海上
这是 GISer 最常见的问题之一,通常由坐标系不一致导致。Folium 和大多数 Web 地图底图使用经纬度坐标显示矢量数据,GeoJSON 一般应为 EPSG:4326。
- 如果数据是投影坐标,例如 CGCS2000 高斯投影,需要先转换为 EPSG:4326。
- 如果数据没有 CRS 信息,GeoPandas 可能无法判断坐标系。
- 如果经纬度顺序被写反,也会出现位置异常。
建议在加载后检查:
print(gdf.crs)
print(gdf.total_bounds)
如果中国范围内经纬度数据大致应满足:经度约 73 到 135,纬度约 18 到 54。超出太多就要检查坐标系。
2. GeoJSON 文件太大,地图很卡
Streamlit 能快速搭建地图应用,但浏览器直接加载大体量 GeoJSON 时仍然会卡顿。特别是复杂行政区边界、地块面、道路网等数据,容易导致页面刷新慢。
优化方向包括:
- 先按区域或属性筛选,只加载当前需要的数据。
- 对复杂面做几何简化。
- 减少弹窗和 tooltip 中展示的字段数量。
- 将大数据改为切片服务、矢量瓦片或数据库接口。
- 使用
st.cache_data缓存读取结果,避免每次刷新都重新读文件。
3. Streamlit 页面一操作就重新运行
这是 Streamlit 的正常机制。用户每次调整控件,脚本会从上到下重新执行。解决办法不是阻止刷新,而是合理使用缓存和状态管理。
- 读取文件、计算统计量等耗时步骤使用
st.cache_data。 - 数据库连接、模型对象等资源使用
st.cache_resource。 - 需要保存用户临时选择时使用
st.session_state。
4. Folium 地图不显示或显示空白
常见原因有:
- 没有使用
st_folium正确嵌入 Folium 地图。 - GeoDataFrame 为空,筛选条件把所有要素过滤掉了。
- 几何字段无效,例如存在空几何或自相交面。
- 浏览器无法访问在线底图,导致底图空白。
可以先用以下方式检查数据:
st.write(gdf.head())
st.write(gdf.crs)
st.write(gdf.geometry.is_empty.sum())
st.write(gdf.geometry.is_valid.value_counts())
方法比较
Streamlit 不是唯一的地理数据可视化方案。选择工具时,要看你的目标是快速演示、数据分析,还是正式 WebGIS 系统。
| 方案 | 适合场景 | 优点 | 限制 |
|---|---|---|---|
| Streamlit + Folium | Python GIS 快速原型、GeoJSON 展示、内部工具 | 上手快,代码少,适合 GIS 数据分析人员 | 不适合超大数据和复杂前端交互 |
| QGIS | 桌面制图、数据检查、空间处理 | GIS 功能完整,插件丰富 | 分享为交互式网页需要额外流程 |
| Leaflet / OpenLayers | 正式 WebGIS 前端开发 | 交互能力强,适合工程化项目 | 需要 JavaScript 和前端工程经验 |
| PostGIS + WebGIS 前端 | 多用户、大数据、空间查询服务 | 扩展性好,适合生产系统 | 架构复杂,开发和运维成本更高 |
| ArcGIS Online / Enterprise | 组织级地图发布和共享 | 平台能力完整,权限和服务管理成熟 | 依赖平台授权和生态 |
如果你的目标是“今天就把 GeoJSON 做成可交互地图给同事看”,Streamlit 很合适。如果你的目标是“支撑大量用户访问、复杂图层控制、空间查询和权限管理”,就应该考虑 WebGIS 工程架构。
检查清单
在把 Streamlit 地图应用交给别人使用前,建议按下面清单检查一次。
- 坐标系:GeoJSON 是否为 EPSG:4326,地图位置是否正确。
- 数据量:文件大小是否过大,复杂面是否做过简化。
- 字段:tooltip 和属性表是否只展示必要字段。
- 筛选:侧边栏筛选后是否会出现空地图,是否有提示。
- 缓存:读取数据是否使用
st.cache_data。 - 路径:部署环境中的数据路径是否和本地一致。
- 底图:目标网络环境是否能访问在线底图。
- 异常数据:是否存在空几何、无效几何或错误坐标。
- 用途边界:是否只是演示工具,还是要作为正式生产系统。
FAQ
Streamlit 能不能替代 WebGIS 前端开发?
不能完全替代。Streamlit 适合快速构建 GIS 数据可视化原型、内部分析工具和教学应用。如果项目需要复杂图层管理、高并发访问、精细权限控制、矢量瓦片和专业地图交互,仍然建议使用 Leaflet、OpenLayers、Cesium 等 WebGIS 技术栈。
Streamlit 加载 GeoJSON 慢怎么办?
先确认慢的是文件读取、几何渲染还是页面刷新。常见优化方法包括使用 st.cache_data 缓存数据、减少字段、简化几何、按区域筛选、拆分文件。如果数据量很大,不建议把完整 GeoJSON 直接丢给浏览器渲染,可以考虑 PostGIS 接口、矢量瓦片或服务端分页。
为什么 GeoJSON 在 QGIS 中正常,在 Streamlit 地图中偏移?
QGIS 可以自动识别和动态投影多种坐标系,而 Web 地图通常要求 GeoJSON 使用 EPSG:4326。请检查 gdf.crs,必要时使用 gdf.to_crs(epsg=4326) 转换。不要只修改坐标系标签,要确认坐标值真的完成了转换。
Streamlit 适合 GIS 初学者学习吗?
适合。它能让 GIS 初学者把 Python 数据处理结果快速变成可交互页面,有助于理解“数据读取、空间处理、地图表达、用户交互”的完整流程。但学习 Streamlit 不代表可以跳过坐标系、数据格式、空间索引和地图渲染原理。
GeoJSON、Shapefile、GeoPackage 哪个更适合 Streamlit 地图应用?
如果是轻量级 Web 展示,GeoJSON 最直观;如果是桌面 GIS 交换,Shapefile 仍然常见,但字段名和编码限制较多;如果是本地项目数据管理,GeoPackage 更现代,也更适合存放多图层数据。最终展示到 Folium 地图时,仍然通常需要转换到 EPSG:4326。
能不能在 Streamlit 中显示多个图层?
可以。你可以读取多个 GeoJSON 或多个 GeoDataFrame,然后分别用 folium.GeoJson 添加到同一个地图对象中,并用 folium.LayerControl 控制显示隐藏。需要注意的是,图层越多,浏览器渲染压力越大。
结论
对 GISer 来说,Streamlit 地理数据可视化的核心价值不是替代专业 WebGIS,而是把 Python GIS 分析结果快速变成可交互、可演示、可分享的地图应用。只要掌握 GeoJSON 读取、坐标系转换、Folium 地图嵌入和 Streamlit 控件筛选,就能完成很多实用的小工具。
实际项目中,请重点关注三个问题:坐标系是否正确、GeoJSON 是否过大、交互逻辑是否足够清晰。把这三点处理好,Streamlit 就能成为 GIS 数据分析和项目汇报中非常高效的一环。