Edinburgh Living Landscape Pollinator Pledge

This notebook illustrates how to plot the location data collected by the Edinburgh Pollinator Pledge initiative.

It uses the folium library, which is a Python interface to leaflet.js.

Preliminary Steps

We start off by importing a few libraries

In [1]:
%matplotlib inline

from os import path

import folium
import geopandas as gpd
import matplotlib.pyplot as plt
import pandas as pd
from shapely.geometry import Point

Next, we fetch the main pledge data which was provided for this challenge. We've stored it on GitHub for convenience.

In [49]:
url = 'https://raw.githubusercontent.com/prewired/workshops/master/data/processed/'
fn = 'swt_pollinator_data-2019-02-15.csv'
fn = path.join(url, fn)
fn
Out[49]:
'https://raw.githubusercontent.com/prewired/workshops/master/data/processed/swt_pollinator_data-2019-02-15.csv'

The pandas library is powerful tool for loading tabular data into a structure called a DataFrame. The read_csv() method takes file-like object and returns a DataFrame. Although it's probably not required, we can make sure that the date column in the input is parsed correctly. We also shorten one of the column labels, which is inconveniently long.

The head() method allows us to look at the first few rows of the DataFrame.

In [50]:
pollinator_data = pd.read_csv(fn, parse_dates=['Entry Date'])
pollinator_data = pollinator_data.rename(columns={'What type of space do you have?': 'type'})
pollinator_data.head()
Out[50]:
latitude longitude type Plant for pollinators Make space for nature Expand the network What is your first step going to be? Entry Id Entry Date
0 55.938051 -3.217624 Communal greenspace NaN Make space for nature NaN NaN 767 2019-12-02 13:43:00
1 55.925941 -3.277307 Small garden Plant for pollinators Make space for nature NaN Create an insect house 760 2019-11-02 07:36:00
2 55.943268 -3.288348 Large garden Plant for pollinators Make space for nature NaN Plant native flowers, make wildlife pond 756 2019-10-02 13:09:00
3 55.956412 -3.290882 Small garden Plant for pollinators Make space for nature NaN Planting flowers 750 2019-09-02 09:10:00
4 55.899452 -3.218028 Small garden Plant for pollinators NaN NaN Decide on types of pollinators 746 2019-08-02 07:57:00

Version 1: Import location using CircleMarkers

This approach pulls the data from the DataFrame directly.

We will ignore all columns apart from the first three — this is just for cosmetic reasons, we could skip this step.

In [4]:
df = pollinator_data[['latitude', 'longitude', 'type']]
df = df.dropna(subset=['latitude', 'longitude']) # drop any rows that have missing geo-coordinates
df.shape # rows x columns
Out[4]:
(226, 3)
In [5]:
df.head()
Out[5]:
latitude longitude type
0 55.938051 -3.217624 Communal greenspace
1 55.925941 -3.277307 Small garden
2 55.943268 -3.288348 Large garden
3 55.956412 -3.290882 Small garden
4 55.899452 -3.218028 Small garden

To make it easier to import data into a map, we will create a list of triples with the data we need. The Python zip() method creates an iterable of n-tuples from n input iterables.

In [6]:
locations = zip(df.latitude, df.longitude, df.type)
list(locations)[:5] # we convert the iterable to a list before indexing into it
Out[6]:
[(55.93805129999999, -3.2176237000000003, 'Communal greenspace'),
 (55.9259405, -3.2773065000000003, 'Small garden'),
 (55.943267500000005, -3.2883483, 'Large garden'),
 (55.9564125, -3.290882, 'Small garden'),
 (55.899451899999995, -3.2180276, 'Small garden')]

Now that we have an iterable of locations, it is straighforward to feed them into a folium map. In this approach, we can style the markers in various ways, so we've chosen to represent them as a red CircleMarker.

In [7]:
tiles = "openstreetmap"
edinburgh_centre = (55.953251, -3.188267)

m = folium.Map(location=edinburgh_centre, tiles=tiles, zoom_start=12)

