Search code examples
pythonfoliumstreamlit

How do I explore a map without deleting everything in Streamlit-Folium?


I'm using streamlit-folium to visualize a map in Streamlit and let the user select a custom number of points. The map has a default starting point but in my wish the user can explore the map with the help of a search bar.

import folium
import streamlit as st
from folium.plugins import Draw
from geopy.geocoders import Nominatim
from streamlit_folium import st_folium

# Default location
x, y = [41.00, 29.00]

# Search for another location
location_input = st.text_input("Search in the map")
if location_input:
    location = Nominatim(user_agent="GetLoc")
    getLocation = location.geocode(location_input)
    x, y = getLocation.latitude, getLocation.longitude

# Draw the map centered in location
m = folium.Map(location=[x,y],zoom_start=12)
Draw(
    draw_options={
        'polyline': False, 'rectangle': False, 
        'circle': False, 'polygon': False,
        'circlemarker': False
    },
    edit_options={'remove': False}
).add_to(m)
Map = st_folium(m, width = 700, height=500)

My problem is that when I search for a new location, all the markers I have put on the map disappear.

Default

default with a marker

After searching

after search

Hope someone can help. I will keep updating this post if I reach something on my own. Thank you very much.


Solution

  • I solved my problem. Here's the code:

    import folium
    import streamlit as st
    from folium.plugins import Draw
    from streamlit_folium import st_folium
    from typing import Union
    
    import geocoder
    import requests
    import urllib.parse
    
    def latlon(address: str) -> tuple[float, float]:
        url = f"https://nominatim.openstreetmap.org/search/{urllib.parse.quote(address)}?format=json"
        response = requests.get(url).json()
        return response[0]["lat"], response[0]["lon"]
    
    def geolocalize() -> Union[tuple[float, float], None]:
        try:
            latitude, longitude = geocoder.ip('me').latlng
            return latitude, longitude
        except:
            return None
    
    def geo_map(
        name: str,
        search_hint: dict[str, str],
        extra_hint: dict[str, str],
        submit: dict[str, str],
        lang: str
    ) -> dict[list[float],str]:
        c1, c2 = st.columns(2)
    
        with c1:
    
            if "geolocalized" not in st.session_state:
                st.session_state.geolocalized = geolocalize()
                
            if f"location_input_{name}" not in st.session_state:
                st.session_state[f"location_input_{name}"] = ""
            
            location_input = st.text_input(
                label="", placeholder=search_hint[lang], key=f"text_input_{name}"
            )
    
            try:
                if location_input==st.session_state[f"location_input_{name}"]:
                    raise Exception
                x, y = latlon(location_input)
                st.session_state[f"location_input_{name}"]=location_input
                st.session_state[f"location_coord_{name}"]=[x,y]
            except:
                last_clicked = None
                if f"lastclicked_{name}" in st.session_state:
                    last_clicked = st.session_state[f"lastclicked_{name}"]
                last_marker = None
                if f"lastmarker_{name}" in st.session_state:
                    last_marker = st.session_state[f"lastmarker_{name}"]
    
                if f"location_coord_{name}" in st.session_state and st.session_state[f"location_coord_{name}"]:
                    x, y = st.session_state[f"location_coord_{name}"]
                elif last_marker or last_clicked:
                    x, y =  last_marker[::-1] or [last_clicked["lat"], last_clicked["lng"]]
                else:
                    x, y =  [45.971750,13.639878]\
                            or st.session_state.geolocalized \
                            or [45.652020, 13.783930] # HQ
    
            ## Map
            m = folium.Map(location=[x,y], zoom_start=15)
            draw_options={foo: False for foo in ['polyline', 'rectangle', 'circle', 'polygon', 'circlemarker']}
            ##draw_options['marker'] = {'icon'=}
            Draw(
                draw_options=draw_options,
                edit_options={'edit':False, 'remove': False}
            ).add_to(m)
    
            if f"points_{name}" in st.session_state:
                for point in st.session_state[f"points_{name}"]:
                    x, y = [float(n) for n in point[1:-1].split(',')]
                    folium.Marker(
                        #?
                        location=[y,x], 
                        #?
                        tooltip = st.session_state[f"points_{name}"][point] or None
                    ).add_to(m)
    
            Map = st_folium(m, width = 700, height=500, key=f"map_{name}")
    
            if f"points_{name}" not in st.session_state:
                st.session_state[f"points_{name}"] = {}
            if f"lastclicked_{name}" not in st.session_state:
                st.session_state[f"lastclicked_{name}"] = None
            if f"lastmarker_{name}" not in st.session_state:
                st.session_state[f"lastmarker_{name}"] = None
    
        with c2:
    
            points = Map.get("all_drawings")
            if points:
                form = st.form(f"labels_{name}")
                labels = []
                for i, p in enumerate(points):
                    labels.append(form.text_input(
                            f"{extra_hint[lang]} {i+1}",
                            ""
                        )
                    )
                sub = form.form_submit_button(submit[lang])
                if sub:
                    st.session_state[f"lastclicked_{name}"] = Map["last_clicked"]
                    st.session_state[f"lastmarker_{name}"] = \
                        Map["last_active_drawing"]["geometry"]["coordinates"]
                    
                    points = Map.get("all_drawings")
                    for point, label in zip(points, labels):
                        coordinates_p = point["geometry"]["coordinates"]
                        st.session_state[f"points_{name}"][str(coordinates_p)] = label
    
        return st.session_state[f"points_{name}"]
    

    Usage example:

    
    map_hint = {
        'eng': 'Search in the map',
        'ita': 'Cerca nella mappa',
        'slo': 'Iskanje na zemljevidu'
    }
    extra_hint1 = {
        'eng': 'Name or reason for favorite place',
        'ita': 'Nome o motivo del posto preferito',
        'slo': 'Ime ali razlog za najljubši kraj'
    }
    submit = {
        'eng': 'Submit (click twice)',
        'ita': 'Invia (clicca due volte)',
        'slo': 'Pošlji (dvakrat kliknite)'
    }
    map1 = geo_map(
        'map1',
        search_hint=map_hint,
        extra_hint=extra_hint1,
        submit=submit,
        lang='eng'
    )