You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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()会返回包含zoomcenter等参数的字典,也清楚需要用Streamlit的会话状态(session state)来保存这些值,但试了各种方法都没成功,遇到的问题包括:

  • 刷新后地图总是回到默认的CENTER_STARTZOOM_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

火山引擎 最新活动