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.
We start off by importing a few libraries
%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.
url = 'https://raw.githubusercontent.com/prewired/workshops/master/data/processed/'
fn = 'swt_pollinator_data-2019-02-15.csv'
fn = path.join(url, fn)
fn
'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
.
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()
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 |
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.
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
(226, 3)
df.head()
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.
locations = zip(df.latitude, df.longitude, df.type)
list(locations)[:5] # we convert the iterable to a list before indexing into it
[(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
.
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
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
(226, 4)
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
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.
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:
edinburgh_greenspaces.columns
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.
edinburgh_greenspaces['geometry'][:10]
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.
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)