Streamlit Folium Update -- more dynamic maps

Streamlit-folium 0.8.0 was just released, and it contains new functionality for dynamic maps.
ezgif-1-0189d7f263

If you’re only making static maps (not adding/removing/updating things within your app), then you may not make use of this. But, if you’re changing your map (e.g. changing the markers that are on the map, moving to a new location, etc.) via other streamlit widgets, you can now do that without re-drawing the map every time.

The new arguments to st_folium are zoom (takes an int), center (takes a pair of floats for lat/lng), and feature_group_to_add (which accepts any folium FeatureGroup). If these arguments are passed to st_folium, and you change them from within your streamlit app, the map will not be redrawn from scratch, but will be updated in-place.

You can try this out here: https://folium.streamlit.app/dynamic_map_vs_rerender

Hope you all enjoy this new functionality!

8 Likes

Thanks for the update! This will be very useful.

This update is great!

I have a scenario where I am creating a map and using folium.Draw to filter a dataset and add makers to my map.

I want the user to be able to Clear or Reset the map back to it’s original state, but I am having some trouble doing this while maintaining the dynamic part that you made possible.

My attempt at the solution: Clear my entire session state and re-initialize the map. This successfully removes the pins that were added after drawing the circle, but the actual circle drawn sticks around.

Reproducible Example:

import random

import folium
import numpy as np
import streamlit as st
from folium.plugins import Draw, MeasureControl
from streamlit_folium import st_folium

st.set_page_config(
    page_title="st_folium Example",
    page_icon="🔎",
    layout="wide"
)
# Set up initial map state
CENTER_START = [37.8, -96]
ZOOM_START = 5


def initialize_session_state():
    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 "markers" not in st.session_state:
        st.session_state["markers"] = []
    if "map_data" not in st.session_state:
        st.session_state["map_data"] = {}
    if "all_drawings" not in st.session_state["map_data"]:
        st.session_state["map_data"]["all_drawings"] = None
    if "upload_file_button" not in st.session_state:
        st.session_state["upload_file_button"] = False


def reset_session_state():
    # Delete all the items in Session state besides center and zoom
    for key in st.session_state.keys():
        if key in ["center", "zoom"]:
            continue
        del st.session_state[key]
    initialize_session_state()


def initialize_map(center, zoom):
    m = folium.Map(location=center, zoom_start=zoom, scrollWheelZoom=False)
    draw = Draw(export=False,
                filename='custom_drawn_polygons.geojson',
                position='topright',
                draw_options={'polyline': False,  # disable polyline option
                              'rectangle': False,  # disable rectangle option for now
                              # enable polygon option
                              #   'polygon': {'showArea': True, 'showLength': False, 'metric': False, 'feet': False},
                              'polygon': False,  # disable rectangle option for now
                              # enable circle option
                              'circle': {'showArea': True, 'showLength': False, 'metric': False, 'feet': False},
                              'circlemarker': False,  # disable circle marker option
                              'marker': False,  # disable marker option
                              },
                edit_options={'poly': {'allowIntersection': False}})
    draw.add_to(m)
    MeasureControl(position='bottomleft', primary_length_unit='miles',
                   secondary_length_unit='meters', primary_area_unit='sqmiles', secondary_area_unit=np.nan).add_to(m)
    return m


initialize_session_state()

m = initialize_map(
    center=st.session_state["center"], zoom=st.session_state["zoom"])

# Buttons
col1, col2, col3 = st.columns(3)

if col1.button("Add Pins"):
    st.session_state["markers"] = [folium.Marker(location=[random.randint(37, 38), random.randint(
        -97, -96)], popup="Test", icon=folium.Icon(icon='user', prefix='fa', color="lightgreen")) for i in range(0, 10)]

if col2.button("Clear Map", help="ℹ️ Click me to **clear the map and reset**"):
    reset_session_state()
    m = initialize_map(
        center=st.session_state["center"], zoom=st.session_state["zoom"])

with col3:
    st.markdown(
        "##### Draw a circle by clicking the circle icon ----👇")

fg = folium.FeatureGroup(name="Markers")
for marker in st.session_state["markers"]:
    fg.add_child(marker)

# Create the map and store interaction data inside of session state
map_data = st_folium(
    m,
    center=st.session_state["center"],
    zoom=st.session_state["zoom"],
    feature_group_to_add=fg,
    key="new",
    width=1285,
    height=725,
    returned_objects=["all_drawings"],
    use_container_width=True
)
st.write("## map_data")
st.write(map_data)
st.write("## session_state")
st.write(st.session_state)

Screen Recording of the Issue:

P.S. Thanks for the work you and Randy have put into this package, I really enjoy using it!

That is an interesting problem – I haven’t noticed that behavior before. I’ll need to think about how to handle that in an ideal way. In the meantime, one silly (but probably effective) way to clear the entire map is to change the key that you pass – you could generate a random key (or just something like key = str(datetime.now()), put that in session state, and then change it to a new value when you click the clear button.

Like I said, not ideal, but it should do the trick.

1 Like

Thanks for the workaround, I’ll use this for now.

Appreciate the speedy response, cheers!

1 Like