James JD Lee

Oct 30, 2020

6 min read

Simple App using Folium Choropleth with .geojson

Unlike the other previous blog posts, I will be diving into some technical skills I learned in class and online. Background info: Feras Altwal, Caroline Clark, and myself worked on a project together to create model to predict COVID-19 severity on county level. As an introduction to our project, we created a model to predict COVID-19 severity on county level using the census data. My job was to create a working app to visually present the features on a map with severity heatmap and show prediction according to our model.

Enough on our project. Let’s dive into Folium.

Here is a tl;dr. Our team extracted data on COVID-19 and the Census Bureau for California, Florida, Illinois, New York, and Texas in .csv file and exported geojson data for their counties.

The problem is that folium is compatible with json but not so much with pandas DataFrame. To bypass this we must hardcode the information we want into a type of .json file so our app can extract the data.

Starting from the beginning, let’s import the .csv file as ‘ca’ as a DataFrame and .geojson data as ‘json_counties’ since we are focusing on county level. For simplicity, I will only be demonstrating on State of California. Most importantly, the order of our data and .geojson must match up accordingly. So let’s alphabetically organize both data by county names.

ca = pd.read_csv('ca_counties.csv')
ca.sort_values(by = ['county'], inplace = True)
ca
with open('ca-counties.geojson') as f:
json_counties = json.load(f)
json_counties = sorted(json_counties["features"],
key = lambda x:x["properties"]["name"])

First things first, let’s briefly examine the .geojson data and our DataFrame look like after sorting.

