GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)

编程与开发
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

如果你正在搜索“GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)”,大概率是想用最少的前端代码,把 GeoJSON、点线面图层和属性筛选做成一个可交互地图应用。本文用 Streamlit 搭配 Folium,演示一个 GISer 能直接复用的 GeoJSON 加载与地图可视化流程。

引言:用 Streamlit 做地理数据可视化到底适合谁

Streamlit 常被读作“斯特里姆-lit”,更接近英文中的 “stream-lit”。读音不是重点,重点是它能让 Python 用户快速把脚本变成交互式网页应用。对于 GIS 学生、空间数据分析师和初级 GIS 工程师来说,Streamlit 的优势是:不用先学习完整的 Vue、React 或复杂 WebGIS 工程,也能快速做出一个可演示、可筛选、可部署的交互式地图。

本文聚焦一个具体问题:如何用 Streamlit 加载 GeoJSON,并在网页中显示交互式地图。你将得到一个可运行的最小项目,包括环境安装、目录结构、GeoJSON 读取、Folium 地图渲染、属性字段筛选和常见报错排查。

Streamlit地理数据可视化 GeoJSON加载源码交互式地图流程图
Streamlit 加载 GeoJSON 并生成交互式地图应用的基本工作流。

背景:为什么 GISer 会卡在 GeoJSON 地图可视化

很多 GISer 的数据处理能力并不弱,问题通常卡在“展示”这一环。比如你已经有了行政区边界、采样点、道路中心线或项目范围 GeoJSON,但想给同事或甲方一个可交互页面时,会遇到这些问题:

  • 只会在 QGIS 或 ArcGIS Pro 里看图,无法做成网页应用。
  • 会 Python,但不会写前端页面和 WebGIS 框架。
  • GeoJSON 文件能打开,但放到网页中加载慢、样式难调。
  • 想按字段筛选图层,却不知道如何把筛选控件和地图联动。
  • 想快速分享成果,但不想搭建复杂服务器。

Streamlit 适合解决这类轻量级 GIS 可视化需求。它不替代专业 WebGIS 平台,也不适合直接承载超大规模生产级地图服务,但非常适合数据探索、教学演示、项目汇报、内部工具和原型验证。

原理:Streamlit 加载 GeoJSON 的基本机制

Streamlit 本身负责网页界面和交互控件,Folium 负责生成 Leaflet 地图,GeoPandas 负责读取和处理空间数据。三者组合起来,可以形成一条清晰的数据流:

  1. GeoPandas 读取本地 GeoJSON 文件,生成 GeoDataFrame。
  2. 检查坐标系,确保数据适合在 Web 地图中展示。
  3. Streamlit 提供上传文件、字段选择、属性筛选等交互控件。
  4. Folium 把筛选后的 GeoJSON 添加到 Leaflet 地图中。
  5. 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.crsgdf.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 或矢量切片架构,会更稳妥。