GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)
如果你正在搜索“GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)”,大概率是想用最少的前端代码,把 GeoJSON、点线面图层和属性筛选做成一个可交互地图应用。本文用 Streamlit 搭配 Folium,演示一个 GISer 能直接复用的 GeoJSON 加载与地图可视化流程。
引言:用 Streamlit 做地理数据可视化到底适合谁
Streamlit 常被读作“斯特里姆-lit”,更接近英文中的 “stream-lit”。读音不是重点,重点是它能让 Python 用户快速把脚本变成交互式网页应用。对于 GIS 学生、空间数据分析师和初级 GIS 工程师来说,Streamlit 的优势是:不用先学习完整的 Vue、React 或复杂 WebGIS 工程,也能快速做出一个可演示、可筛选、可部署的交互式地图。
本文聚焦一个具体问题:如何用 Streamlit 加载 GeoJSON,并在网页中显示交互式地图。你将得到一个可运行的最小项目,包括环境安装、目录结构、GeoJSON 读取、Folium 地图渲染、属性字段筛选和常见报错排查。

背景:为什么 GISer 会卡在 GeoJSON 地图可视化
很多 GISer 的数据处理能力并不弱,问题通常卡在“展示”这一环。比如你已经有了行政区边界、采样点、道路中心线或项目范围 GeoJSON,但想给同事或甲方一个可交互页面时,会遇到这些问题:
- 只会在 QGIS 或 ArcGIS Pro 里看图,无法做成网页应用。
- 会 Python,但不会写前端页面和 WebGIS 框架。
- GeoJSON 文件能打开,但放到网页中加载慢、样式难调。
- 想按字段筛选图层,却不知道如何把筛选控件和地图联动。
- 想快速分享成果,但不想搭建复杂服务器。
Streamlit 适合解决这类轻量级 GIS 可视化需求。它不替代专业 WebGIS 平台,也不适合直接承载超大规模生产级地图服务,但非常适合数据探索、教学演示、项目汇报、内部工具和原型验证。
原理:Streamlit 加载 GeoJSON 的基本机制
Streamlit 本身负责网页界面和交互控件,Folium 负责生成 Leaflet 地图,GeoPandas 负责读取和处理空间数据。三者组合起来,可以形成一条清晰的数据流:
- GeoPandas 读取本地 GeoJSON 文件,生成 GeoDataFrame。
- 检查坐标系,确保数据适合在 Web 地图中展示。
- Streamlit 提供上传文件、字段选择、属性筛选等交互控件。
- Folium 把筛选后的 GeoJSON 添加到 Leaflet 地图中。
- streamlit-folium 把 Folium 地图嵌入 Streamlit 页面。
这里需要特别注意坐标系。大多数在线底图和 Leaflet 地图使用 WGS84 经纬度坐标,也就是 EPSG:4326。如果你的 GeoJSON 来自投影坐标系,例如 CGCS2000 高斯投影、Web Mercator 或地方坐标系,需要先转换为 EPSG:4326,否则地图可能会偏移、空白或出现在奇怪的位置。
步骤:搭建 Streamlit 交互式地图应用
步骤 1:准备项目目录
建议先建立一个清晰的项目目录,避免把代码、数据和输出文件混在一起。
streamlit-gis-map/
├── app.py
├── requirements.txt
└── data/
└── sample.geojson
其中 app.py 是主程序,requirements.txt 用于记录依赖包,data/sample.geojson 是示例地理数据。
步骤 2:安装 Python 依赖
在项目目录下创建虚拟环境,并安装所需库:
pip install streamlit geopandas folium streamlit-folium
也可以把依赖写入 requirements.txt:
streamlit
geopandas
folium
streamlit-folium
后续部署到 Streamlit Community Cloud 或服务器时,平台会根据这个文件安装依赖。
步骤 3:编写最小可运行的 GeoJSON 加载源码
下面这段代码可以直接保存为 app.py。它会读取本地 GeoJSON,自动转换坐标系,并在页面中显示交互式地图。
import streamlit as st
import geopandas as gpd
import folium
from streamlit_folium import st_folium
st.set_page_config(
page_title="Streamlit GeoJSON 交互式地图",
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("当前 GeoJSON 未声明坐标系,默认按 EPSG:4326 处理。")
gdf = gdf.set_crs(epsg=4326)
elif 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("图层信息")
st.sidebar.write(f"要素数量:{len(gdf)}")
st.sidebar.write(f"字段数量:{len(gdf.columns)}")
center = [
gdf.geometry.centroid.y.mean(),
gdf.geometry.centroid.x.mean()
]
m = folium.Map(
location=center,
zoom_start=10,
tiles="OpenStreetMap"
)
folium.GeoJson(
gdf,
name="GeoJSON 图层",
tooltip=folium.GeoJsonTooltip(
fields=[col for col in gdf.columns if col != "geometry"][:5]
)
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=1200, height=650)
运行应用:
streamlit run app.py
浏览器打开本地地址后,就可以看到 GeoJSON 图层叠加在交互式地图上。这个版本适合验证数据能否正常加载,也是后续添加筛选、样式和上传功能的基础。
步骤 4:增加字段筛选功能
真实项目中,GeoJSON 往往包含行政区名称、类型、等级、年份、项目编号等字段。我们可以让用户在侧边栏选择字段和值,再动态过滤地图图层。
import streamlit as st
import geopandas as gpd
import folium
from streamlit_folium import st_folium
st.set_page_config(page_title="GeoJSON 字段筛选地图", layout="wide")
@st.cache_data
def load_geojson(path):
gdf = gpd.read_file(path)
if gdf.crs is None:
gdf = gdf.set_crs(epsg=4326)
elif gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)
return gdf
gdf = load_geojson("data/sample.geojson")
st.sidebar.header("属性筛选")
attribute_fields = [col for col in gdf.columns if col != "geometry"]
selected_field = st.sidebar.selectbox("选择筛选字段", attribute_fields)
unique_values = sorted(gdf[selected_field].dropna().astype(str).unique())
selected_values = st.sidebar.multiselect(
"选择字段值",
unique_values,
default=unique_values[:5] if len(unique_values) > 5 else unique_values
)
if selected_values:
filtered_gdf = gdf[gdf[selected_field].astype(str).isin(selected_values)]
else:
filtered_gdf = gdf
st.write(f"当前显示要素数量:{len(filtered_gdf)} / {len(gdf)}")
if len(filtered_gdf) == 0:
st.warning("筛选结果为空,请重新选择字段值。")
st.stop()
center = [
filtered_gdf.geometry.centroid.y.mean(),
filtered_gdf.geometry.centroid.x.mean()
]
m = folium.Map(location=center, zoom_start=10, tiles="OpenStreetMap")
folium.GeoJson(
filtered_gdf,
name="筛选后的 GeoJSON",
tooltip=folium.GeoJsonTooltip(
fields=attribute_fields[:5],
aliases=attribute_fields[:5]
),
style_function=lambda feature: {
"fillColor": "#3186cc",
"color": "#1f4e79",
"weight": 2,
"fillOpacity": 0.45
}
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=1200, height=650)
这个版本已经具备一个小型 GIS 浏览器的雏形:用户可以选择属性字段,按字段值筛选要素,并查看筛选后的地图结果。
步骤 5:支持用户上传 GeoJSON
如果你希望应用不固定读取 data/sample.geojson,可以加入文件上传控件。这样用户可以把自己的 GeoJSON 上传到页面中查看。
import streamlit as st
import geopandas as gpd
import folium
import tempfile
from streamlit_folium import st_folium
st.set_page_config(page_title="上传 GeoJSON 地图查看器", layout="wide")
uploaded_file = st.file_uploader("上传 GeoJSON 文件", type=["geojson", "json"])
if uploaded_file is None:
st.info("请上传一个 GeoJSON 文件。")
st.stop()
with tempfile.NamedTemporaryFile(delete=False, suffix=".geojson") as tmp:
tmp.write(uploaded_file.read())
tmp_path = tmp.name
gdf = gpd.read_file(tmp_path)
if gdf.crs is None:
st.warning("上传文件未声明坐标系,默认按 EPSG:4326 处理。")
gdf = gdf.set_crs(epsg=4326)
elif gdf.crs.to_epsg() != 4326:
gdf = gdf.to_crs(epsg=4326)
if len(gdf) == 0:
st.error("GeoJSON 中没有可显示的空间要素。")
st.stop()
center = [gdf.geometry.centroid.y.mean(), gdf.geometry.centroid.x.mean()]
m = folium.Map(location=center, zoom_start=10, tiles="OpenStreetMap")
folium.GeoJson(
gdf,
name="上传的 GeoJSON",
tooltip=folium.GeoJsonTooltip(
fields=[col for col in gdf.columns if col != "geometry"][:5]
)
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=1200, height=650)
这个功能非常适合做课堂练习、内部数据查看器或项目汇报工具。用户不需要安装 QGIS,也能在浏览器里快速查看 GeoJSON 数据。
常见坑:Streamlit 地理数据可视化最容易出错的地方
1. 地图空白但代码没有报错
地图空白通常不是 Streamlit 的问题,而是坐标系或几何范围问题。优先检查以下内容:
- GeoJSON 是否真的有几何字段。
- 坐标是否为经纬度,范围是否大致在经度 -180 到 180、纬度 -90 到 90。
- 数据是否被错误设置为 EPSG:4326,而不是正确转换为 EPSG:4326。
- 几何是否为空或无效。
可以用下面代码检查范围:
st.write(gdf.crs)
st.write(gdf.total_bounds)
st.write(gdf.geometry.is_empty.sum())
st.write(gdf.geometry.is_valid.value_counts())
2. GeoJSON 加载很慢
GeoJSON 是文本格式,浏览器解析成本较高。几万甚至几十万个要素直接塞进 Folium,体验会明显变差。可以考虑:
- 先用 QGIS 或 GeoPandas 简化几何。
- 只保留展示需要的字段,删除冗余属性。
- 按行政区、类型或级别做筛选后再加载。
- 把大数据转为 PostGIS、矢量切片或 GeoParquet,不要强行用单个 GeoJSON 承载全部数据。
3. 中文字段或中文路径报错
在 Windows 环境下,中文路径、特殊符号和编码问题容易导致读取失败。建议项目路径使用英文,字段名尽量规范,必要时先用 QGIS 另存为 UTF-8 编码的 GeoJSON。
4. 计算中心点时出现警告
如果数据是经纬度坐标,直接计算 centroid 可能出现警告。对于本文这种只用于地图初始定位的场景,影响通常有限;如果要做严谨面积、距离或中心点分析,应先转换到合适的投影坐标系,再计算几何结果。
5. 部署后找不到数据文件
本地可以读取 data/sample.geojson,部署后报文件不存在,多半是目录没有上传,或相对路径写错。建议确保数据文件在项目仓库中,并使用稳定的相对路径。
方法比较:Streamlit、QGIS、Leaflet 和传统 WebGIS 怎么选
| 方案 | 适合场景 | 优势 | 限制 |
|---|---|---|---|
| Streamlit + Folium | Python 用户快速搭建交互式地图应用 | 开发快、代码少、适合数据分析和演示 | 不适合复杂前端交互和高并发生产系统 |
| QGIS | 桌面制图、数据检查、空间处理 | 功能完整、插件丰富、适合专业编辑 | 分享交互结果不如网页方便 |
| Leaflet / OpenLayers | 正式 WebGIS 前端开发 | 交互能力强、可定制程度高 | 需要掌握 JavaScript 和前端工程 |
| PostGIS + WebGIS | 多用户、大数据、空间查询服务 | 适合生产级空间数据管理与查询 | 部署和维护成本更高 |
简单来说,如果你的目标是“快速把 GeoJSON 做成可交互地图并分享给别人看”,Streamlit 是很合适的选择。如果你的目标是建设长期运行的业务系统,就应考虑 PostGIS、GeoServer、矢量切片和前端 WebGIS 框架。
检查清单:发布前请逐项确认
- 数据格式:GeoJSON 能否被 GeoPandas 正常读取。
- 坐标系:地图展示前是否转换为 EPSG:4326。
- 几何质量:是否存在空几何、无效几何或异常范围。
- 字段数量:是否删除了不需要展示的冗余字段。
- 数据体量:是否避免一次加载过大的 GeoJSON。
- 交互控件:字段筛选是否会产生空结果,是否有提示信息。
- 路径设置:部署环境中是否能找到数据文件。
- 依赖文件:
requirements.txt是否包含所有依赖。 - 展示效果:不同缩放级别下样式是否清晰。
- 隐私安全:上传和展示的数据是否包含敏感位置或内部属性。
FAQ:Streamlit GeoJSON 地图应用常见问题
Streamlit 能不能直接替代 WebGIS 开发?
不能完全替代。Streamlit 更适合快速原型、数据分析应用和内部工具。如果需要复杂图层管理、权限控制、高并发访问、矢量切片渲染和深度前端交互,仍然建议使用 Leaflet、OpenLayers、Cesium 等 WebGIS 技术栈。
GeoJSON 文件多大以内适合用 Streamlit 加载?
没有绝对阈值,取决于要素数量、几何复杂度、字段数量和服务器性能。经验上,如果加载明显卡顿,就应考虑简化几何、减少字段、按区域筛选,或改用数据库和切片方案。不要把 Streamlit 当作大规模 GeoJSON 分发服务。
为什么我的 GeoJSON 在 QGIS 能打开,在 Streamlit 地图上却看不到?
最常见原因是坐标系不适合 Web 地图展示,或者 GeoJSON 没有正确声明 CRS。QGIS 会自动处理很多显示细节,但 Folium 和 Leaflet 更依赖经纬度坐标。建议先检查 gdf.crs 和 gdf.total_bounds。
Streamlit 可以加载 Shapefile 吗?
可以,但 Shapefile 是一组文件,包括 .shp、.shx、.dbf、.prj 等。网页上传时不如 GeoJSON 方便。实际应用中,建议先把 Shapefile 转为 GeoJSON、GeoPackage 或通过压缩包方式上传后再读取。
如何给不同类型的要素设置不同颜色?
可以在 Folium 的 style_function 中根据属性字段返回不同样式。例如按 type 字段设置颜色,把不同类型映射到不同色值即可。需要注意的是,字段值要先清洗,避免空值和拼写不一致导致样式混乱。
结论:GISer 用 Streamlit 做交互式地图的正确姿势
对于 GISer 来说,Streamlit 的价值不在于炫技,而在于把 Python 空间数据处理结果快速变成可交互、可分享的地图应用。只要掌握 GeoJSON 读取、坐标系检查、Folium 渲染和属性筛选这几个关键点,就能搭建出实用的地理数据可视化工具。
如果你的需求是教学演示、项目汇报、数据初筛或内部查看器,可以优先选择 Streamlit + GeoPandas + Folium。若后续数据量变大、访问人数变多、交互逻辑变复杂,再逐步升级到 PostGIS、GeoServer、Leaflet、OpenLayers 或矢量切片架构,会更稳妥。