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

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

引言

《GISer还在为地理数据可视化发愁?Streamlit读音读对了吗,一文教你搭建交互式地图应用(附:GeoJSON加载源码)》这篇文章,解决的是一个很具体的问题:手里有 GeoJSON、Shapefile 或空间分析结果,但不想从零搭建 WebGIS 前后端,怎样快速做出一个可交互、可分享、可演示的地图应用。

很多 GISer 会写 Python,也会用 QGIS、ArcGIS Pro 做数据处理,但一到“把结果做成网页地图”,就会被前端框架、地图 SDK、部署环境卡住。Streamlit 的价值就在这里:它可以用很少的 Python 代码,把数据表、筛选控件、图表和交互式地图组合成一个轻量级 GIS 数据可视化应用。

顺便说一句,Streamlit 常见读音可以近似读作“斯特림利特”或“stream-lit”,重点不是读得多标准,而是理解它的定位:它不是专业 WebGIS 引擎,而是一个适合快速构建数据应用的 Python 框架。

Streamlit地理数据可视化与GeoJSON加载源码流程图
使用 Streamlit 搭建 GeoJSON 交互式地图应用的基本流程。

背景

在 GIS 项目中,地理数据可视化通常有三类需求:

  • 把 GeoJSON、Shapefile、CSV 点位数据快速展示出来。
  • 给业务人员提供筛选、缩放、点击查看属性等交互能力。
  • 把空间分析结果做成一个可演示、可复现的小应用。

传统做法可能需要 Leaflet、OpenLayers、Vue、React、后端接口、瓦片服务等组件。对于正式 WebGIS 项目,这些技术很重要;但对于教学演示、数据质检、项目汇报和内部工具,完整前后端架构往往过重。

Streamlit 更适合以下场景:

  • 你已经用 Python 处理了 GIS 数据,希望直接可视化结果。
  • 你需要一个快速原型,而不是复杂生产系统。
  • 你希望通过侧边栏控件筛选地图内容。
  • 你要把 GeoJSON 加载到交互式地图中,并保留基本属性查看能力。

原理

Streamlit 的核心思路是:用 Python 脚本描述页面,脚本从上到下执行,用户调整控件后页面自动刷新。对于 GIS 数据可视化,可以把它理解为三个环节:

  1. 使用 GeoPandas 读取 GeoJSON、Shapefile 等空间数据。
  2. 使用 Folium 或 PyDeck 构建交互式地图对象。
  3. 使用 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 数据分析和项目汇报中非常高效的一环。