for loc in locations:
    point = [loc[0], loc[1]]
    folium.CircleMarker(location=point,                      
                        radius = 5,
                        popup= loc[2],
                        color = 'red',
                        weight = 1,
                        fill='true',
                        fill_color='red',
                        fill_opacity=0.25).add_to(m)

# m.save('pollinators_circlmarkers.html') # do this if you want to save the map as a standalone html file.
m
Out[7]:

Version 2: Import locations as GeoJSON

This alternative approach uses GeoPandas to help organise the data as GeoJSON.

We start off by creating a list of shapely Point objects.

In [8]:
points = [Point(x, y) for x, y in zip(df.longitude, df.latitude)]

polli_gdf = gpd.GeoDataFrame(df, geometry=points) # create a GeoDataFrame
polli_gdf.crs = {"init": "epsg:4326"} # set a the Coordinate Reference System
polli_gdf.shape
Out[8]:
(226, 4)
In [9]:
tiles = "openstreetmap"
edinburgh_centre = (55.953251, -3.188267)

m = folium.Map(location=edinburgh_centre, tiles=tiles, zoom_start=12)

style_function = lambda x: {"fillColor": "#00FFFFFF", "color": "#000000"}

polli_geo = folium.GeoJson(
    polli_gdf,
    tooltip=folium.GeoJsonTooltip(
        fields=["type"], 
        labels=True,
        sticky=False,
    ),
    style_function=style_function
)

m.add_child(polli_geo)

m.save("pollinators_markers.html")
m
Out[9]:

Adding a new layer: Edinburgh Green Space Audit

One of the nice things we can do with this kind of map is add a new layer. It would be interesting to see how the pollinator locations relate to other green spaces in the city. So let's use data from the Council's Green Space Audit. You can find out some more information about it on the Council's Mapping Portal

We can fetch the data in GeoJSON format from the Mapping Portal using the GeoPandas read_file() method.

In [10]:
edinburgh_greenspaces = gpd.read_file(
    "http://data.edinburghcouncilmaps.info/datasets/223949a6212f4068b30aa6ed8fc2e1ef_15.geojson"
)

If we look at the columns in the GeoDataFrame, there is quite a lot of information:

In [11]:
edinburgh_greenspaces.columns
Out[11]:
Index(['OBJECTID_1', 'OBJECTID', 'Id', 'Ownership', 'Access', 'ry_Use',
       'Shape_Leng', 'PAN65', 'Shape_Le_1', 'NP_Name', 'Np_NO', 'CLASSIFICA',
       'NAME', 'YEAROPEN', 'Comments', 'PF_Quality', 'PF_CF', 'AuditScore',
       'Area_ha', 'OS_Quality', 'PQA_Grade', 'OLD_OS_Ref', 'OS_Ref',
       'Shapearea', 'Shapelen', 'geometry'],
      dtype='object')

However, we are mainly interested in the geometry column, which consists of a series of shapely Polygon objects.

In [12]:
edinburgh_greenspaces['geometry'][:10]
Out[12]:
0    (POLYGON ((-3.191305985701971 55.9798613048917...
1    (POLYGON ((-3.191575981920173 55.9736176672753...
2    POLYGON ((-3.192261702385649 55.9744905596996,...
3    POLYGON ((-3.189952463388474 55.97719496687115...
4    POLYGON ((-3.189397958243322 55.97560093581826...
5    POLYGON ((-3.179811420034048 55.97683150098928...
6    POLYGON ((-3.186472913977041 55.97290840346809...
7    POLYGON ((-3.180550860742277 55.97340911991013...
8    POLYGON ((-3.180143933366065 55.9741876890718,...
9    POLYGON ((-3.180884988531127 55.97478681461671...
Name: geometry, dtype: object

Folium's GeoJson() method makes it simple to read in this data and add it to the map m that we created earlier.

In [13]:
style_function = lambda x: {"fillColor": "#00FFFFFF", "color": "#000000"}

greenspace_geo = folium.GeoJson(
    edinburgh_greenspaces,
    tooltip=folium.features.GeoJsonTooltip(
        fields=["NAME", "PAN65", "AuditScore"],
        labels=True,
        sticky=False,
    ),
    style_function=style_function,
)

m.add_child(greenspace_geo)
Out[13]: