Interactive maps on Leaflet

Whenever you go into a website that has some kind of interactive map, it is quite probable that you are wittnessing a map that has been made with a JavaScipt library called Leaflet (the other popular one that you might have wittnessed is called OpenLayers).

There is also a Python module called Folium that makes it possible visualize data that’s been manipulated in Python on an interactive Leaflet map.

Creating a simple interactive web-map

Let’s first see how we can do a simple interactive web-map without any data on it. We just visualize OpenStreetMap on a specific location of the a world.

First thing that we need to do is to create a Map instance. There are few parameters that we can use to adjust how in our Map instance that will affect how the background map will look like. We should already be able to see what our map looks like. More details can be found in module API documentation.

In [1]: import folium

# Create a Map instance
In [2]: m = folium.Map(location=[58.37, 26.72], zoom_start=11, control_scale=True, prefer_canvas=True, width=600, height=450)

The first parameter location takes a pair of lat, lon values as list as an input which will determine where the map will be positioned when user opens up the map. zoom_start -parameter adjusts the default zoom-level for the map (the higher the number the closer the zoom is). control_scale defines if map should have a scalebar or not.

Now we can check how it looks, by either displaying it directly in the Jupyter Notebook:

# To display it in a Jupyter notebook, simply ask for the object representation
m

or by saving it into a html file which we can open in the browser:

# Filepath to the output
In [3]: outfp = "source/_static/img/folium_base_map.html"

# Save the map
In [4]: m.save(outfp)

It will now just show the basemap in such a way that we initialized it.

Take a look at the map by clicking it with right mouse and open it with Google Chrome which then opens it up in a web browser.

  • Let’s change the basemap style to Stamen Toner and change the location of our map slightly. The tiles -parameter is used for changing the background map provider and map style (see here for all possible ones).

# Let's change the basemap style to 'Stamen Toner'
In [5]: m = folium.Map(location=[58.37, 26.72], tiles='Stamen Toner', zoom_start=11, control_scale=True, prefer_canvas=True, width=600, height=450)
# To display it in a Jupyter notebook, simply ask for the object representation
m
  • And in order to save the file:

# Filepath to the output
In [6]: outfp = "source/_static/img/folium_base_map_toner.html"

# Save the map
In [7]: m.save(outfp)

Adding layers to the map

Adding layers to a web-map is fairly straightforward with Folium and similar procedure as with Bokeh and we can use familiar tools to handle the data, i.e. Geopandas. Our ultimate aim is to create a plot like this where population in Tartumaa, road network and the schools are plotted on top of a web-map.

First we need to prepare the data.

In [8]: import geopandas as gpd

In [9]: from fiona.crs import from_epsg

In [10]: from shapely.geometry import LineString, MultiLineString

 # Filepaths
In [11]: grid_fp = "source/_static/data/L6/population_square_km.shp"

In [12]: roads_fp = "source/_static/data/L6/roads.shp"

In [13]: schools_fp = "source/_static/data/L6/schools_tartu.shp"

 # Read files
In [14]: grid = gpd.read_file(grid_fp)

In [15]: roads = gpd.read_file(roads_fp)

In [16]: schools = gpd.read_file(schools_fp)

 # Re-project to WGS84, Folium requires all data to be in WGS84
In [17]: grid['geometry'] = grid['geometry'].to_crs(epsg=4326)

In [18]: roads['geometry'] = roads['geometry'].to_crs(epsg=4326)

In [19]: schools['geometry'] = schools['geometry'].to_crs(epsg=4326)

 # Make a selection (only data above 0 and below 1000)
In [20]: grid = grid.loc[(grid['Population'] > 0)]

 # Create a Geo-id which is needed by the Folium (it needs to have a unique identifier for each row)
In [21]: grid['geoid'] = grid.index.astype(str)

In [22]: roads['geoid'] = roads.index.astype(str)

In [23]: schools['geoid'] = schools.index.astype(str)

 # Select data
In [24]: grid = grid[['geoid', 'Population', 'geometry']]

In [25]: roads = roads[['geoid', 'TYYP', 'geometry']]

In [26]: schools = schools[['geoid', 'name', 'geometry']]

 # convert the dataframe to geojson
In [27]: grid_jsontxt = grid.to_json()

In [28]: roads_jsontxt = roads.to_json()

In [29]: schools_jsontxt = schools.to_json()

Now we have our data stored in the grid_jsontxt etc. variables as GeoJSON format which basically contains the data as text in a similar way that it would be written in a .geojson -file.

Now we can start visualizing our data with Folium.

In [30]: m = folium.Map(location=[58.37, 26.72], zoom_start=11, control_scale=True, prefer_canvas=True, width=600, height=450)

In [31]: folium.GeoJson(grid_jsontxt).add_to(m)
Out[31]: <folium.features.GeoJson at 0x29c39303408>

In [32]: folium.GeoJson(roads_jsontxt).add_to(m)
Out[32]: <folium.features.GeoJson at 0x29c393037c8>

In [33]: folium.GeoJson(schools_jsontxt).add_to(m)
Out[33]: <folium.features.GeoJson at 0x29c39a1c288>
# To display it in a Jupyter notebook, simply ask for the object representation
m
  • And in order to save the file:

# Filepath to the output
In [34]: outfp = "source/_static/img/folium_geojson_plain.html"

# Save the map
In [35]: m.save(outfp)