GIS项目Web可视化太丑?手把手教你用Streamlit打造高颜值交互界面(含:组件源码)
GIS项目Web可视化太丑?手把手教你用Streamlit打造高颜值交互界面(含:组件源码),这个问题很多 GIS 学生和入门工程师都会遇到:空间分析结果已经做出来了,地图也能显示,但交互界面像临时脚本,给老师、甲方或团队演示时缺少“产品感”。本文用一个可复现的 Streamlit GIS 可视化项目,带你把 GeoJSON 数据、指标卡片、筛选控件、交互地图和图表面板整合成一个更清爽的 Web 界面。
引言:为什么 GIS项目Web可视化 经常显得“不够好看”
很多 GIS 项目不是输在空间分析,而是输在展示层。常见情况是:地图占满页面、图例随意摆放、筛选条件不明显、统计图和地图没有联动,最后看起来像“工具窗口截图”,而不是一个完整的 WebGIS 演示界面。
Streamlit 的优势是上手快,不需要完整掌握前端框架,也能用 Python 快速搭建交互式数据应用。对于 GIS 场景,它适合做这些工作:
- 空间分析成果的快速 Web 展示。
- GeoJSON、Shapefile 转换后的轻量级地图预览。
- 项目汇报用的交互式仪表盘。
- 带筛选、统计、地图联动的 GIS 数据探索工具。
- 给非技术人员使用的内部数据查看页面。
本文重点不是讲复杂前端,而是解决一个具体问题:如何用 Streamlit 把 GIS 数据做成更美观、更易用、更适合演示的交互界面。

