Streamlit-Folium实现刷新按钮并保留地图中心与缩放级别问题求助
Streamlit-Folium实现刷新按钮并保留地图中心与缩放级别问题求助
我目前正在开发一个本地运行的Streamlit应用,计划很快部署上线!我用到的技术栈包括:
- Python 3.11
- Streamlit 1.29.0
- Streamlit-Folium 0.17.4
- IDE:PyCharm 2023.3.1
项目背景
这个应用是调用英国交通部(DfT)的公开公交数据API,获取、处理并展示伦敦的实时公交位置。数据获取、处理和基础展示都很顺利:
- 因为伦敦有大约8300辆公交,直接逐个标记会很耗资源,所以我用
st_folium()的标记集群功能——缩放时自动显示单个标记,这一点没问题。 - 我之前常用
folium_static(),现在切换到st_folium()是为了给地图添加更多交互性。
需求与问题
我想实现一个刷新按钮:点击后重新拉取公交数据,同时让地图保持最后一次的中心位置和缩放级别,这样就能模拟公交实时移动的效果(DfT那边每10-30秒更新一次数据)。
我知道st_folium()会返回包含zoom、center等参数的字典,也清楚需要用Streamlit的会话状态(session state)来保存这些值,但试了各种方法都没成功,遇到的问题包括:
- 刷新后地图总是回到默认的
CENTER_START和ZOOM_START位置 - 应用陷入无限刷新循环
当前可用代码(无刷新功能,可正常运行)
import folium from folium.plugins import MarkerCluster import streamlit as st from bod_api import fetch_and_save_bus_data from xml_to_parquet import xml_to_parquet from geo_prep import load_data, create_gdf from streamlit_folium import st_folium # Set page config and title st.set_page_config(layout="wide") st.markdown("# :bus: Live London Bus Map") # Set up initial map location CENTER_START = [51.5074, -0.1278] ZOOM_START = 12 def load_and_prepare_data(): # Call DfT API for bus data and convert xml into a parquet file # Create a geo dataframe from the parquet (CRS is 4328) api_key = st.secrets["tfl_bus"]["api_key"] api_url = st.secrets["tfl_bus"]["api_url"] bounding_box = st.secrets["tfl_bus"]["bounding_box"] fetch_and_save_bus_data(api_url, bounding_box, api_key, "bus_location_data.xml") xml_to_parquet("bus_location_data.xml", "bus_location_data_combined.parquet") data = load_data("bus_location_data_combined.parquet") geo_df = create_gdf(data) return geo_df def initialize_session_state(): # Set session state variables if "center" not in st.session_state: st.session_state["center"] = CENTER_START if "zoom" not in st.session_state: st.session_state["zoom"] = ZOOM_START if "map_data" not in st.session_state: st.session_state["map_data"] = {} def add_markers_to_cluster(geo_df, marker_cluster): # Iterate over rows to create markers for index, row in geo_df.iterrows(): # Set the colour based on OperatorRef icon_color = 'red' if row['OperatorRef'] == 'TFLO' else 'blue' # Create popup content popup_content = f""" <b>Bus Route:</b> {row['PublishedLineName']}<br> <b>Direction:</b> {row['DirectionRef']}<br> <b>Operator:</b> {row['OperatorRef']} """ # Add marker to marker cluster folium.Marker( location=[row.geometry.y, row.geometry.x], icon=folium.Icon(color=icon_color, icon="bus", prefix="fa"), popup=popup_content, tooltip=row['PublishedLineName'] ).add_to(marker_cluster) def create_map(center, zoom): # Create the map with the marker cluster m = folium.Map(location=center, zoom_start=zoom) marker_cluster = MarkerCluster().add_to(m) return m, marker_cluster # Load session state initialize_session_state() # Load and prepare data geo_df = load_and_prepare_data() # Display the map m, marker_cluster = create_map(st.session_state["center"], st.session_state["zoom"]) add_markers_to_cluster(geo_df, marker_cluster) map_data = st_folium(m, center=st.session_state["center"], zoom=st.session_state["zoom"], width=1285, height=800, key='new', returned_objects=[])
我尝试过的方法(导致无限刷新)
我试过把逻辑放进main()函数,结果应用无限刷新:
def main(): initialize_session_state() # Add a refresh button if st.button("Refresh"): geo_df = load_and_prepare_data() else: if "geo_df" not in st.session_state: st.session_state["geo_df"] = load_and_prepare_data() geo_df = st.session_state["geo_df"] m, marker_cluster = create_map(st.session_state["center"], st.session_state["zoom"]) add_markers_to_cluster(geo_df, marker_cluster) # Handle map interactions map_data = st_folium(m, center=st.session_state["center"], zoom=st.session_state["zoom"], width=1285, height=800, key='map') if map_data: if map_data.get("last_center"): st.session_state["center"] = map_data["last_center"] if map_data.get("last_zoom"): st.session_state["zoom"] = map_data["last_zoom"] if __name__ == "__main__": main()
有没有人做过类似的功能?或者能帮我排查一下问题出在哪吗?非常感谢!
备注:内容来源于stack exchange,提问作者Cal Mingo




