Streamlit-folium 0.8.0 was just released, and it contains new functionality for dynamic maps.
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.
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.
I’m trying to live update the map using asyncio, which works great for tables and charts (I’m using Streamlit 1.35.0)
I have this very basic, standalone example:
import asyncio
import random
import folium
import streamlit as st
from streamlit_folium import st_folium
def random_marker():
random_lat = random.random() * 0.5 + 37.79
random_lon = random.random() * 0.5 - 122.4
return folium.Marker(
location=[random_lat, random_lon],
popup=f"Random marker at {random_lat:.2f}, {random_lon:.2f}",
)
def add_random_marker():
marker = random_marker()
print(f'Adding marker {marker}')
st.session_state["markers"].append(marker)
def clear_markers():
st.session_state["markers"].clear()
async def add_markers_to_map_async():
while True:
marker = random_marker()
print(f'Adding marker {marker}')
st.session_state["markers"].append(marker)
await asyncio.sleep(1)
async def main():
if "markers" not in st.session_state:
st.session_state["markers"] = []
if st.button("Add random marker"):
add_random_marker()
if st.button("Clear markers"):
clear_markers()
if st.button("Start adding markers asynchronously"):
await add_markers_to_map_async()
m = folium.Map(location=[37.95, -122.200], zoom_start=10)
fg = folium.FeatureGroup(name='Markers')
for marker in st.session_state["markers"]:
fg.add_child(marker)
st_folium(m, feature_group_to_add=fg, width=725, key='user-map', returned_objects=[])
if __name__ == "__main__":
asyncio.run(main())
Unfortunately, once I click on the “Start adding markers asynchronously” button, the map is kind of frozen and the newly added markers won’t show up until I click on the “Add random marker”.
Is there a way to perform what the “Add random marker” does but from within the “add_markers_to_map_async” function?
You are waiting for a while True loop to end, thus the code execution blocks at the await line. Also, the async function only appends stuff to st.session_state["markers"], but the map is rendered just once.
How about using st.fragment to redraw the map every certain period of time:
Code:
from random import random
import folium
import streamlit as st
from streamlit_folium import st_folium
def create_marker():
random_lat = random() * 0.5 + 37.79
random_lon = random() * 0.5 - 122.4
return folium.Marker(
location=[random_lat, random_lon],
popup=f"Random marker at {random_lat:.2f}, {random_lon:.2f}",
)
@st.experimental_fragment(run_every=0.5)
def draw_map(container):
m = folium.Map(location=[37.95, -122.200], zoom_start=9)
fg = folium.FeatureGroup(name="Markers")
for marker in st.session_state["markers"]:
fg.add_child(marker)
with container:
st_folium(
m,
feature_group_to_add=fg,
key="user-map",
returned_objects=[],
use_container_width=True,
height=300,
)
def add_random_marker():
st.session_state["markers"].append(create_marker())
def main():
st.set_page_config(layout="wide")
if "markers" not in st.session_state:
st.session_state["markers"] = []
st.header("Refreshing map", divider="rainbow")
left_column, right_column = st.columns([1, 2])
with left_column:
if st.button("Add random marker"):
add_random_marker()
if st.button("Clear markers"):
st.session_state["markers"].clear()
if st.toggle("Start adding markers automatically"):
st.experimental_fragment(add_random_marker, run_every=1.0)()
with right_column:
placeholder = st.empty()
draw_map(placeholder)
if __name__ == "__main__":
main()
Thanks @edsaac for the example. I should have mention what I want to achieve from the get go: a streamlit app that streams back paginated results from a REST endpoint and live update a map to display users location.
That’s why I have those async definitions in my example (I replaced the http calls with a simple in-process random marker generation).
I’ve read many posts in here on how to achieve this, but haven’t seen a compelling example. Is that possible at all?
I have three live updating components: a map, a table and a chart. Each of these components receives events streamed from an ad-hoc Flink SQL query running in Confluent Cloud (side note: with Confluent Cloud you can run Kafka and Flink in the cloud in a few click)
The only small downside is that the initial querying blocks the streamlit UI. Is there a way to put that in the background instead?
In terms of doing it in the background, I’m not sure how well this would work for your use-case, but you could consider hydrating the data in the session state with empty data on the initial run (i.e. putting a special case for the first time fetch_result_page_user_locations runs, checking to see if it’s empty, and just returning [] or something like that, so that the first real query happens on the first rerun), and see how that works. That’s just an idea off the top of my head, not sure how well it would work in practice.
Not sure if there is a more updated thread on the topic but I recently was working to create a map that uses the level of zoom passed back from st_folium to maintain the zoom and centroid state while adding and subtracting elements from the feature group with widgets. I’ve encountered two types of unintended performance. I’ve attached sample code for both cases (built on the original example provided in this post for reference):
The app starts to oscillate between two zoom levels and gets stuck in a loop. This happens when starting with a hardcoded initial set of conditions then feeding in st_folium updates from the last action.
import random
import folium
import streamlit as st
from streamlit_folium import st_folium
st.set_page_config(layout="wide")
CENTER_START = [39.949610, -75.150282]
ZOOM_START = 8
if "center" not in st.session_state:
st.session_state["center"] = [39.949610, -75.150282]
if "zoom" not in st.session_state:
st.session_state["zoom"] = 8
col1, col2 = st.columns(2)
st.write(f"Session State Center: {st.session_state['center']}")
st.write(f"Session State Zoom: {st.session_state['zoom']}")
with col1:
"# New method"
"### Pass `center`, `zoom`, and `feature_group_to_add` to `st_folium`"
with st.echo(code_location="below"):
m = folium.Map(location=CENTER_START, zoom_start=ZOOM_START)
output = st_folium(
m,
center=st.session_state["center"],
zoom=st.session_state["zoom"],
key="new",
height=400,
width=700,
)
st.session_state['zoom'] = output['zoom']
The zoom lags in updating so the app zoom level doesn’t update dynamically. It sort of lags and jumps to one level before the current zoom.
import random
import folium
import streamlit as st
from streamlit_folium import st_folium
st.set_page_config(layout="wide")
CENTER_START = [39.949610, -75.150282]
ZOOM_START = 8
if "center" not in st.session_state:
st.session_state["center"] = [39.949610, -75.150282]
if "zoom" not in st.session_state:
st.session_state["zoom"] = 8
col1, col2 = st.columns(2)
st.write(f"Session State Center: {st.session_state['center']}")
st.write(f"Session State Zoom: {st.session_state['zoom']}")
with col1:
"# New method"
"### Pass `center`, `zoom`, and `feature_group_to_add` to `st_folium`"
with st.echo(code_location="below"):
m = folium.Map(location=st.session_state["center"], zoom_start=st.session_state["zoom"])
output = st_folium(
m,
center=st.session_state["center"],
zoom=st.session_state["zoom"],
key="new",
height=400,
width=700,
)
st.session_state['zoom'] = output['zoom']
I’d imagine both have to do with a delay in the session state updating and then the stored state being out of sync with the state of the map, I’m not sure how to do some equivalent of an on-call function to make sure it get’s updated automatically though for this type of situation.
I saw in the PR a mention of a new demo page, I don’t see it in the repo or streamlit-folium demo app, could you point me towards that? I am on streamlit-folium 0.24.0 (the latest release from the time of your merge) but am getting issues with the on_change option.
TypeError: st_folium() got an unexpected keyword argument ‘on_change’
I would double-check that you’ve actually updated streamlit-folium (and streamlit to at least 1.35, which is now the minimum required version), because once that’s done, that example should work fine.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.