Creating Maps in Streamlit Apps using Folium
Tags: python, streamlit, folium
Published: 2024-02-12 06:48:08

###Introduction
In this blog post, I will show you how to use Folium to create maps in a Streamlit application. In the sample app, I will use the following two data provided by the CITY OF VANCOUVER OPEN DATA PORTAL.

- Electric vehicle charging stations (CSV format)
- Local area boundary (GeoJSON format)

###What I made  
- MarkerCluster Example
![Screenshot1-EV Charging Stations in the Vancouver](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1ujbf2as8fiyb3jf4kx.png)

- GeoJson Example
![Screenshot2-Local Area Boundary in the Vancouver](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bza9nn2z71q9cjxxtyn1.png)

###Code & Explanation
{% embed https://gist.github.com/yoshan0921/bc2c65560311c25a5c5d4dbf715752d1 %}

###Preparation

####Importing libraries
```python
import csv
import pandas as pd
import streamlit as st
import folium
from folium import plugins
from streamlit_option_menu import option_menu
```
I think everyone can understand csv, pandas, streamlit and forlium. So, I don't need to explain about them. 

I used [streamlit_option_menu](https://github.com/victoryhb/streamlit-option-menu) to create a menu in the sidebar. I know that there is a component called [streamlit-forlium](https://folium.streamlit.app/) but I didn't use it this time.

####Page Setting
```python
st.set_page_config(
    page_title=None,
    page_icon=None,
    layout="wide",
    initial_sidebar_state="auto",
    menu_items=None,
)
```
The layout was specified as wide because I wanted to display the map as large as the full width of the screen.

###Creating a Map with MarkerCluster
```python
map1 = folium.Map(location=[49.255, -123.13], zoom_start=12)
marker_cluster = plugins.MarkerCluster().add_to(map1)

data = read_csv_data(datafile1)
for station in data:
    location = [station["latitude"], station["longitude"]]
    folium.Marker(
        location,
        popup=folium.Popup(
            "<b>Operator:</b><br>" + station["operator"] + "<br>"
            "<b>Address:</b><br>" + station["address"],
            max_width=450,
        ),
    ).add_to(marker_cluster)
```
Firstly, I need to do the basic settings for the map with "folium.Map()". The center position of the map (latitude and longitude) and zoom are specified as the minimum settings. Shifting the values a little bit at a time, I looked for a value where Vancouver would just fit.

Next, generate a layer of marker clusters by "plugins.MarkerCluster()” and add them to the map I just created. MarkerCluster is one of folium's many Plugins. Using this, markers placed on the map can be grouped together when the map is zoomed out. The number of markers in the cluster is displayed instead of the number of markers in the map. When the map is zoomed in, the markers are displayed one by one.

Finally, marker information contained in the CSV file is retrieved one by one and added to the marker cluster layer.

###Creating a Map with GeoJson
```python
map2 = folium.Map(location=[49.255, -123.13], zoom_start=12)

popup = folium.GeoJsonPopup(
    fields=["name"],
    aliases=["Area Name:"],
)

folium.GeoJson(
    datafile2,
    popup=popup,
).add_to(map2)
```
The basic map settings are the same. Then add GeoJSON data to the map. GeoJSON data itself is provided by CITY OF VANCOUVER OPEN DATA PORTAL, and I didn't edit the file.

We can display a popup by specifying the argument named popup in folium.GeoJson. folium has a function called "GeoJsonPopup" that retrieves all specified information from a JSON file and displays it in a popup.

###Displaying a Map on the Screen
```python
st.header("EV Charging Stations in the Vancouver", divider=True)
st.components.v1.html(folium.Figure().add_child(map1).render(), height=500)

st.header("Local Area Boundary in the Vancouver (GeoJSON)", divider=True)
st.components.v1.html(folium.Figure().add_child(map2).render(), height=500)
```

Streamlit's Components API is used here. Please check the following url for details. ([Components API Reference](https://docs.streamlit.io/library/components/components-api))

There are two types of Streamlit components: static components and bi-directional components. This time, my goal is solely to render a map from a Python visualization library (forlium); I used st.components.v1.html

It is very easy to use and is as follows.
<table>
<tr>
<th colspan="2">Function signature</td>
</th>
<tr>
<td colspan="2">st.components.v1.html(html, width=None, height=None, scrolling=False)</td>
</tr>
<tr>
<th colspan="2">Parameters</th>
</tr>
<tr>
<td>html (str)</td>
<td>The HTML string to embed in the iframe.</td>
</tr>
<tr>
<td>width (int)</td>
<td>The width of the frame in CSS pixels. Defaults to the app's default element width.</td>
</tr><tr>
<td>height (int)</td>
<td>The height of the frame in CSS pixels. Defaults to 150.</td>
</tr>
<tr>
<td>scrolling (bool)</td>
<td>If True, show a scrollbar when the content is larger than the iframe. Otherwise, do not show a scrollbar. Defaults to False.</td>
</tr>
</table>

The HTML returned from the forlium’s render method is passed as the first parameter, the width is not specified but left to the screen size, and the height is set to 500px.

As explained in the Streamlit documentation, the result is incorporated as an iframe.

![Screenshot3-Development Tool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwev8inh4t26vttotm8b.png)

By the way, I didn't use bi-directional component in this blog post, but it is something not simply controlled and displayed only from the Streamlit (Python) side but instead receives input values from the component's UI, passes them to the Streamlit (Python) side for processing and then returns the results to the component side.

I want to try bi-directional component next time.

That's all for this blog post!