Map of the City of Groningen
Photo by Skitterphoto from Pexels
The neighbourhoods of Groningen¶
Using Python and the folium mapping library to create an interactive map¶
This example shows how to convert a Shape file to a GEOJson format using the geopandas library and then plot an interactive map using the folium mapping library
Geopandas
- A geopandas dataframe is similar to a pandas dataframe with some addtional geospatial features:
- Using the geopandas read_file method to import a Shape file into a geopandas dataframe
- Store specfic geospatial figures as polygon's or point's (longitude and latitude pairs)
- Convert one geo-coordinate system to another
- Export to a GEOJson format
- Methods to calculate a distance between two points or calculate an area of a polygon
- A geopandas dataframe has all the features of a pandas dataframe which makes it easy to manipulate
Folium
- The folium library has the Choropleth class which enables you to create an interactive map
- The map can have the following functionality:
- zoom-in/zoom-out
- color coding of intensities, for example showing the population density of a neighbourhood
a lower population density having a lighter color or shade and higher population density showing
as a darker color or shade - adding a title and a legend
- More examples can be found here
Here we will be using a GEOJson file and a geopandas dataframe to plot the population densities by neighbourhood
In [1]:
import geopandas as gpd
import folium
import numpy as np
import matplotlib.pyplot as plt
Steps to convert a ESRI-Shape file to a geopandas dataframe and GEOJson format¶
- Download the zip file from the CBS (Centraal Bureau voor de Statistieken) containing the Wijk- en Buurtkaart 2017.
This file contains all the neighbourhoods of cities and towns in the Netherlands
Note: parts of the data from CBS has also been provided by the Kadaster - Unzip the file which will contain several files.
We are looking for the ones with a buurt_2017 prefix. The main file is buurt_2017.shp
This file contains the boundaries of each neighbourhood stored in a polygon format
a collection latitude/longitude points - Import the buurt_2017.shp file into a geopandas dataframe using the read_file function
- We only want the neighbourhoods of the city of Gronigen so create a filter and rebuild the dataframe
- The folium map coordinates system is based on the WSG84 system also know as egsg:4326
We need to convert the existing format "rijksdriehoekcoordinaten format" (epsg:28992) to WSG84
the to_crs method will do the job - For this example we only need a few columns so only select the following
- BU_CODE = neighbourhood code
- BU_NAAM = neighbourhood name,
- BEV_DICHTH = population density
- AANT_INW = number of inhabitants
- Note for more information (in Dutch) on the columns see the accompanying pdf
- There are several neighbourhoods that have a negative population density, like parks
Correct these values and set to 0 - Determine the center geo-coordinates of each neighbourhood using the geopandas centroid function
We need this to display a marker for each neighbourhood on the map
In [2]:
# 3. Reading the Netherlands neighbourhoods shape file into a geopandas dataframe
df_grun = gpd.read_file('data/buurt_2017.shp')
# 4. Only interested in Gronigen
df_grun = df_grun[df_grun["GM_NAAM"] == "Groningen"]
# 5. Convert geometry from rijksdriehoekcoordinaten format (epsg:28992)
# to WSG84 (epsg:4326) format in order to display in a folium choropleth map
df_grun = df_grun.to_crs({'init' :'epsg:4326'})
# 6. We only need a few columns:
# - BU_CODE = neighbourhood code
# - BU_NAAM = neighbourhood name,
# - BEV_DICHTH = population density
# - AANT_INW = number of inhabitants
df_grun = df_grun[['BU_CODE','BU_NAAM','BEV_DICHTH','AANT_INW','geometry']]
# 7. Fix negative population density as there are area's with -9999 (parks etc.)
df_grun["BEV_DICHTH"] = np.where((df_grun.BEV_DICHTH < 0), 0, df_grun.BEV_DICHTH)
# 8. Determine the centroid of each neighbourhood for display purposes
df_grun['Latitude'] = df_grun['geometry'].centroid.y
df_grun['Longitude'] = df_grun['geometry'].centroid.x
# have a look at the file
df_grun.head()
Out[2]:
Use the geopandas to_file method to create a GeoJson file which we will need to display the map
In [3]:
# convert to GeoJSON format
df_grun.to_file("data/groningen_buurten.json", driver="GeoJSON")
Create and display the folium choropleth map¶
- Used google maps to determine the central coordinates of the city
and with some trial and error determine the best zoom factor to be 13
in order to display the map at the prefered size.
Use the foliumMap
class to create the initial map object - Use the folium
Choropleth
class to create a map based on the GeoJson file we have just create
The map will used the geo-coordinates in the file to display the boundaries of each neighbourhood
Method attributesgeo_data="data/groningen_buurten.json"
-> the name and path of the GeoJson filedata=df_grun
-> the geopandas dataframe containing addtional information of each neighbourhood
like the population density and number of inhabitantscolumns=['BU_CODE','BEV_DICHTH']
-> we are using the neighbourhood code and population density
to display the density by color codetiles='OpenStreetMap'
-> use the open street map backgroundkey_on='feature.properties.BU_CODE'
-> this is the propertie in the GeoJson file to look for
when plotting the neighbourhood boundariesfill_color='RdPu'
-> use the color map from red to purple to display the population densityfill_opacity=0.3
-> scale from 0 to 1, 0 = no fill, 1 = completely cover
- Add circle shaped markers using the folium
CircleMarker
class to create a marker showing in
the central location of each neighbourhood. when a user hovers over the markerwith a mouse,
a text will appear showing the name, number of inhabitants and population density of the neighbourhood
Method attributes[lat, lon]
-> latitude and logitude position of the neighbourhood marker (centroid)radius=4
-> circle radius, a higher number is largerpopup=label
-> display text in a popup when clicking on the marker.tooltip=toolt
-> display text when the mouse moves over a markercolor='red'
-> display the marker in readfill=True
-> fill the marker with a background colorfill_opacity=0.3
-> set the markers opacity to 0.3
Note See the documentation for folium for further information
In [4]:
# 1. Use the central coordinates of the city of Groningen and use these to display the map
# The zoom_start attribute is used for the zoom factor when initially displaying the map
map_groningen = folium.Map(location=[53.216116, 6.562249], zoom_start=13);
# 2. Set up the Choropleth map object
folium.Choropleth(geo_data="data/groningen_buurten.json",
data = df_grun,
popup=df_grun['BU_CODE'],
columns=['BU_CODE','BEV_DICHTH'],
key_on='feature.properties.BU_CODE',
tiles='OpenStreetMap',
fill_color='RdPu',
fill_opacity=0.3,
line_opacity=0.2,
legend_name='Groningen - bevolkingsdichtheid per buurt - 2017').add_to(map_groningen)
# 3. Looping through each neighbourhood's attributes create circle-shaped markers which when a user hovers over the marker
# with a mouse, a text will appear showing the name, number of inhabitants and population density of the neighbourhood
for lat, lon, name, density, population in zip(df_grun['Latitude'], df_grun['Longitude'],
df_grun['BU_NAAM'], df_grun['BEV_DICHTH'],df_grun['AANT_INW']):
label = folium.Popup(html = '<h4>{}</h4><br> inhabitants: {} ({}/km2)'.format(name,str(population),str(density)), parse_html=False)
toolt = '{} - inhabitants: {} ({}/km2)'.format(name, str(population),str(density))
folium.CircleMarker(
[lat, lon],
radius=4,
popup=label,
tooltip=toolt,
color='red',
fill=True,
# fill_color=rainbow[cluster-1],
fill_opacity=0.3).add_to(map_groningen)
map_groningen.save('data/bevolkings_dichtheid.html')
# display the map
map_groningen
Out[4]:
Simplest version displaying a map using folium's Map class¶
- create a map object based on folium's Map class
map_groningen = folium.Map(location=[53.216116, 6.562249], zoom_start=12)
- add the GeoJson file to the map
folium.GeoJson("data/groningen_buurten.json").add_to(map_groningen)
- display the map
map_groningen
In [5]:
map_groningen = folium.Map(location=[53.216116, 6.562249], zoom_start=12)
folium.GeoJson("data/groningen_buurten.json").add_to(map_groningen)
map_groningen
Out[5]:
In [ ]: