Python: Contributing to the Folium library

Contributing to Folium

What is Folium?

Folium is a easy to use leaflet JavaScript wrapper for Python. It allows you to create interactive maps much like Google maps but with your own data laid over the top. It does this by accessing a tile server to add a background image to that point in space that you are in (x,y,z) the latitude, longitude and zoom level.

For example:

If we want a tile at (z,x,y) = (1,1,1) we can view this using the tile server API by going to https://cartodb-basemaps-1.global.ssl.fastly.net/light_all/1/1/1.png we would receive a tile with lat, lon and zoom of 1 (notice the way the path of the URL ends in /1/1/1.png, this corresponds to /{z}/{x}/{y}.png

Similarly we could get (z,x,y) = (3, 1, 2)

The good news is, that by using Leaflet/Folium we don't need to worry about such details. We simply need to create a map and the JavaScript library will do all of this behind the scenes for us!

Let's look at a brief example:

In [1]:
import folium

m = folium.Map(
    location=[56.096555, -3.64746],
    tiles = 'cartodbpositron',
    zoom_start=5
)

m
Out[1]:

That was incredibly easy, we have created a map centered around the United Kingdom, at a zoom level of 5 and using this minimalistic style dubbed 'cartodbpositron'.

How do you contribute to Folium?

Folium is a free open source library - that means it is developed and maintained by volunteers and as such it realizes on users feedback and them getting involved with fixes, extra features and assisting with documentation. If you are new to an open source library and don't feel like you can contribute much to the code then I would implore you to contribute to their documentation, as you are the perfect candidate, you have gone through the learning curve and can impart that knowledge onto other users.

Short tutorial on contributing to open source libraries:

Also if you run into any issues while using the library and have looked through all the documentation then head over to the 'issues' page on the library's GitHub page and inform them so they can look at improving their functionality/documentation.

My latest contributions

How to download and use the new features before the pull request is accepted

Install git on your machine

!pip uninstall -y folium && pip install git+https://github.com/ghandic/[email protected]

Quick download function

In [2]:
import urllib.request, json 

def downloadJson(link):
    
    with urllib.request.urlopen(link) as url:
        data = json.loads(url.read().decode())
    
    return data

GeoJsonCss

GeoJson is a file format that specifies the geometry of the shapes you wish to visualize as well as the properties associated with that point.

The following example denotes a geometry which is a point type, it is located at (37.61001, 55.752301). The point represents Vladamir's house so we have added some properties to the point, in this case 'title' and 'age'.

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "title": "Vladamir's house", "age" : 400 }, "geometry": { "type": "Point", "coordinates": [37.61001, 55.752301] } } ] }

However, if we were to plot this using Folium's GeoJson method we would get a default marker - not very customizable, one solution would be to go into the Folium packages JavaScript and hack it ourselves as an add-hoc way to get the beautiful looking plot that we had planned. However, this would not work for other users of Folium, so instead we can look outward to see if there are any JavaScript library plugins for Leaflet. In this case there was already a library for what we need, it is called GeoJsonCss, which allows you to pass through the style for your geometries inside the GeoJson object.

How do we use the new GeoJsonCss method?

This new GeoJson method is an elegant way to pass through the styles for your data points. Here is the same point before, but this time it will be displayed with a house icon and a pop-up window that contains the title of the point.

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "title": "Vladamir's house", "age" : 400 }, "geometry": { "type": "Point", "coordinates": [37.61001, 55.752301] }, "style": { "icon": { "iconUrl": "http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png", "iconSize": [32, 32], "iconAnchor": [16, 16] } }, "popupTemplate": "{title}" } ] }

Let's see it in action!

Original vs New

In [3]:
import folium
vlad = downloadJson("https://raw.githubusercontent.com/ghandic/folium/master/examples/data/vlad.json")
In [4]:
GeoJson = folium.Map(location=[55.75258, 37.61489], zoom_start=14, tiles = 'cartodbpositron')

folium.GeoJson(vlad).add_to(GeoJson)
GeoJson
Out[4]:
In [5]:
GeoJsonCss = folium.Map(location=[55.75258, 37.61489], zoom_start=14, tiles = 'cartodbpositron')

folium.GeoJsonCss(vlad).add_to(GeoJsonCss)
GeoJsonCss
Out[5]:

TimeStamped GeoJson

As we have seen the GeoJson file format is very good at adding properties which make it very accessible for the JavaScript libraries to use. Another piece of information we can encode into this file format is a time dimension. This can be very useful for visualizing the spatial-temporal variation (how things change over space and time). For example, one may wish to see where they had lived over the course of their life.

In this contribution I hacked a little bit of the JavaScript to add some styling to the points and make the time slider look more human readable by allowing the user to pass through a time format.

Here is a small example of the GeoJson that is required to produce a spatial-temporal visualization, it depicts a persons movement over time with the house surrounded by a green circle being their most recent house up till that point:

