Challenge view
Back to ProjecthistoricalMAP
Build interactive maps in HTML format to assess changes in species distribution through time
Projects
(2+6) Connecting the Herbarium to Wikidata around playful experiences and storytelling
This was presented as Challenge 6 at GLAMhack 2022.
Challenge
Develop a plugin to compare the distribution of (mainly historical) observations from natural history collections with approximate (mainly contemporary) data from Swiss biodiversity portals.
Problem
Swiss biodiversity portals like InfoSpecies publish most of their georeferenced data as 5km square grids in GBIF (see for example here). In order to compare these contemporary data with georeferenced historical data from natural history collections, the latter must be ‘normalised’ to the square grid system used by Swiss biodiversity portals.
Benefit
Comparisons between historical and contemporary observational data allow the assessment of changes in species distributions over time. In the context of the current biodiversity crisis, facilitating such comparisons would tremendously facilitate the identification of threats to the Swiss indigenous flora, fauna and fungi and, by implication, the actions needed to preserve them.
Case study
The vascular plant collections of the United Herbaria Z+ZT of the University (Z) and ETH Zurich (ZT) encompass about 2.5 millions herbarium specimens (see this short movie for a brief presentation of the institution). Between 2019 and 2021, all (ca. 90,000) vouchers from the canton of Valais have been imaged and publicly released in GBIF. More than half (ca. 55’000) of the specimens were transcribed and georeferenced for a total of nearly 1,200 taxa occurring in Valais. These historical data could now be compared with ongoing vegetation surveys from the FloraVS project, which are being published on GBIF via InfoFlora.
Basic approach
- Specify taxon in search field
- Extract historical data of the given taxon from the United Herbaria Z+ZT
- Extract contemporary data of the given taxon from InfoFlora
- Normalise all data to 5km square grids
- Create distribution map for the time period before and after 1980
Jump start
I suggest to use the open-source JavaScript library called Leaflet to solve this challenge. I tried it using R (instead of Java, which I do not know how to program) and herewith briefly summarise what I have already been able to achieve with a few screenshots.
Trial dataset
Distributional data of Adonis aestivalis L. from canton Valais
Link to contemporary data from InfoFlora using following filters:
- Scientific name = Adonis aestivalis L.
- Basis of record = Human observation (to avoid including data from herbaria or literature)
- Administrative areas = Valais - CHE.23_1
- Dataset = Swiss National Databank of Vascular Plants
Link to (mainly) historical data from the United Herbaria Zurich Z+ZT using following filters:
- Scientific name = Adonis aestivalis L.
- Administrative areas = Valais - CHE.23_1
- Dataset = United Herbaria of the University and ETH Zurich
R code
#############################################################################
## Script to assess changes in species distribution using interactive maps ##
#############################################################################
## Code by Alessia Guggisberg
## Created: 18.10.2022
## Last edited: 21.10.2022 by Alessia Guggisberg
## helpful links
## https://rpubs.com/huanfaChen/grid_from_polygon
## https://rstudio.github.io/leaflet/
## https://cran.r-project.org/web/packages/leaflet/leaflet.pdf
## https://leaflet-extras.github.io/leaflet-providers/preview/
## https://leanpub.com/leaflet-tips-and-tricks/read
## https://api3.geo.admin.ch/services/sdiservices.html
## https://leaflet-tilelayer-swiss.karavia.ch/layers.html#
## https://mikejohnson51.github.io/leaflet-intro/hospitals.html # to customised legends
## Initialize libraries and set working directory ######################################################
library(rgdal)
library(leaflet) # to create interactive maps
library(leaflegend)
library(htmlwidgets) # to save interactive maps as HTML
setwd("/Users/guggisberg/Documents/Alessia/Professional/Herbarium/Projekte/GLAMhack2022/Rscript/")
## Import historical Z+ZT and contemporary Infoflora data from GBIF #####################################
INFOFLORA <-read.table("Adonis_aestivalis_0101032-220831081235567/occurrence.txt", header=T, sep = "\t", quote = "\"'")
## Build raster #########################################################################################
grid_size <- 0.055
r <- raster(xmn=5.956601, ymn=45.81914, xmx=10.49347, ymx=47.80988, res=grid_size)
r[] <- 0
crs(r) <- CRS("+init=epsg:4326") # add CRS to raster
rZZT <- rasterize(ZZT[,c("decimalLongitude","decimalLatitude")], r, fun = "count")
rINFOFLORA <- rasterize(INFOFLORA[,c("decimalLongitude","decimalLatitude")], r, fun = "count")
# Retain only grids that contain a count and 'normalise' to centroids of 5km-by-5km grids ###############
rZZT[rZZT < 1] <- NA
rINFOFLORA[rINFOFLORA < 1] <- NA
cellsZZT <- which(is.na(rZZT@data@values)==F)
cellsINFOFLORA <- which(is.na(rINFOFLORA@data@values)==F)
centroidsZZT <- data.frame(xyFromCell(rZZT, cellsZZT))
centroidsINFOFLORA <- data.frame(xyFromCell(rINFOFLORA, cellsINFOFLORA))
# URLs to various Swiss maps ############################################################################
url1 <- 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/3857/{z}/{x}/{y}.jpeg' # swisstopo colour
url2 <- 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-grau/default/current/3857/{z}/{x}/{y}.jpeg' # swisstopo greyscale#
url3 <- 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swissimage/default/current/3857/{z}/{x}/{y}.jpeg' # aerial view
url4 <- 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swisstlm3d-karte-grau.3d/default/current/3857/{z}/{x}/{y}.jpeg' # topographic greyscale map
# Create leaflet map using centroids #####################################################################
addLegendCustom <- function(map, color, labels, sizes, opacity = 1, stroke, title = "Observations per grid", position = "bottomright"){ # setup customised legend to plot circles (instead of squares)
colorAdditions <- paste0(color, "; border-radius: 50%; width:", sizes, "px; height:", sizes, "px")
labelAdditions <- paste0("<div style='display: inline-block;height: ",
sizes, "px;margin-top: 4px;line-height: ", sizes, "px;'>",
labels, "</div>")
return(addLegend(map,
colors = colorAdditions,
labels = labelAdditions,
opacity = opacity,
title = title,
position = position))
}
m <- leaflet(centroidsZZT) %>%
addTiles(urlTemplate = url1, group ="Swisstopo colour") %>%
addTiles(urlTemplate = url2, group ="Swisstopo greyscale") %>%
addTiles(urlTemplate = url3, group ="Aerial view") %>%
addTiles(urlTemplate = url4, group ="Topographic view") %>%
addCircleMarkers(~ZZT$decimalLongitude, ~ZZT$decimalLatitude, fillOpacity = 1, col = c("red"), weight = 2, radius=5, group = "ZZT - precise") %>%
addCircleMarkers(~centroidsZZT$x, ~centroidsZZT$y, fillOpacity = 0.5, col = c("red"), weight = 2, radius=5, group = "ZZT - presence in grid") %>%
addCircleMarkers(~centroidsZZT$x, ~centroidsZZT$y, fillOpacity = 0.5, col = c("red"), weight = 2, radius=levels(cut(rZZT@data@values, c(1,5,10,20,100,1000), labels=c(1,3,5,10,20))), group = "ZZT - observations per grid") %>% #plot data points proportionate to number of counts per grid
addCircleMarkers(~centroidsINFOFLORA$x, ~centroidsINFOFLORA$y, fillOpacity = 0.5, col = c("blue"), weight = 2, radius=5, group = "InfoFlora - presence in grid") %>%
addCircleMarkers(~centroidsINFOFLORA$x, ~centroidsINFOFLORA$y, fillOpacity = 0.5, col = c("blue"), weight = 2, radius=levels(cut(rINFOFLORA@data@values, c(1,5,10,20,100,1000), labels=c(1,3,5,10,20))), group = "InfoFlora - observations per grid") %>%#plot data points proportionate to number of counts per grid
addLayersControl(baseGroups = c("Swisstopo colour", "Swisstopo greyscale", "Aerial view", "Topographic view"), overlayGroups = c("ZZT - precise", "ZZT - presence in grid", "ZZT - observations per grid", "InfoFlora - presence in grid", "InfoFlora - observations per grid"), options = layersControlOptions(collapsed = FALSE)) %>%
addLegend(colors = c("red","blue"), position = "bottomright", labels = c("ZZT", "InfoFlora"), opacity = 1, title = c("Dataset")) %>%
addLegendCustom(color = "grey", labels = c("1-5","6-10","11-20","21-100","101-1000"), sizes = c(3,5,10,20,40), stroke = FALSE)
m
# Save the leaflet maps as HTML objects ##################################################################
saveWidget(m, file="historicalMAP_example_Adonis_aestivalis.html")
Here's what you get:
a) Mapping of (precise) herbarium locations
b) Mapping of grids where the species is/was present (red, herbarium data; blue, InfoFlora data)
c) Mapping of grids where the species is/was present (red, herbarium data; blue, InfoFlora data); the sizes of the circles are proportionate to the number of observations recorded per grid (see legend at the bottom right)
To be implemented or solved
- The InfoFlora database entails observations dating back to the 1970s and the United Herbaria Zurich Z+ZT also possess vouchers from this millennium. Accordingly, one should implement a time slider, to facilitate temporal comparisons and accommodate data from other databases (e.g. iNaturalist).
- To better assess the reasons for putative distributional changes through time (e.g. habitat loss, urbanisation), one should integrate the historical Dufour and Siegfried swisstopo maps. Further maps (e.g. land use, vegetation type, road network) are, of course, welcome! Having the opportunity to visualise two maps (from two different time periods) side-by-side might also be useful, but maybe it would be more elegant to establish a hypothetical distribution based upon all the observations and let the internet users choose which data they want to plot against it (e.g. only data from a given dataset or a given time period).
- In order to identify putative wrong determinations, one should be able to access any images (if available) pertaining to the mapped datapoints (see examples from the United Herbaria Zurich Z+ZT or iNaturalist). Generally, implementing dynamic popups with important information pertaining to the datapoints would be a nice feature!
- Logically, the code should be setup in such a way that data are being automatically retrieved from GBIF, so that one always constructs the most current map.
- I herewith compared data of only one species (Adonis aestivalis L.), but ideally internet users should be able to customise their map based on following criteria: plant species, administrative areas (cantons), data sources (national database, herbaria, citizen science platforms), etc.
- Herbaria should be able to integrate the code into their websites.
- Export functions (with copyrights) are needed.