背景:适合用 Streamlit 做 GIS Web 可视化的场景
Streamlit 不是传统意义上的 WebGIS 框架,它更像是一个 Python 数据应用框架。它的核心价值是:用少量 Python 代码,把数据处理、地图展示和交互控件组织成一个可访问的网页。
对于 GIS 项目,以下场景特别适合使用 Streamlit:
- 课程设计展示:例如城市 POI 分析、人口密度分析、道路可达性分析。
- 毕业设计原型:需要一个能点击、能筛选、能看图表的 Web 展示页面。
- 空间分析报告:把缓冲区分析、叠加分析、栅格统计结果做成可交互仪表盘。
- 内部数据查看:给业务同事查看地块、网点、管线、巡检点等空间数据。
- 算法结果演示:比如路径规划、选址分析、风险分区、变化检测结果展示。
但也要注意,Streamlit 并不适合替代完整 WebGIS 系统。如果项目需要复杂权限、多人编辑、海量矢量瓦片、前端定制组件和长期运维,通常还是要考虑 OpenLayers、Leaflet、Mapbox GL JS 或 Cesium 等更专业的前端方案。
原理:Streamlit GIS 可视化界面由哪些部分组成
一个好看的 Streamlit GIS 可视化界面,本质上由五个部分组成:
- 数据读取层:使用 GeoPandas 读取 GeoJSON、Shapefile、GeoPackage 等空间数据。
- 空间处理层:完成坐标系转换、字段清洗、分类统计、范围筛选。
- 地图展示层:使用 Folium、PyDeck 或 Plotly 展示交互地图。
- 交互控制层:使用 Streamlit 的 sidebar、selectbox、multiselect、slider 等控件。
- 信息表达层:用指标卡片、图表、表格和说明文字让用户理解地图结果。
如果只把地图塞进页面,界面通常会显得粗糙。更推荐的布局是:左侧控制条件,中间地图展示,顶部给关键指标,底部补充图表和明细表。
在本文示例中,我们使用以下技术组合:
- Streamlit:搭建 Web 应用界面。
- GeoPandas:读取和处理 GIS 矢量数据。
- Folium:生成 Leaflet 风格的交互地图。
- streamlit-folium:把 Folium 地图嵌入 Streamlit 页面。
- Pandas:做属性表统计和图表数据整理。
步骤:从零搭建一个高颜值 Streamlit GIS 交互界面
步骤 1:准备 Python 环境
建议使用独立虚拟环境,避免和 ArcGIS Pro、QGIS 自带 Python 环境混在一起。下面示例以普通 Python 环境为准。
pip install streamlit geopandas folium streamlit-folium pandas shapely pyproj
如果你在 Windows 上安装 GeoPandas 报错,可以优先使用 Conda 环境:
conda create -n streamlit-gis python=3.10
conda activate streamlit-gis
conda install -c conda-forge geopandas folium streamlit-folium streamlit
步骤 2:准备示例数据结构
假设我们有一个城市公共服务点数据,文件名为 data/poi.geojson,至少包含以下字段:
| 字段名 | 含义 | 示例 |
|---|---|---|
name |
点位名称 | 第一人民医院 |
type |
设施类型 | 医院、学校、公园 |
district |
所属区县 | 海淀区 |
score |
综合评分 | 86 |
geometry |
空间几何 | Point |
如果你手头是 Shapefile,也可以先用 QGIS 另存为 GeoJSON,或者在 Python 中直接读取 .shp。需要注意的是,Shapefile 字段名可能被截断,中文编码也更容易出问题。
步骤 3:创建 Streamlit 主程序
新建文件 app.py,先完成页面配置、数据读取和基础样式。
import streamlit as st
import geopandas as gpd
import pandas as pd
import folium
from streamlit_folium import st_folium
st.set_page_config(
page_title="GIS 项目 Web 可视化仪表盘",
page_icon="🗺️",
layout="wide"
)
st.markdown(
"""
<style>
.main {
background-color: #f7f9fc;
}
.block-container {
padding-top: 1.5rem;
padding-bottom: 2rem;
}
.metric-card {
background: white;
padding: 18px;
border-radius: 14px;
box-shadow: 0 4px 16px rgba(0,0,0,0.06);
border: 1px solid #edf0f5;
}
</style>
""",
unsafe_allow_html=True
)
@st.cache_data
def load_data(path):
gdf = gpd.read_file(path)
if gdf.crs is None:
gdf = gdf.set_crs(epsg=4326)
else:
gdf = gdf.to_crs(epsg=4326)
return gdf
gdf = load_data("data/poi.geojson")
这里有两个关键点:
layout="wide"可以让 GIS 地图获得更宽的显示空间。- 地图展示通常需要 WGS84 坐标系,也就是
EPSG:4326,因此读取后要统一转换。
步骤 4:设计侧边栏筛选控件
GIS项目Web可视化要好用,筛选控件必须清晰。不要把所有字段都堆在页面上,优先放用户最关心的字段,例如行政区、类型、评分范围。
st.sidebar.title("筛选条件")
district_options = sorted(gdf["district"].dropna().unique().tolist())
type_options = sorted(gdf["type"].dropna().unique().tolist())
selected_districts = st.sidebar.multiselect(
"选择区县",
district_options,
default=district_options
)
selected_types = st.sidebar.multiselect(
"选择设施类型",
type_options,
default=type_options
)
min_score = int(gdf["score"].min())
max_score = int(gdf["score"].max())
score_range = st.sidebar.slider(
"评分范围",
min_value=min_score,
max_value=max_score,
value=(min_score, max_score)
)
filtered = gdf[
(gdf["district"].isin(selected_districts)) &
(gdf["type"].isin(selected_types)) &
(gdf["score"].between(score_range[0], score_range[1]))
].copy()
这样用户可以通过左侧控件动态控制地图内容,页面会自动刷新。对于 GIS 展示项目来说,这比静态地图截图更有说服力。
步骤 5:添加标题区和指标卡片
高颜值 Streamlit 界面的关键不只是颜色,而是信息层次。顶部最好先告诉用户当前数据规模、筛选结果和核心指标。
st.title("城市公共服务设施 GIS 可视化仪表盘")
st.caption("基于 Streamlit + GeoPandas + Folium 的轻量级 WebGIS 展示示例")
col1, col2, col3, col4 = st.columns(4)
total_count = len(filtered)
district_count = filtered["district"].nunique()
type_count = filtered["type"].nunique()
avg_score = round(filtered["score"].mean(), 1) if total_count > 0 else 0
with col1:
st.metric("点位数量", total_count)
with col2:
st.metric("覆盖区县", district_count)
with col3:
st.metric("设施类型", type_count)
with col4:
st.metric("平均评分", avg_score)
指标卡片适合放“第一眼要看的信息”。如果你的项目是土地利用,可以放建设用地面积、耕地面积、变化面积;如果是管线巡检,可以放隐患点数量、已处理数量、超期数量。
步骤 6:用 Folium 绘制交互地图
下面是核心地图组件源码。它会根据筛选结果动态显示点位,并按设施类型设置颜色。
def get_color(category):
color_map = {
"医院": "red",
"学校": "blue",
"公园": "green",
"商场": "purple",
"交通": "orange"
}
return color_map.get(category, "gray")
def create_map(data):
if data.empty:
return folium.Map(location=[35.0, 105.0], zoom_start=4, tiles="CartoDB positron")
center_lat = data.geometry.y.mean()
center_lon = data.geometry.x.mean()
m = folium.Map(
location=[center_lat, center_lon],
zoom_start=11,
tiles="CartoDB positron"
)
for _, row in data.iterrows():
popup_html = f"""
<b>名称:</b>{row["name"]}<br>
<b>类型:</b>{row["type"]}<br>
<b>区县:</b>{row["district"]}<br>
<b>评分:</b>{row["score"]}
"""
folium.CircleMarker(
location=[row.geometry.y, row.geometry.x],
radius=7,
color=get_color(row["type"]),
fill=True,
fill_color=get_color(row["type"]),
fill_opacity=0.75,
popup=folium.Popup(popup_html, max_width=260),
tooltip=row["name"]
).add_to(m)
folium.LayerControl().add_to(m)
return m
把地图嵌入页面:
left_col, right_col = st.columns([2.2, 1])
with left_col:
st.subheader("交互地图")
map_obj = create_map(filtered)
st_folium(map_obj, width=None, height=620)
with right_col:
st.subheader("类型统计")
type_count_df = filtered["type"].value_counts().reset_index()
type_count_df.columns = ["设施类型", "数量"]
st.bar_chart(type_count_df.set_index("设施类型"))
st.subheader("区县统计")
district_count_df = filtered["district"].value_counts().reset_index()
district_count_df.columns = ["区县", "数量"]
st.dataframe(district_count_df, use_container_width=True)
这里使用左右分栏:左侧放地图,右侧放统计图和表格。这个布局很适合 GIS项目Web可视化,因为地图负责空间位置,图表负责数量对比,表格负责明细核查。
步骤 7:添加数据明细和下载按钮
实际项目中,用户通常希望筛选后能看到数据表,甚至导出结果。可以继续添加明细表和 CSV 下载。
st.subheader("筛选结果明细")
show_columns = ["name", "type", "district", "score"]
st.dataframe(
filtered[show_columns],
use_container_width=True,
hide_index=True
)
csv_data = filtered[show_columns].to_csv(index=False).encode("utf-8-sig")
st.download_button(
label="下载筛选结果 CSV",
data=csv_data,
file_name="filtered_gis_result.csv",
mime="text/csv"
)
如果数据包含中文,导出 CSV 时建议使用 utf-8-sig,这样用 Excel 打开时更不容易乱码。
步骤 8:运行项目
在终端进入项目目录,执行:
streamlit run app.py
浏览器打开本地地址后,你就可以看到一个具备筛选、指标卡片、交互地图、图表和数据下载功能的 Streamlit GIS 可视化界面。
常见坑:Streamlit GIS 可视化最容易出错的地方
1. 地图点位跑到海上或完全不显示
最常见原因是坐标系错误。Folium 和 Leaflet 默认使用经纬度坐标,也就是 WGS84。如果你的数据是 CGCS2000 高斯投影、Web Mercator 或地方坐标,必须先转换到 EPSG:4326。
gdf = gdf.to_crs(epsg=4326)
判断方法很简单:经度通常在 -180 到 180 之间,纬度通常在 -90 到 90 之间。如果坐标值是几十万或几百万,多半不是经纬度。
2. Shapefile 中文字段乱码
Shapefile 由多个文件组成,编码问题很常见。建议优先把数据转换为 GeoPackage 或 GeoJSON。如果必须读取 Shapefile,可以尝试指定编码:
gdf = gpd.read_file("data/poi.shp", encoding="utf-8")
gdf = gpd.read_file("data/poi.shp", encoding="gbk")
如果一个不行,再换另一个。国内历史数据中 gbk 很常见。
3. 数据量太大导致页面卡顿
Streamlit GIS 可视化适合中小规模数据快速展示。如果一次性渲染几万甚至几十万个点,Folium 地图会明显变慢。
可以考虑这些优化:
- 先按行政区、类型或时间筛选,再渲染地图。
- 只显示当前视图需要的数据,不要全量加载。
- 点数据过多时使用聚合显示,例如 MarkerCluster。
- 大规模矢量数据考虑切片服务或矢量瓦片。
- 面数据先进行简化,减少几何节点数量。
4. 页面看起来还是很“脚本感”
原因通常不是 Streamlit 不行,而是布局和信息层次没有设计。建议遵循这个顺序:
- 顶部放项目标题和一句话说明。
- 左侧放筛选条件。
- 第一屏放核心指标和地图。
- 地图旁边放图表,而不是把所有内容垂直堆叠。
- 底部放明细表、下载按钮和说明。
5. Streamlit 部署后找不到数据文件
部署时要检查相对路径。推荐项目结构如下:
streamlit-gis-demo/
├── app.py
├── data/
│ └── poi.geojson
├── requirements.txt
代码中使用:
gdf = load_data("data/poi.geojson")
不要写成本机绝对路径,例如 D:projectdatapoi.geojson,否则部署到服务器后会直接失效。
方法比较:Streamlit、Leaflet、OpenLayers、ArcGIS Experience Builder 怎么选
| 方案 | 适合场景 | 优点 | 限制 |
|---|---|---|---|
| Streamlit | Python GIS 分析结果展示、课程设计、内部原型 | 开发快,Python 友好,适合数据分析和仪表盘 | 复杂前端交互和大型 WebGIS 能力有限 |
| Leaflet | 轻量 Web 地图、点线面展示、移动端地图 | 轻巧,插件丰富,学习成本相对低 | 需要写 JavaScript,复杂工程需自行组织 |
| OpenLayers | 专业 WebGIS、复杂图层控制、投影支持 | GIS 能力强,适合严肃 WebGIS 项目 | 学习曲线比 Leaflet 高 |
| ArcGIS Experience Builder | ArcGIS 生态内的快速应用搭建 | 低代码,和 ArcGIS Online、Portal 集成好 | 依赖 Esri 生态,定制和部署受平台影响 |
简单判断:如果你的核心工作在 Python 里,目标是快速展示 GIS 分析成果,Streamlit 很合适;如果你要做正式 WebGIS 产品,尤其涉及复杂图层、权限、编辑和长期维护,就应该考虑 Leaflet、OpenLayers 或成熟 GIS 平台。
检查清单:做一个更好看的 Streamlit GIS 项目前先核对这些项
- 坐标系:地图展示前是否统一到
EPSG:4326? - 字段:筛选字段、统计字段、弹窗字段是否清晰可读?
- 布局:是否使用宽屏布局和合理分栏?
- 第一屏:用户打开后是否能立刻看到标题、指标和地图?
- 交互:是否提供区县、类型、时间、评分等关键筛选?
- 地图:底图是否简洁?点线面符号是否区分明显?
- 图表:是否用图表解释地图背后的数量关系?
- 性能:是否避免一次性渲染过大的矢量数据?
- 导出:是否提供筛选结果下载?
- 部署:是否使用相对路径,并准备好
requirements.txt?
FAQ:Streamlit GIS 可视化常见问题
Q1:Streamlit 能不能直接显示 Shapefile?
可以。使用 GeoPandas 的 read_file 可以读取 Shapefile,但要保证 .shp、.dbf、.shx、.prj 等文件完整。为了部署和分享更方便,建议转换成 GeoJSON 或 GeoPackage。
Q2:Streamlit 做 GIS项目Web可视化 是否需要会 JavaScript?
不一定。使用 Folium、PyDeck、Plotly 等 Python 库,可以完成很多地图展示和交互。如果要做非常复杂的前端地图交互,例如自定义绘制、复杂图层管理、前端空间编辑,就需要 JavaScript 和 WebGIS 前端框架。
Q3:Folium 和 PyDeck 选哪个更适合 GIS 地图?
Folium 更接近传统 Leaflet 地图,适合点线面、弹窗、图层控制等常规 GIS 展示。PyDeck 更适合大规模点、轨迹、热力和 3D 风格的数据可视化。入门项目建议先用 Folium。
Q4:Streamlit 页面如何变得更美观?
优先调整信息结构,而不是一开始就改 CSS。先做好标题、指标卡片、分栏、地图、图表和表格的位置关系。必要时再用少量 CSS 调整背景、边距、卡片圆角和阴影。
Q5:数据量很大时还适合用 Streamlit 吗?
适合做分析后台和结果预览,但不适合直接渲染海量原始矢量数据。大数据量场景建议先做抽稀、聚合、分区筛选,或者使用 PostGIS、GeoServer、矢量瓦片等服务端方案。
Q6:Streamlit GIS 应用怎么部署?
常见方式包括内网服务器部署、Docker 部署、Streamlit Community Cloud 部署等。部署前要准备 requirements.txt,并确保数据文件路径使用相对路径。如果涉及敏感空间数据,建议优先部署在单位内网环境。
结论:用 Streamlit 快速提升 GIS 项目的展示效果
GIS项目Web可视化太丑,通常不是因为分析结果不专业,而是界面没有组织好。用 Streamlit 可以在不深入前端开发的情况下,把 GIS 数据读取、筛选、交互地图、指标卡片、统计图和结果下载整合到一个清爽的 Web 页面中。
本文给出的组件源码可以作为一个基础模板:左侧筛选、顶部指标、中间地图、右侧统计、底部明细。你可以把示例中的 POI 数据替换成地块、管线、道路、遥感解译结果或空间分析成果,再根据项目主题调整字段和符号样式。
如果你的目标是课程设计、毕业设计、项目汇报或内部 GIS 数据看板,Streamlit 是一个非常实用的选择;如果后续要走向正式 WebGIS 产品,再逐步迁移到 Leaflet、OpenLayers、Cesium 或企业 GIS 平台会更稳妥。