rm(list = ls())
library(dplyr) # data wrangling
library(tidyr) # data wrangling
library(ggplot2) # data visualisation
library(sf) # simple features - geospatial geometries
library(osmdata) # obtaining OpenStreetMap vector data
library(units) # working with units
library(mapview) # interactive geometry viewing
library(ggmap) # downloading raster maps from a variety of sources
library(ggspatial) # map backgrounds and annotations for ggplot
library(tmap) # static/interactive map library with ggplot-like syntaxgreensp_sf <- readRDS("greensp_sf.rds")head(greensp_sf)## Simple feature collection with 6 features and 3 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -3.388398 ymin: 55.90319 xmax: -3.140106 ymax: 55.98513
## Geodetic CRS: WGS 84
## name osm_id leisure
## 4271400 Dundas Park 4271400 park
## 4288119 Harrison Park East 4288119 park
## 4348244 Bruntsfield Links 4348244 park
## 4891768 Baberton Golf Course 4891768 golf_course
## 4891786 Kingsknowe Golf Course 4891786 golf_course
## 4892551 <NA> 4892551 <NA>
## geometry
## 4271400 MULTIPOLYGON (((-3.387083 5...
## 4288119 MULTIPOLYGON (((-3.224193 5...
## 4348244 MULTIPOLYGON (((-3.203498 5...
## 4891768 MULTIPOLYGON (((-3.28719 55...
## 4891786 MULTIPOLYGON (((-3.265052 5...
## 4892551 MULTIPOLYGON (((-3.146338 5...
unique(greensp_sf$leisure)## [1] "park" "golf_course" NA "nature_reserve"
st_crs(greensp_sf)## Coordinate Reference System:
## User input: EPSG:4326
## wkt:
## GEOGCRS["WGS 84",
## DATUM["World Geodetic System 1984",
## ELLIPSOID["WGS 84",6378137,298.257223563,
## LENGTHUNIT["metre",1]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433]],
## CS[ellipsoidal,2],
## AXIS["geodetic latitude (Lat)",north,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433]],
## AXIS["geodetic longitude (Lon)",east,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433]],
## USAGE[
## SCOPE["unknown"],
## AREA["World"],
## BBOX[-90,-180,90,180]],
## ID["EPSG",4326]]
Let’s transform our data into the British National Grid CRS, using it’s ESPG number (27700):
greensp_sf <- st_transform(greensp_sf, 27700)st_crs(greensp_sf)## Coordinate Reference System:
## User input: EPSG:27700
## wkt:
## PROJCRS["OSGB36 / British National Grid",
## BASEGEOGCRS["OSGB36",
## DATUM["Ordnance Survey of Great Britain 1936",
## ELLIPSOID["Airy 1830",6377563.396,299.3249646,
## LENGTHUNIT["metre",1]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433]],
## ID["EPSG",4277]],
## CONVERSION["British National Grid",
## METHOD["Transverse Mercator",
## ID["EPSG",9807]],
## PARAMETER["Latitude of natural origin",49,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8801]],
## PARAMETER["Longitude of natural origin",-2,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8802]],
## PARAMETER["Scale factor at natural origin",0.9996012717,
## SCALEUNIT["unity",1],
## ID["EPSG",8805]],
## PARAMETER["False easting",400000,
## LENGTHUNIT["metre",1],
## ID["EPSG",8806]],
## PARAMETER["False northing",-100000,
## LENGTHUNIT["metre",1],
## ID["EPSG",8807]]],
## CS[Cartesian,2],
## AXIS["(E)",east,
## ORDER[1],
## LENGTHUNIT["metre",1]],
## AXIS["(N)",north,
## ORDER[2],
## LENGTHUNIT["metre",1]],
## USAGE[
## SCOPE["Engineering survey, topographic mapping."],
## AREA["United Kingdom (UK) - offshore to boundary of UKCS within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain (England, Wales and Scotland). Isle of Man onshore."],
## BBOX[49.75,-9,61.01,2.01]],
## ID["EPSG",27700]]
Calculating the area of polygon or multipolygon geometries is done using the st_area() function:
# Create and calculate a new column for feature area
greensp_sf <- mutate(greensp_sf, area = st_area(greensp_sf))
head(greensp_sf$area)## Units: [m^2]
## [1] 29515.82 29073.37 150754.53 477437.67 398419.52 313020.65
# convert m2 to hectares
greensp_sf <- greensp_sf %>%
mutate(area_ha = set_units(area, "ha")) %>%
select(-area) # drop "area" column mapview(greensp_sf)# Filter out unneeded shapes
greensp_sf <-
greensp_sf %>%
filter(is.na(leisure) == FALSE) %>%
# remove leisure NAs
rename(greensp_type = leisure) %>%
# rename leisure to greensp_type
st_make_valid()
# a good function to use after importing data to make sure shapes are valid# Remove green spaces with <2 ha
greensp_sf <- filter(greensp_sf, as.numeric(area_ha) >= 2)mapview(greensp_sf)# Separate into a list of multiple sf grouped by type
greensp_sf_list <- greensp_sf %>% split(.$greensp_type)
# Each sf object in the list can be accessed using the $ operator.
# E.g. greensp_sf_list$nature_reserve to get the sf object
# containing only nature reserves.Using st_difference(x, y)
# Remove the parts of parks where they are overlapped by nature reserves
greensp_sf_list$park <- st_difference(greensp_sf_list$park,
st_union(greensp_sf_list$nature_reserve))## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
# Remove the parts of parks where they are overlapped by golf courses
greensp_sf_list$park <- st_difference(greensp_sf_list$park,
st_union(greensp_sf_list$golf_course))## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
# Let's turn the list back into one sf object. We will also need to re-calculate area!
greensp_sf <- bind_rows(greensp_sf_list) %>%
# bind the list into one sf object
mutate(area_ha = set_units(st_area(.), "ha")) %>%
# calculate area again
filter(as.numeric(area_ha) >= 2) # remove area < 2 ha again# Reorder greenspace types, capitalise, remove underscores
greensp_sf_forplot <-
mutate(greensp_sf,
greensp_type = factor(greensp_type,
levels = c("park", "nature_reserve", "golf_course"),
labels = c("Park", "Nature reserve", "Golf course")))# Get the rectangular bounding box
city_rect <- getbb("City of Edinburgh", featuretype = "settlement")
stamen_raster <- get_stamenmap(city_rect, zoom = 12)# Plot map with ggplot
(edi_greenspaces_map <-
ggplot(data = greensp_sf_forplot) +
inset_ggmap(stamen_raster) + # add ggmap background
geom_sf(aes(fill = greensp_type)) + # add sf shapes, coloured by greensp_type
coord_sf(crs = st_crs(4326), expand = FALSE) +
# change the CRS of the sf back to WGS84 to match the ggmap raster
scale_fill_manual(values = c("#44AA99", "#117733", "#AA4499")) +
# add custom colours from Tol palette (colourblind-friendly)
labs(title = "Green spaces in Edinburgh, Scotland",
subtitle = "Parks, nature reserves and golf courses > 2 ha\n",
caption = paste0("Map tiles by Stamen Design (stamen.com), CC BY 3.0. ",
"http://creativecommons.org/licenses/by/3.0\n",
"Map data © OpenStreetMap contributors, ODbL. ",
"http://www.openstreetmap.org/copyright")) +
# add various labels
annotation_scale(location = "bl") + # ggspatial scale on bottom left
annotation_north_arrow(location = "tr") + # ggspatial arrow on top right
theme_void() + # get rid of axis ticks, titles
theme(legend.title = element_blank(),
legend.position = c(.98, .02),
legend.justification = c("right", "bottom"),
legend.box.just = "right",
legend.box.background = element_rect(fill = "white", colour = "gray"),
legend.margin = margin(6, 6, 6, 6),
# move legend to bottom right and customise
plot.margin = margin(12,12,12,12))
# add margin around plot
)ggsave("/Users/admin/Desktop/Linh Data Studio/Spatial Data/Spatial Modelling/edi_greenspaces_map.png", edi_greenspaces_map, width = 8, height = 6.5)# Plot interactively with tmap
tmap_mode("view") # interactive mode
(edi_greenspace_tmap <-
tm_basemap("Stamen.Terrain") + # add Stamen Terrain basemap
tm_shape(greensp_sf_forplot) + # add the sf
tm_sf(col = "greensp_type", # colour by green space type
title = "", # no legend title
palette = c("#44AA99", "#117733", "#AA4499"), # custom fill colours
popup.vars = c("Area " = "area_ha"), # customise popup to show area
popup.format = list(digits=1)) + # limit area to 1 decimal digit
tm_scale_bar() # add scale bar
)tmap_save(tm = edi_greenspace_tmap, filename = "/Users/admin/Desktop/Linh Data Studio/Spatial Data/Spatial Modelling/edi_greenspace_tmap.html")Calculate the total area of each type of green space. Please note that we can’t sum the areas we calculated as there are overlapping polygons within the green space types.
(greensp_type_area <-
lapply(greensp_sf_list,
function(x) set_units(st_area(st_union(x)), "ha")) %>% # apply the function to each element of the list using lapply()
as.data.frame() %>%
pivot_longer(cols = everything(), names_to = "greensp_type",
values_to = "area_ha"))## # A tibble: 3 × 2
## greensp_type area_ha
## <chr> [ha]
## 1 golf_course 907.
## 2 nature_reserve 290.
## 3 park 1387.