使用Streamlit+Folium时地图移动后模糊的问题排查
问题:Streamlit + Folium地图移动后模糊,缓存尝试无效
我用Streamlit和Folium做交互式地图,每次移动地图后都会变得模糊。推测是重复处理导致的性能问题,试过用缓存只执行一次处理,但没改善,想确认是不是操作错了。
相关代码
import geopandas as gpd import folium import streamlit as st from streamlit_folium import st_folium import pandas as pd import functools from branca.colormap import LinearColormap APP_TITLE = 'Agences du réseau' done = False def read_files(): #df = pd.read_excel("data/data.xlsx") # For testing purpose use this generated dataframe df = pd.DataFrame({"Agence":["Ardèche", "Isère", "Savoie"], "Occupation":[3, 2, 1], "Commentaire": ["RAS", "RAS", "RAS"]}) geo_data = gpd.read_file("data/departements.geojson") # Create a Folium map centered at the bounds m = folium.Map(location=[46.5, 2.5], zoom_start=6, tiles='CartoDB positron') # Create linear color scale from red to green color_scale = LinearColormap(['#FF0000', '#00FF00'], vmin=df['Occupation'].min(), vmax=df['Occupation'].max()) def style_function(feature): value = feature['properties']['Occupation'] return { 'fillColor': color_scale(value), 'color': 'white', 'weight': 1, 'fillOpacity': 0.8 } choropleth = folium.Choropleth( geo_data=geo_data, data=df, columns=["Agence", 'Occupation'], key_on='feature.properties.nom', line_opacity=0.8, highlight=False, fill_opacity=0.5, style_function=style_function ) choropleth.geojson.add_to(m) # Extract the boundaries of the GeoJSON data boundary_data = geo_data.boundary # Calculate the bounds of the boundary geometries bounds = boundary_data.total_bounds.tolist() # Add the boundaries to the map folium.GeoJson(boundary_data).add_to(m) return df, geo_data, m, choropleth def display_map(geofile, df, geo_data, m, choropleth): # TEST df_indexed = df.set_index('Agence') global done if done == False: for feature in choropleth.geojson.data['features']: try: state_name = feature['properties']['nom'] print(state_name) feature['properties']['Occupation'] = 'Population: ' + '{:,}'.format(df_indexed.loc[state_name, 'Occupation'][0]) if state_name in list(df_indexed.index) else '' feature['properties']['Commentaire'] = 'Reports/100K Population: ' + str(round(df_indexed.loc[state_name, 'Commentaire'][0])) if state_name in list(df_indexed.index) else '' except: pass done = True choropleth.geojson.add_child( folium.features.GeoJsonTooltip(['nom', 'Occupation', 'Commentaire'], labels=False) ) # Display the map in Streamlit st_map = st_folium(m, width=1200, height=800) print(st_map) state_name = '' if st_map['last_active_drawing']: state_name = st_map['last_active_drawing']['properties']['nom'] return state_name def main(): df, geo_data, m, choropleth = read_files() st.set_page_config(APP_TITLE) st.title(APP_TITLE) display_map("data/departements.geojson", df, geo_data, m, choropleth) if __name__ == "__main__": main()
数据来源
使用的GeoJSON为法国省份数据,文件名为departements.geojson。
地图状态对比
- 未模糊状态:

- 模糊状态:

问题分析与修复方案
核心问题
- 全局变量
done无效:Streamlit每次交互都会重新运行整个脚本,全局变量done会被重置为False,导致每次地图交互都重复修改GeoJSON属性、添加Tooltip,引发渲染异常。 - 图层重复叠加:
read_files每次运行都会添加边界GeoJSON,配合Streamlit的重运行机制,多层叠加导致地图模糊。 - Choropleth与Tooltip重复绑定:每次调用
display_map都会给choropleth.geojson添加Tooltip,叠加元素进一步加重渲染混乱。
修复步骤
- 用Streamlit缓存替代全局变量:用
@st.cache_data缓存地图初始化和数据读取操作,确保只执行一次。 - 提前整合数据与图层:在地图初始化阶段就完成GeoJSON属性修改、Tooltip绑定,避免后续重复操作。
- 合并图层逻辑:把边界样式整合到Choropleth的样式函数中,减少独立图层数量。
修复后的完整代码
import geopandas as gpd import folium import streamlit as st from streamlit_folium import st_folium import pandas as pd from branca.colormap import LinearColormap APP_TITLE = 'Agences du réseau' @st.cache_data def read_files(): # 测试用数据 df = pd.DataFrame({ "Agence":["Ardèche", "Isère", "Savoie"], "Occupation":[3, 2, 1], "Commentaire": ["RAS", "RAS", "RAS"] }) geo_data = gpd.read_file("data/departements.geojson") # 初始化地图 m = folium.Map(location=[46.5, 2.5], zoom_start=6, tiles='CartoDB positron') # 创建颜色比例尺 color_scale = LinearColormap( ['#FF0000', '#00FF00'], vmin=df['Occupation'].min(), vmax=df['Occupation'].max() ) # 提前合并业务数据到GeoJSON属性 df_indexed = df.set_index('Agence') for feature in geo_data.to_dict('records'): nom = feature['nom'] if nom in df_indexed.index: feature['Occupation'] = f"Population: {df_indexed.loc[nom, 'Occupation']:,}" feature['Commentaire'] = f"Reports/100K Population: {df_indexed.loc[nom, 'Commentaire']}" else: feature['Occupation'] = "" feature['Commentaire'] = "" # 定义统一的样式函数(包含边界样式) def style_function(feature): value = float(feature['Occupation'].split(': ')[1]) if feature['Occupation'] else 0 return { 'fillColor': color_scale(value), 'color': 'white', 'weight': 1, 'fillOpacity': 0.8, 'lineOpacity': 0.8 } # 单一GeoJSON图层整合所有逻辑 folium.GeoJson( geo_data, style_function=style_function, tooltip=folium.features.GeoJsonTooltip( ['nom', 'Occupation', 'Commentaire'], labels=False ) ).add_to(m) return df, geo_data, m def display_map(m): # 仅负责渲染地图,不修改图层结构 st_map = st_folium(m, width=1200, height=800) state_name = '' if st_map.get('last_active_drawing'): state_name = st_map['last_active_drawing']['properties']['nom'] return state_name def main(): df, geo_data, m = read_files() st.set_page_config(page_title=APP_TITLE) st.title(APP_TITLE) display_map(m) if __name__ == "__main__": main()
关键优化点
- 缓存初始化流程:
@st.cache_data确保地图和数据只在首次加载或内容变更时生成,避免每次交互重复执行。 - 单一图层设计:把Choropleth着色、Tooltip、边界样式整合到一个GeoJson图层,消除图层叠加问题。
- 提前数据绑定:在初始化阶段完成业务数据与GeoJSON的关联,避免后续重复修改属性引发的渲染异常。
内容的提问来源于stack exchange,提问作者Achille G
相关产品推荐
相关产品推荐