{'features': [{'geometry': {'coordinates': [-2.548828, 51.467697], 'type': 'Point'}, 'properties': {'icon': 'marker', 'iconstyle': {'iconSize': [20, 20], 'iconUrl': 'http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png'}, 'popup': '<h1>address1</h1>', 'time': '2017-06-02'}, 'type': 'Feature'}, {'geometry': {'coordinates': [-0.087891, 51.536086], 'type': 'Point'}, 'properties': {'icon': 'marker', 'iconstyle': {'iconSize': [20, 20], 'iconUrl': 'http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png'}, 'popup': '<h2 style="color:blue;">address2</h2>', 'time': '2017-07-02'}, 'type': 'Feature'}, {'geometry': {'coordinates': [-6.240234, 53.383328], 'type': 'Point'}, 'properties': {'icon': 'marker', 'iconstyle': {'iconSize': [20, 20], 'iconUrl': 'http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png'}, 'popup': '<h2 style="color:orange;">address3</h2>', 'time': '2017-08-02'}, 'type': 'Feature'}, {'geometry': {'coordinates': [-1.40625, 60.261617], 'type': 'Point'}, 'properties': {'icon': 'marker', 'iconstyle': {'iconSize': [20, 20], 'iconUrl': 'http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png'}, 'popup': '<h2 style="color:green;">address4</h2>', 'time': '2017-09-02'}, 'type': 'Feature'}, {'geometry': {'coordinates': [-1.516113, 53.800651], 'type': 'Point'}, 'properties': {'icon': 'marker', 'iconstyle': {'iconSize': [20, 20], 'iconUrl': 'http://downloadicons.net/sites/default/files/small-house-with-a-chimney-icon-70053.png'}, 'popup': '<h2 style="color:green;">address5</h2>', 'time': '2017-10-02'}, 'type': 'Feature'}, {'geometry': {'coordinates': [[-2.548828, 51.467697], [-0.087891, 51.536086], [-6.240234, 53.383328], [-1.40625, 60.261617], [-1.516113, 53.800651]], 'type': 'LineString'}, 'properties': {'icon': 'circle', 'iconstyle': {'fillColor': 'green', 'fillOpacity': 0.6, 'radius': 13, 'stroke': 'false'}, 'popup': 'Current address', 'style': {'weight': 0}, 'times': ['2017-06-02', '2017-07-02', '2017-08-02', '2017-09-02', '2017-10-02']}, 'type': 'Feature'}], 'type': 'FeatureCollection'}

The next step for this plugin is to get it to work with GeoJsonCss so that the styling is consistent throughout the Folium library

Let's see it in action!

In [6]:
import folium
from folium import plugins

spatial_temporal_data = downloadJson("https://raw.githubusercontent.com/ghandic/folium/master/examples/data/house_movement.json")

m = folium.Map(
    location=[56.096555, -3.64746],
    tiles = 'cartodbpositron',
    zoom_start=5
)

plugins.TimestampedGeoJson(spatial_temporal_data, 
            period='P1M', add_last_point=True,
            auto_play=False, loop=False, 
            maxSpeed=1, loopButton=True,
            dateOptions='YYYY/MM/DD',
            timeSliderDragUpdate=True).add_to(m)

m
Out[6]:

Search Layer Plugin

This plugin was in response to a request made on the issues page for Folium, as a user wanted the Folium package to incorporate a leaflet plugin called leaflet-search which allows you to 'search stuff' as the author Stefano Cudini puts it. To use this plugin we require the user to use a single GeoJson object into the search layer.

The next steps for this plugin will be to enable fuzzy match, have more options for geometries than Polygon/Point and have multiple search layers, as at present there is only the ability to search through a single layer.

Loading some data

In [7]:
bars = downloadJson("https://raw.githubusercontent.com/ghandic/folium/master/examples/data/bars.json")
    
states = downloadJson("https://raw.githubusercontent.com/ghandic/folium/master/examples/data/us-states.json")

Adding in some style

Note this does not work too well with polygons as it resets the style to the default rather than the initial settings. Issue has been raised on the leaflet-plugin GitHub page

In [8]:
for bar in bars['features']:
    bar['style'] = {"icon": {
        "iconUrl": "https://d30y9cdsu7xlg0.cloudfront.net/png/80776-200.png",
        "iconSize": [16, 16]}}
    bar["popupTemplate"] = "<strong>{name}</strong>"

Using the plugin

In [9]:
import folium
from folium import plugins
In [10]:
m = folium.Map(
    location=[41.9,12.5],
    tiles = 'cartodbpositron',
    zoom_start=11
)

plugins.Search(bars, position='topright',
               search_zoom=20,
               search_label='name',
               geom_type="Point",
               popup_on_found=True).add_to(m) 

m
Out[10]:
In [11]:
m = folium.Map(
    location=[37.8, -96],
    tiles = 'cartodbpositron',
    zoom_start=3 
)

plugins.Search(states, search_zoom=6,
               position='topright',
               search_label='name',
               geom_type="Polygon").add_to(m) 

m
Out[11]:

Summary

I had a lot of fun playing around with Folium, and there are so many more plugins to incorporate! So if you found these plugins useful take a look how you can do some contributing yourself!

The framework that the contributors of Folium have set up allows a little bit of copy, paste and manipulate development which makes it very fast to add in plugins.

Leave a comment

Your email address will not be published. Required fields are marked *