想用Streamlit开发GIS Web应用?手把手教你搭建(附:3个GIS项目源码)
如果你正在搜索“想用Streamlit开发GIS Web应用?手把手教你搭建(附:3个GIS项目源码)”,大概率是想把已有的 Shapefile、GeoJSON、CSV 点位或栅格分析结果,快速做成一个可交互的 WebGIS 小应用,而不是从零学习复杂的前端框架。
这篇文章面向 GIS 学生、初级 GIS 工程师和空间数据分析人员,重点讲清楚:如何用 Streamlit 搭建 GIS Web 应用、如何加载地图与空间数据、如何组织项目代码,以及如何扩展出 3 个常见 GIS 项目原型。

引言:为什么 GIS 项目适合先用 Streamlit 做原型
传统 WebGIS 开发通常需要前端框架、地图 SDK、后端接口、数据库和部署环境。对于很多 GIS 分析型项目来说,这个技术栈偏重,尤其是当你的目标只是展示数据、调试模型、做内部工具或快速验证业务流程时。
Streamlit 的优势在于:用 Python 就能完成页面、控件、图表和交互逻辑。对于已经熟悉 ArcPy、GeoPandas、Rasterio、PostGIS 或 Jupyter Notebook 的 GIS 用户来说,学习成本明显更低。
用 Streamlit 开发 GIS Web 应用,适合以下场景:
- 把 GeoJSON、Shapefile、CSV 点位快速发布为交互式地图。
- 给缓冲区分析、空间叠加、热力图、选址分析等流程做可视化界面。
- 为项目组内部搭建一个轻量 GIS 数据查看器。
- 把 Python 空间分析脚本包装成非技术同事也能使用的工具。
- 制作课程作业、竞赛原型、汇报演示或数据质检面板。
背景:Streamlit GIS Web应用通常由哪些部分组成
一个实用的 Streamlit GIS Web 应用,一般不只是“显示一张地图”。它通常由数据输入、空间处理、地图渲染、属性表查看和结果下载几个部分组成。
| 模块 | 常用工具 | 作用 |
|---|---|---|
| 页面与交互 | Streamlit | 创建按钮、下拉框、侧边栏、文件上传、结果展示 |
| 矢量数据处理 | GeoPandas、Shapely、Fiona | 读取 Shapefile、GeoJSON,执行缓冲区、裁剪、叠加等操作 |
| 栅格数据处理 | Rasterio、rioxarray | 读取 GeoTIFF、提取像元值、做栅格统计 |
| 地图展示 | Folium、streamlit-folium、PyDeck | 在网页中显示底图、点线面、热力图和交互图层 |
| 空间数据库 | PostGIS、SQLAlchemy、psycopg | 存储和查询大规模空间数据 |
| 部署发布 | Streamlit Community Cloud、Docker、服务器 | 让别人通过浏览器访问应用 |
对于入门阶段,建议优先使用 Streamlit + GeoPandas + Folium + streamlit-folium。这个组合足以完成大多数矢量 GIS Web 应用原型。
原理:Streamlit如何把Python脚本变成GIS Web应用
Streamlit 的核心机制是“脚本即应用”。你写一个 Python 文件,例如 app.py,然后运行 streamlit run app.py,Streamlit 会把脚本中的控件、图表、地图和文本渲染成网页。
当用户在页面上点击按钮、上传文件或修改参数时,Streamlit 会重新执行脚本,并根据当前控件状态刷新页面结果。这个机制非常适合 GIS 分析流程,因为很多 GIS 任务本身就是“输入数据和参数,然后生成结果”。
在 GIS Web 应用中,常见的数据流如下:
- 用户上传 GeoJSON、Shapefile 压缩包或 CSV 点位数据。
- Python 使用 GeoPandas 读取数据。
- 检查坐标系,必要时转换为 WGS84,即 EPSG:4326。
- 使用 Folium 或 PyDeck 创建交互地图。
- 通过 Streamlit 将地图、属性表和统计结果显示出来。
- 用户下载处理后的 GeoJSON、CSV 或图表结果。
注意:Web 地图常用经纬度坐标 EPSG:4326 或 Web Mercator EPSG:3857。如果你的数据是 CGCS2000、高斯投影或地方坐标系,直接加载到 Web 地图上很容易出现位置偏移或显示为空。
步骤:从零搭建一个Streamlit GIS Web应用
步骤1:准备Python环境
建议使用独立虚拟环境,避免和 ArcGIS Pro、QGIS 自带 Python 环境混在一起。下面以 Conda 为例:
conda create -n streamlit-gis python=3.11 -y
conda activate streamlit-gis
pip install streamlit geopandas folium streamlit-folium shapely pyproj pandas
如果你需要处理栅格数据,可以继续安装:
pip install rasterio rioxarray
如果你准备连接 PostGIS,可以安装:
pip install sqlalchemy psycopg2-binary geoalchemy2
步骤2:创建项目目录
建议一开始就把目录组织清楚,后面扩展项目会容易很多。
streamlit-gis-demo/
├── app.py
├── requirements.txt
├── data/
│ └── sample.geojson
├── pages/
│ ├── 1_点位地图.py
│ ├── 2_缓冲区分析.py
│ └── 3_空间查询.py
└── outputs/
其中 app.py 是首页,pages 目录可以放多个功能页面。Streamlit 会自动识别多页面应用。
步骤3:写一个最小可运行的GIS地图页面
下面这个示例会读取 GeoJSON,并在 Folium 地图中显示。请确保数据坐标系是 EPSG:4326。
import streamlit as st
import geopandas as gpd
import folium
from streamlit_folium import st_folium
st.set_page_config(page_title="Streamlit GIS Web应用", layout="wide")
st.title("Streamlit GIS Web应用示例")
st.write("上传或读取GeoJSON数据,并在交互式地图中显示。")
uploaded_file = st.file_uploader("上传GeoJSON文件", type=["geojson", "json"])
if uploaded_file is not None:
gdf = gpd.read_file(uploaded_file)
if gdf.crs is None:
st.warning("数据缺少坐标系信息,请先确认是否为EPSG:4326。")
else:
gdf = gdf.to_crs(epsg=4326)
st.write("属性表预览")
st.dataframe(gdf.drop(columns="geometry").head())
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="上传图层",
tooltip=folium.GeoJsonTooltip(fields=list(gdf.columns.drop("geometry"))[:3])
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=1100, height=600)
else:
st.info("请上传一个GeoJSON文件。")
运行应用:
streamlit run app.py
浏览器打开后,你就能看到文件上传控件、属性表和交互地图。这就是一个最小版的 Streamlit GIS Web 应用。
步骤4:处理Shapefile上传
Shapefile 不是单文件格式,至少包含 .shp、.shx、.dbf,通常还需要 .prj。在 Web 应用中,建议让用户上传 ZIP 压缩包,然后解压读取。
import tempfile
import zipfile
from pathlib import Path
import geopandas as gpd
def read_shapefile_zip(uploaded_zip):
with tempfile.TemporaryDirectory() as tmpdir:
zip_path = Path(tmpdir) / "upload.zip"
zip_path.write_bytes(uploaded_zip.read())
with zipfile.ZipFile(zip_path, "r") as z:
z.extractall(tmpdir)
shp_files = list(Path(tmpdir).glob("*.shp"))
if not shp_files:
raise ValueError("压缩包中没有找到.shp文件")
gdf = gpd.read_file(shp_files[0])
return gdf
在实际项目中,还要检查是否存在 .prj 文件。如果没有坐标系信息,后续地图定位和空间分析都可能出错。
步骤5:加入坐标系检查
GIS Web 应用最常见的问题之一,就是数据加载成功但地图上看不到。很多时候不是代码错了,而是坐标系不对。
def ensure_wgs84(gdf):
if gdf.crs is None:
raise ValueError("数据没有坐标系信息,无法安全转换到EPSG:4326")
return gdf.to_crs(epsg=4326)
如果你的原始数据是投影坐标系,例如 EPSG:4547、EPSG:4490、EPSG:3857 或地方独立坐标系,需要先确认坐标系定义是否正确,再转换到 Web 地图可用的坐标系。
步骤:3个GIS项目源码原型
项目1:GeoJSON在线查看器
这个项目适合做 GIS 数据预览、作业检查和项目成果浏览。功能包括上传 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.header("GeoJSON在线查看器")
file = st.file_uploader("上传GeoJSON", type=["geojson", "json"])
if file:
gdf = gpd.read_file(file)
if gdf.crs is not None:
gdf = gdf.to_crs(epsg=4326)
else:
st.warning("未检测到坐标系,默认按EPSG:4326显示。")
gdf = gdf.set_crs(epsg=4326)
bounds = gdf.total_bounds
center = [(bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2]
col1, col2 = st.columns([2, 1])
with col1:
m = folium.Map(location=center, zoom_start=9)
folium.GeoJson(gdf, name="GeoJSON图层").add_to(m)
st_folium(m, width=850, height=550)
with col2:
st.metric("要素数量", len(gdf))
st.write("字段列表")
st.write(list(gdf.columns))
st.write("属性表")
st.dataframe(gdf.drop(columns="geometry"))
output = gdf.to_json()
st.download_button(
"下载GeoJSON",
output,
file_name="converted.geojson",
mime="application/geo+json"
)
这个项目的关键点是 坐标系统一 和 属性表预览。如果数据比较大,建议不要一次性把全部属性都渲染出来,可以只显示前 100 行。
项目2:点位缓冲区分析工具
这个项目适合做门店服务范围、采样点影响范围、设施覆盖范围等分析。用户上传点数据,输入缓冲半径,应用自动生成缓冲区并显示。
import streamlit as st
import geopandas as gpd
import folium
from streamlit_folium import st_folium
st.set_page_config(page_title="点位缓冲区分析工具", layout="wide")
st.header("点位缓冲区分析工具")
file = st.file_uploader("上传点GeoJSON", type=["geojson", "json"])
radius = st.number_input("缓冲半径(米)", min_value=100, max_value=50000, value=1000, step=100)
if file:
points = gpd.read_file(file)
if points.crs is None:
st.error("数据缺少坐标系,无法进行可靠的米制缓冲区分析。")
st.stop()
points_wgs84 = points.to_crs(epsg=4326)
projected = points.to_crs(epsg=3857)
buffers = projected.copy()
buffers["geometry"] = projected.geometry.buffer(radius)
buffers = buffers.to_crs(epsg=4326)
center = [
points_wgs84.geometry.y.mean(),
points_wgs84.geometry.x.mean()
]
m = folium.Map(location=center, zoom_start=11)
folium.GeoJson(
buffers,
name="缓冲区",
style_function=lambda x: {
"fillColor": "#3186cc",
"color": "#08519c",
"weight": 1,
"fillOpacity": 0.35
}
).add_to(m)
folium.GeoJson(
points_wgs84,
name="点位"
).add_to(m)
folium.LayerControl().add_to(m)
st_folium(m, width=1100, height=600)
st.write("缓冲区属性表")
st.dataframe(buffers.drop(columns="geometry").head())
这里要特别注意:不要直接在 EPSG:4326 经纬度坐标下做米制缓冲区。经纬度单位是度,不是米。更严谨的做法是根据研究区选择合适的投影坐标系,例如 UTM 分带或国家标准投影。
项目3:PostGIS空间查询面板
这个项目适合把数据库里的空间数据做成查询工具,例如按行政区筛选地块、按范围查询设施、按类型显示 POI。下面示例演示从 PostGIS 读取数据并显示到 Streamlit 地图中。
import streamlit as st
import geopandas as gpd
import folium
from sqlalchemy import create_engine
from streamlit_folium import st_folium
st.set_page_config(page_title="PostGIS空间查询面板", layout="wide")
st.header("PostGIS空间查询面板")
db_url = st.text_input(
"PostGIS连接字符串",
value="postgresql://user:password@localhost:5432/gisdb"
)
table_name = st.text_input("表名", value="public.poi")
type_value = st.text_input("筛选类型字段值", value="学校")
if st.button("查询"):
engine = create_engine(db_url)
sql = f"""
SELECT *
FROM {table_name}
WHERE type = %(type_value)s
LIMIT 1000
"""
gdf = gpd.read_postgis(
sql,
engine,
geom_col="geom",
params={"type_value": type_value}
)
if gdf.empty:
st.warning("没有查询到数据。")
st.stop()
gdf = gdf.to_crs(epsg=4326)
center = [
gdf.geometry.centroid.y.mean(),
gdf.geometry.centroid.x.mean()
]
m = folium.Map(location=center, zoom_start=12)
folium.GeoJson(gdf, name="查询结果").add_to(m)
st_folium(m, width=1100, height=600)
st.write("查询结果")
st.dataframe(gdf.drop(columns="geom", errors="ignore").drop(columns="geometry", errors="ignore").head(100))
真实项目中,不建议把数据库账号密码直接写在页面输入框里。更合适的做法是使用 Streamlit 的 secrets 配置,或者在服务器环境变量中管理连接信息。
常见坑:Streamlit GIS Web应用最容易出错的地方
1. 地图空白或数据跑到海里
优先检查坐标系。Web 地图常见底图通常需要经纬度坐标或 Web Mercator 坐标。如果你的 GeoDataFrame 没有 crs,或者错误地把投影坐标当成 EPSG:4326,地图就会出现严重偏移。
- 检查
gdf.crs是否为空。 - 确认原始数据的真实坐标系。
- 使用
to_crs(epsg=4326)前,必须先有正确的 CRS 定义。 - 不要用
set_crs代替to_crs做坐标转换。
2. Shapefile上传后读取失败
Shapefile 是一组文件,不是单个 .shp 文件。用户只上传 .shp 通常会失败,或者属性丢失。
- 要求用户上传 ZIP 压缩包。
- 压缩包中至少包含
.shp、.shx、.dbf。 - 建议包含
.prj,否则无法可靠识别坐标系。 - 中文字段或中文路径可能导致编码问题,建议优先使用 UTF-8。
3. 数据量稍大页面就很卡
Streamlit 很适合快速原型,但不是万能的高并发 WebGIS 引擎。几万到几十万个要素直接传给 Folium,浏览器可能明显卡顿。
- 先对数据做简化,例如
simplify。 - 只显示当前范围或筛选后的数据。
- 大数据量优先使用 PostGIS 做空间查询。
- 矢量切片、栅格切片和地图服务更适合大规模发布。
- 属性表不要一次性显示全部记录。
4. 缓冲区面积和距离不准确
如果直接在 EPSG:4326 下做距离和面积计算,结果通常不可靠。因为 EPSG:4326 的单位是度,不是米。
- 距离、面积、缓冲区分析应使用合适的投影坐标系。
- 小范围项目可选择当地 UTM 投影。
- 全国尺度数据要谨慎选择等面积投影或分区处理。
- 分析完成后,再转换为 EPSG:4326 用于 Web 地图显示。
5. 部署后本地能跑,服务器不能跑
常见原因包括依赖没有写入 requirements.txt、服务器缺少 GDAL 相关库、路径写死为本地绝对路径、上传文件没有临时目录权限等。
- 使用相对路径,不要写
D:projectdata这类本地路径。 - 把依赖固定在
requirements.txt。 - 部署前在干净环境中测试安装。
- 大文件上传要考虑服务器内存和超时限制。
方法比较:Streamlit、Dash、传统WebGIS怎么选
| 方案 | 适合场景 | 优点 | 限制 |
|---|---|---|---|
| Streamlit | GIS分析工具、数据看板、快速原型、内部应用 | Python友好,开发快,适合空间分析脚本包装 | 复杂前端交互和高并发能力有限 |
| Dash | 数据可视化系统、图表联动、企业内部面板 | 交互能力较强,组件体系成熟 | 代码结构比Streamlit更复杂 |
| Leaflet / OpenLayers | 正式WebGIS系统、地图编辑、复杂图层控制 | 前端能力强,生态成熟,适合生产系统 | 需要JavaScript和前后端开发经验 |
| QGIS插件 | 桌面端GIS工具扩展 | 能直接利用QGIS环境和图层 | 不适合浏览器访问和多人共享 |
| ArcGIS Experience Builder | ArcGIS生态内的Web应用搭建 | 配置化程度高,适合ArcGIS服务 | 生态绑定较强,定制开发成本较高 |
如果你的目标是“快速把 Python GIS 分析流程变成一个别人能点选参数、查看地图、下载结果的页面”,Streamlit 是非常合适的。如果你的目标是正式上线的公众 WebGIS 平台,建议使用 Leaflet、OpenLayers、Cesium、GeoServer、PostGIS 等更完整的 WebGIS 架构。
检查清单:发布前必须确认的GIS细节
- 坐标系:所有用于 Web 地图展示的数据是否已转换为 EPSG:4326。
- 分析坐标:距离、面积、缓冲区是否在合适的投影坐标系中计算。
- 数据大小:上传文件大小、要素数量、字段数量是否会导致页面卡顿。
- 字段安全:是否隐藏了身份证号、手机号、精确住址等敏感字段。
- 路径写法:是否避免使用本地绝对路径。
- 依赖文件:
requirements.txt是否包含全部依赖包。 - 异常提示:上传错误文件、缺少坐标系、空数据时是否有明确提示。
- 结果验证:输出数据是否能在 QGIS 或 ArcGIS Pro 中正常打开。
- 部署环境:服务器是否支持 GeoPandas、GDAL、Rasterio 等依赖。
- 访问权限:数据库账号、API Key、内部数据是否已做权限控制。
FAQ:Streamlit开发GIS Web应用常见问题
Q1:Streamlit可以开发真正的WebGIS系统吗?
可以开发轻量级 GIS Web 应用,尤其适合内部工具、分析面板、数据预览、模型演示和原型验证。但如果需要复杂地图编辑、海量图层、高并发访问、权限系统和长期运维,建议使用传统 WebGIS 架构。
Q2:Streamlit加载GeoJSON地图很慢怎么办?
先检查 GeoJSON 文件大小和要素数量。可以尝试几种方法:简化几何、减少字段、按范围筛选、只加载前若干条记录、改用 PostGIS 查询,或者将大数据发布为矢量切片服务。GeoJSON 适合中小规模数据交换,不适合无限制承载海量空间数据。
Q3:Streamlit能直接读取Shapefile吗?
可以,但在 Web 上传场景中不建议只上传单个 .shp 文件。应上传包含 .shp、.shx、.dbf、.prj 的 ZIP 压缩包,然后在后台解压并用 GeoPandas 读取。
Q4:为什么Streamlit缓冲区分析结果不准确?
通常是因为在经纬度坐标系下直接做了缓冲区。经纬度单位是度,缓冲半径如果按米理解就会出错。正确做法是先转换到合适的投影坐标系,完成缓冲区分析后,再转换回 EPSG:4326 用于地图显示。
Q5:Streamlit适合连接PostGIS吗?
适合。Streamlit 可以作为前端交互界面,PostGIS 负责空间数据存储和空间查询。对于数据量较大的 GIS Web 应用,用 PostGIS 在数据库端完成筛选、相交、范围查询,再把结果返回给 Streamlit,会比一次性加载全部文件更稳妥。
Q6:Streamlit GIS应用可以部署到哪里?
常见选择包括 Streamlit Community Cloud、企业内网服务器、Docker 容器、云服务器等。如果项目依赖 GeoPandas、Rasterio、GDAL 等 GIS 库,部署前要特别确认运行环境是否能正确安装这些依赖。
Q7:Folium和PyDeck在Streamlit里怎么选?
Folium 更适合传统二维 Web 地图展示,使用 Leaflet 生态,加载 GeoJSON、点线面图层比较直观。PyDeck 更适合大规模点数据、三维柱状图、弧线、热力可视化等效果。入门阶段建议先用 Folium,数据可视化需求增强后再考虑 PyDeck。
结论:先做轻量原型,再决定是否升级为完整WebGIS
用 Streamlit 开发 GIS Web 应用的最大价值,不是替代所有 WebGIS 技术,而是让 GIS 分析人员可以快速把 Python 空间处理能力变成可交互页面。
对于 GeoJSON 在线查看、点位缓冲区分析、PostGIS 空间查询这类任务,Streamlit 的开发效率很高,也非常适合教学、项目汇报和内部工具建设。
实际使用时,请重点把握三个原则:第一,地图展示前检查坐标系;第二,距离和面积分析使用合适的投影坐标系;第三,大数据量不要直接塞进浏览器,而应交给 PostGIS、地图服务或切片方案处理。
如果你刚开始做 GIS Web 应用,建议先按本文的 3 个项目源码原型跑通一遍,再根据自己的数据格式、分析流程和部署环境逐步扩展。这样比一开始就搭建复杂前后端系统更稳,也更容易把 GIS 业务逻辑做正确。