{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Edinburgh Living Landscape Pollinator Pledge\n",
"\n",
"This notebook illustrates how to plot the location data collected by the [Edinburgh Pollinator Pledge](https://edinburghlivinglandscape.org.uk/pollinatorpledge/) initiative.\n",
"\n",
"It uses the [folium](https://github.com/python-visualization/folium) library, which is a Python interface to [leaflet.js](https://leafletjs.com)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Preliminary Steps\n",
"\n",
"We start off by importing a few libraries "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
"from os import path\n",
"\n",
"import folium\n",
"import geopandas as gpd\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"from shapely.geometry import Point"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we fetch the main pledge data which was provided for this challenge. We've stored it on GitHub for convenience."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'https://raw.githubusercontent.com/prewired/workshops/master/data/processed/swt_pollinator_data-2019-02-15.csv'"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"url = 'https://raw.githubusercontent.com/prewired/workshops/master/data/processed/'\n",
"fn = 'swt_pollinator_data-2019-02-15.csv'\n",
"fn = path.join(url, fn)\n",
"fn"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The [pandas](https://pandas.pydata.org) 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.\n",
"\n",
"The `head()` method allows us to look at the first few rows of the `DataFrame`."
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" latitude | \n",
" longitude | \n",
" type | \n",
" Plant for pollinators | \n",
" Make space for nature | \n",
" Expand the network | \n",
" What is your first step going to be? | \n",
" Entry Id | \n",
" Entry Date | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 55.938051 | \n",
" -3.217624 | \n",
" Communal greenspace | \n",
" NaN | \n",
" Make space for nature | \n",
" NaN | \n",
" NaN | \n",
" 767 | \n",
" 2019-12-02 13:43:00 | \n",
"
\n",
" \n",
" 1 | \n",
" 55.925941 | \n",
" -3.277307 | \n",
" Small garden | \n",
" Plant for pollinators | \n",
" Make space for nature | \n",
" NaN | \n",
" Create an insect house | \n",
" 760 | \n",
" 2019-11-02 07:36:00 | \n",
"
\n",
" \n",
" 2 | \n",
" 55.943268 | \n",
" -3.288348 | \n",
" Large garden | \n",
" Plant for pollinators | \n",
" Make space for nature | \n",
" NaN | \n",
" Plant native flowers, make wildlife pond | \n",
" 756 | \n",
" 2019-10-02 13:09:00 | \n",
"
\n",
" \n",
" 3 | \n",
" 55.956412 | \n",
" -3.290882 | \n",
" Small garden | \n",
" Plant for pollinators | \n",
" Make space for nature | \n",
" NaN | \n",
" Planting flowers | \n",
" 750 | \n",
" 2019-09-02 09:10:00 | \n",
"
\n",
" \n",
" 4 | \n",
" 55.899452 | \n",
" -3.218028 | \n",
" Small garden | \n",
" Plant for pollinators | \n",
" NaN | \n",
" NaN | \n",
" Decide on types of pollinators | \n",
" 746 | \n",
" 2019-08-02 07:57:00 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" latitude longitude type Plant for pollinators \\\n",
"0 55.938051 -3.217624 Communal greenspace NaN \n",
"1 55.925941 -3.277307 Small garden Plant for pollinators \n",
"2 55.943268 -3.288348 Large garden Plant for pollinators \n",
"3 55.956412 -3.290882 Small garden Plant for pollinators \n",
"4 55.899452 -3.218028 Small garden Plant for pollinators \n",
"\n",
" Make space for nature Expand the network \\\n",
"0 Make space for nature NaN \n",
"1 Make space for nature NaN \n",
"2 Make space for nature NaN \n",
"3 Make space for nature NaN \n",
"4 NaN NaN \n",
"\n",
" What is your first step going to be? Entry Id Entry Date \n",
"0 NaN 767 2019-12-02 13:43:00 \n",
"1 Create an insect house 760 2019-11-02 07:36:00 \n",
"2 Plant native flowers, make wildlife pond 756 2019-10-02 13:09:00 \n",
"3 Planting flowers 750 2019-09-02 09:10:00 \n",
"4 Decide on types of pollinators 746 2019-08-02 07:57:00 "
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pollinator_data = pd.read_csv(fn, parse_dates=['Entry Date'])\n",
"pollinator_data = pollinator_data.rename(columns={'What type of space do you have?': 'type'})\n",
"pollinator_data.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Version 1: Import location using CircleMarkers\n",
"\n",
"This approach pulls the data from the `DataFrame` directly.\n",
"\n",
"We will ignore all columns apart from the first three — this is just for cosmetic reasons, we could skip this step."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(226, 3)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pollinator_data[['latitude', 'longitude', 'type']]\n",
"df = df.dropna(subset=['latitude', 'longitude']) # drop any rows that have missing geo-coordinates\n",
"df.shape # rows x columns"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" latitude | \n",
" longitude | \n",
" type | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 55.938051 | \n",
" -3.217624 | \n",
" Communal greenspace | \n",
"
\n",
" \n",
" 1 | \n",
" 55.925941 | \n",
" -3.277307 | \n",
" Small garden | \n",
"
\n",
" \n",
" 2 | \n",
" 55.943268 | \n",
" -3.288348 | \n",
" Large garden | \n",
"
\n",
" \n",
" 3 | \n",
" 55.956412 | \n",
" -3.290882 | \n",
" Small garden | \n",
"
\n",
" \n",
" 4 | \n",
" 55.899452 | \n",
" -3.218028 | \n",
" Small garden | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" latitude longitude type\n",
"0 55.938051 -3.217624 Communal greenspace\n",
"1 55.925941 -3.277307 Small garden\n",
"2 55.943268 -3.288348 Large garden\n",
"3 55.956412 -3.290882 Small garden\n",
"4 55.899452 -3.218028 Small garden"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[(55.93805129999999, -3.2176237000000003, 'Communal greenspace'),\n",
" (55.9259405, -3.2773065000000003, 'Small garden'),\n",
" (55.943267500000005, -3.2883483, 'Large garden'),\n",
" (55.9564125, -3.290882, 'Small garden'),\n",
" (55.899451899999995, -3.2180276, 'Small garden')]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"locations = zip(df.latitude, df.longitude, df.type)\n",
"list(locations)[:5] # we convert the iterable to a list before indexing into it"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tiles = \"openstreetmap\"\n",
"edinburgh_centre = (55.953251, -3.188267)\n",
"\n",
"m = folium.Map(location=edinburgh_centre, tiles=tiles, zoom_start=12)\n",
"\n",
"for loc in locations:\n",
" point = [loc[0], loc[1]]\n",
" folium.CircleMarker(location=point, \n",
" radius = 5,\n",
" popup= loc[2],\n",
" color = 'red',\n",
" weight = 1,\n",
" fill='true',\n",
" fill_color='red',\n",
" fill_opacity=0.25).add_to(m)\n",
"\n",
"# m.save('pollinators_circlmarkers.html') # do this if you want to save the map as a standalone html file.\n",
"m"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Version 2: Import locations as GeoJSON \n",
"\n",
"This alternative approach uses [GeoPandas](https://geopandas.readthedocs.io/en/latest/) to help organise the data as GeoJSON.\n",
"\n",
"We start off by creating a list of shapely `Point` objects."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(226, 4)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"points = [Point(x, y) for x, y in zip(df.longitude, df.latitude)]\n",
"\n",
"polli_gdf = gpd.GeoDataFrame(df, geometry=points) # create a GeoDataFrame\n",
"polli_gdf.crs = {\"init\": \"epsg:4326\"} # set a the Coordinate Reference System\n",
"polli_gdf.shape"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tiles = \"openstreetmap\"\n",
"edinburgh_centre = (55.953251, -3.188267)\n",
"\n",
"m = folium.Map(location=edinburgh_centre, tiles=tiles, zoom_start=12)\n",
"\n",
"style_function = lambda x: {\"fillColor\": \"#00FFFFFF\", \"color\": \"#000000\"}\n",
"\n",
"polli_geo = folium.GeoJson(\n",
" polli_gdf,\n",
" tooltip=folium.GeoJsonTooltip(\n",
" fields=[\"type\"], \n",
" labels=True,\n",
" sticky=False,\n",
" ),\n",
" style_function=style_function\n",
")\n",
"\n",
"m.add_child(polli_geo)\n",
"\n",
"m.save(\"pollinators_markers.html\")\n",
"m"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding a new layer: Edinburgh Green Space Audit\n",
"\n",
"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](http://data.edinburghcouncilmaps.info/datasets/223949a6212f4068b30aa6ed8fc2e1ef_15)\n",
"\n",
"We can fetch the data in GeoJSON format from the Mapping Portal using the GeoPandas `read_file()` method."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"edinburgh_greenspaces = gpd.read_file(\n",
" \"http://data.edinburghcouncilmaps.info/datasets/223949a6212f4068b30aa6ed8fc2e1ef_15.geojson\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we look at the columns in the `GeoDataFrame`, there is quite a lot of information:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Index(['OBJECTID_1', 'OBJECTID', 'Id', 'Ownership', 'Access', 'ry_Use',\n",
" 'Shape_Leng', 'PAN65', 'Shape_Le_1', 'NP_Name', 'Np_NO', 'CLASSIFICA',\n",
" 'NAME', 'YEAROPEN', 'Comments', 'PF_Quality', 'PF_CF', 'AuditScore',\n",
" 'Area_ha', 'OS_Quality', 'PQA_Grade', 'OLD_OS_Ref', 'OS_Ref',\n",
" 'Shapearea', 'Shapelen', 'geometry'],\n",
" dtype='object')"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"edinburgh_greenspaces.columns"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, we are mainly interested in the `geometry` column, which consists of a series of shapely `Polygon` objects."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 (POLYGON ((-3.191305985701971 55.9798613048917...\n",
"1 (POLYGON ((-3.191575981920173 55.9736176672753...\n",
"2 POLYGON ((-3.192261702385649 55.9744905596996,...\n",
"3 POLYGON ((-3.189952463388474 55.97719496687115...\n",
"4 POLYGON ((-3.189397958243322 55.97560093581826...\n",
"5 POLYGON ((-3.179811420034048 55.97683150098928...\n",
"6 POLYGON ((-3.186472913977041 55.97290840346809...\n",
"7 POLYGON ((-3.180550860742277 55.97340911991013...\n",
"8 POLYGON ((-3.180143933366065 55.9741876890718,...\n",
"9 POLYGON ((-3.180884988531127 55.97478681461671...\n",
"Name: geometry, dtype: object"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"edinburgh_greenspaces['geometry'][:10]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Folium's `GeoJson()` method makes it simple to read in this data and add it to the map `m` that we created earlier."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"