While they are not natively supported in streamlit, the st-folium component is a powerful custom component that is being actively supported and developed.
1import geopandasimport pandas as pdimport streamlit as st2import folium3from streamlit_folium import st_folium4gp_list_gdf_sw = geopandas.read_file("https://files.catbox.moe/atzk26.gpkg" )# Filter out instances with no geometry5gp_list_gdf_sw = gp_list_gdf_sw[~gp_list_gdf_sw['geometry'].is_empty] ,# Create a geometry list from the GeoDataFrame6geo_df_list = [[point.xy[1][0], point.xy[0][0]] for point in gp_list_gdf_sw.geometry]7gp_map_tooltip = folium.Map( location=[50.7, -4.2], zoom_start=8, tiles='openstreetmap', )8for i, coordinates inenumerate(geo_df_list):9 gp_map_tooltip = gp_map_tooltip.add_child( folium.Marker( location=coordinates, tooltip=gp_list_gdf_sw['name'].values[i],10 icon=folium.Icon(icon="user-md", prefix='fa', color="black") ) )11st_folium(gp_map_tooltip)
1
To work with geographic data, we need to import the geopandas library.
2
We’ll also need the folium library to help set up our interactive map.
3
Finally we need to use the streamlit_folium library, which we have to install separately (but is included in the hsma_webdev environment if you are following the HSMA course). From that library, we import just the function st_folium().
4
We load in a geopackage file. We don’t need to specify a coordinate reference system for this kind of file; it’s recorded within the file itself and geopandas will automatically read and apply this, though as Folium expects the coordinates to be in latitude and longitude (not Northings and Eastings), you may need to convert the CRS of your own data. See the HSMA geographic book for more details.
5
Folium does not cope well with missing data, so we filter out any rows where our ‘geometry’ column is empty.
6
To set up our map of points, we will need to create a list of coordinate pairs, though Folium expects them in the order longitude, latitude, so we swap the order of the points from our geometry column when placing them in the list.
7
We then create a folium map, specifying the starting zoom level and the coordinates around which it should initially be centred.
8
We then iterate through the list of points we created.
9
In each round of our loop we add a Folium ‘marker’ to our original map.
10
In Folium, if we don’t specify an icon to use for the marker, it will choose a default. However, this doesn’t seem to reliably work in all instances of Streamlit, so you may need to specify a custom icon instead using the folium.Icon class to select an icon from a web service such as font awesome. More about this can be found in the HSMA geographic book.
11
Finally, we pass our map to the st_folium() function.
11.1.1 Sneak Peak - Updating the map based on inputs
Let’s use a simple text input to filter the dataframe we are passing to the map.
What happens to the map when we do this?
import geopandasimport pandas as pdimport streamlit as stimport foliumfrom streamlit_folium import st_folium1search_string = st.text_input("Enter a string to search the practice name field by")gp_list_gdf_sw = geopandas.read_file("https://files.catbox.moe/atzk26.gpkg")# Filter out instances with no geometrygp_list_gdf_sw = gp_list_gdf_sw[~gp_list_gdf_sw['geometry'].is_empty]# Filter to just the practice of interest (if given)2if search_string isnot"":3 gp_list_gdf_sw = gp_list_gdf_sw[gp_list_gdf_sw['name'].str.contains(search_string.upper())]4st.dataframe(gp_list_gdf_sw[['name', 'address_1', 'postcode', 'Total List Size']])# Create a geometry list from the GeoDataFrame5geo_df_list = [[point.xy[1][0], point.xy[0][0]] for point in gp_list_gdf_sw.geometry]gp_map_tooltip = folium.Map( location=[50.7, -4.2], zoom_start=8, tiles='openstreetmap', )for i, coordinates inenumerate(geo_df_list): gp_map_tooltip = gp_map_tooltip.add_child( folium.Marker( location=coordinates, tooltip=gp_list_gdf_sw['name'].values[i], icon=folium.Icon(icon="user-md", prefix='fa', color="black") ) )st_folium(gp_map_tooltip)
1
We create a streamlit user input that is designed to take a text string from the user. Whatever the user enters is saved to the variable search_string.
2
We check whether this search string is equal to an empty string, which is "" or '' (but we can use either of those to check against - they are regarded as identical). If the value of search_string is "", we don’t undertake the indented code and jump to the next step instead - i.e. we won’t do any filtering.
3
If the search_string is anything other than a blank string, we filter the name column of the dataframe (which here is the GP practice name) to only include instances where the search_string appears somewhere in the name - e.g. if our search string is “Hill” it would match “Hill Practice”, “Big Hill Surgery”, “Chilly Bend Surgery” and so on. Other methods exist if we only want to match the exact string.
4
Here, we add in a display of the filtered dataframe, restricting it to only the columns specified in the list.
5
All of our Folium code is unchanged; we just pass the filtered (or unfiltered, if no search string is entered) dataframe instead.
11.1.2 Updating the app based on the map zoom
You can do things like filter a dataframe down to only the subset of points that are on the screen within the Folium component.