{"type":"FeatureCollection", "features":
{'type': 'Feature',
'properties': {'name': 'Alameda',
'cartodb_id': 1,
'created_at': '2015-07-04T21:04:58Z',
'updated_at': '2015-07-04T21:04:58Z'},
'geometry': {'type': 'MultiPolygon',
'coordinates': [[[[-122.312934, 37.897333],
[-122.28848, 37.897925],
[-122.27418, 37.905025],
[-122.263946, 37.903734],
[-122.249477, 37.893086],
[-122.248914, 37.886867],
[-122.223878, 37.878326],
[-122.216276, 37.868822],
[-122.220389, 37.864427],
[-122.204094, 37.851387],
[-122.196101, 37.842005]....}}...

Next, we want to show each counties’ severity rate in a sort of a heatmap. We first need folium to read our ‘covid-severity’ from the DataFrame and the name of counties. Our group has broken down the severity rate to 1, 2, and 3, 1 being low and 3 being high. The coordinates used are to show just the state of California. To do this follow the below code.

for i in json_counties['features']:
i['id'] = i['properties']['name']
m_ca = folium.Map(location = [36.17, -119.7462], zoom_start = 6)choropleth = folium.Choropleth(
geo_data = json_counties,
name = 'choropleth',
data = ca,
columns = ['county', 'covid_severity'],
key_on = 'feature.id',
fill_color = "Reds",
fill_opacity = .6,
line_opacity = .5,
legend_name = 'Death Rate',
highlight = True).add_to(m_ca)
m_ca

To explain some thought process, we now created a new key in our .geojson as ‘id’ that contains the county names; this is necessary for folium to pick out where the two data are sharing the information(key_on). Notice that ‘id’ is on the same level as ‘properties’. Next, we create a map that shows California using the coordinates. I created a new variable called ‘choropleth’ and I will explain later why. We will use folium.Choropleth to represent each counties according to the severity rate. So this is how it looks like so far.

Now that our heat map is done, let’s move onto data representation on the map. Using tooltip method, I will append ‘pop_density’ column (a feature of my choice) into .geojson with a new key: ‘Pop’. To create a new key, first we need to create a tooltip_text containing the information from the column ‘pop_density’. Next, we append this tooltip into our ‘json_counties’ under ‘properties’. Let’s save this as a new .geojson file to use it in our app later!

tooltip_text = []
for idx in range(len(ca)):
tooltip_text.append(str(round(ca['pop_density'][idx])))
for idx in range(len(tooltip_text)):
json_counties['features'][idx]['properties']['Pop'] = tooltip_text[idx]
with open('ca-counties2.geojson', 'w', encoding='utf-8') as f:
json.dump(json_counties, f, ensure_ascii=False)

Our new .geojson file now contains the population density for each county. There are many different ways to do a data visualization, but our group wanted the information to pop out as we hover over the counties. So let’s get cracking.

choropleth.geojson.add_child(folium.features.GeoJsonTooltip(
['name', 'Pop'],
aliases = ['County', 'Pop-Den'],
style=('background-color: grey; color: white;'),
localize=True).add_to(m_ca))
m_ca

Here comes why I created ‘choropleth’ variable. This way, we can incorporate add_child. This function combined with GeoJsonToolTip will show our content just by hovering over the county. There are mainly three options to show the labels. We can show the labels as is (name of the key, in this case it’ll be ‘name’ and ‘Pop’), as anything we want (‘County’, ‘Pop-Den’) using aliases like I did, or nothing at all by using ‘labels=False’ inside of GeoJsonTooltip function. Now take a look at our beautiful output.

Awesome sauce! Now we are ready to implement this into our app. Let’s go create a home page, organize the coding, and run it.

To specify, I used Visual Studio and Flask to build the app with some basic html. Compared to Streamlit and Django, I found it easier to work with. Here is a link to Flask’s webpage.

First, import the necessary libraries in your app notebook. Since we are using Flask, I will create my app accordingly.

from flask import Flask, Response, request, render_template, jsonify
import numpy as np
import pandas as pd
import pickle
import requests
import folium
import json
app = Flask("myApp")

To create a home page, I used a separate html file(‘form.html’) that I created to customize the page and saved it under ‘template’ folder. Bootstrapping and some styling were implemented to make the page look better but I won’t get into html much. Maybe later.

After creating the right page format we wanted, we have to plug the html file into our actual homepage. render_template() will allow us to read what is in our html and show it to us when running the app.

#Home Page
@app.route('/')
def home():
return render_template('form.html')

Now that our home page is done, let’s add our mapping to the app.

@app.route('/California')
def camap():
ca = pd.read_csv('ca_counties.csv') with open('ca-counties2.geojson') as f:
json_counties = json.load(f)
for i in json_counties['features']:
i['id'] = i['properties']['name']
m_ca = folium.Map(location = [36.17, -119.7462], zoom_start = 6) choropleth = folium.Choropleth(
geo_data = json_counties,
name = 'choropleth',
data = df,
columns = ['county', 'covid_severity'],
key_on = 'feature.id',
fill_color = "Reds",
fill_opacity = .6,
line_opacity = .5,
legend_name = 'Death Rate',
highlight = True).add_to(m_ca)
choropleth.geojson.add_child(folium.features.GeoJsonTooltip(
['name', 'Pop'],
aliases = ['County', 'Pop-Den'],
style=('background-color: grey; color: white;'),
localize=True).add_to(m_ca))
folium.LayerControl().add_to(m_ca) return m_ca._repr_html_()if __name__ == '__main__':
app.run(debug = True)

LayerControl() is added to make sure our child is layered correctly and _repre_html() is required in ‘return’ line to show the map in a new page. Please be noted that I have omitted information on html. ‘@app.route(‘/California’) reacts to a button I created under my html file. Here is a line from my form.html to show what it reacts to and how it looks like. I’m only a few baby steps into html so please don’t yell at me if there’s something ugly or wrong; most importantly it runs.

<form action="/California">
<p>
<div class = 'col-sm'>
<button type="submit" class = "btn btn-primary">California</button>
</div>
</p>
</form>

All we need to do is do ‘python app.py’ in our terminal under the folder this app is in. WOOHOO! Now we have a working app! Need a beer? I definitely needed one after this.

I dove straight into building the app with shallow knowledge in flask, folium, and html, and it was scary! Knowing that I have finally completed the app, I can look back and say that it was quite fun actually. Hopefully this post helped you and good-luck!