historicalMAP

Build interactive maps in HTML format to assess changes in species distribution through time

⛶  Open fullscreen

This content is a preview from an external site.
 

Projects

Living Herbarium
GLAMhack 2022 Clean

(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

  1. Specify taxon in search field
  2. Extract historical data of the given taxon from the United Herbaria Z+ZT
  3. Extract contemporary data of the given taxon from InfoFlora
  4. Normalise all data to 5km square grids
  5. 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

 Title

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

 Title

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  Title

b) Mapping of grids where the species is/was present (red, herbarium data; blue, InfoFlora data)  Title

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)  Title

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.

Event finished

05.11.2022 16:30

We met with the expert, formed a team, and brainstormed possible designs. We are super motivated to build on this data.

04.11.2022 16:14 ~ loleg

Event started

04.11.2022 09:00

Ask

03.11.2022 13:45

Edited content version 186

03.11.2022 13:45 ~ jonaslendenmann

Edited content version 183

03.11.2022 08:37 ~ loleg

Edited content version 179

26.10.2022 06:18 ~ AlessiaGuggisberg

Edited content version 177

26.10.2022 06:14 ~ AlessiaGuggisberg

Edited content version 175

26.10.2022 05:31 ~ AlessiaGuggisberg

Edited content version 173

26.10.2022 05:30 ~ AlessiaGuggisberg

Edited content version 171

26.10.2022 05:30 ~ AlessiaGuggisberg

Edited content version 169

26.10.2022 05:29 ~ AlessiaGuggisberg

Edited content version 167

26.10.2022 05:29 ~ AlessiaGuggisberg

Edited content version 165

26.10.2022 05:28 ~ AlessiaGuggisberg

Edited content version 163

26.10.2022 05:27 ~ AlessiaGuggisberg

Edited content version 161

26.10.2022 05:26 ~ AlessiaGuggisberg

Edited content version 159

26.10.2022 05:26 ~ AlessiaGuggisberg

Edited content version 157

26.10.2022 05:25 ~ AlessiaGuggisberg

Edited content version 155

26.10.2022 05:25 ~ AlessiaGuggisberg

Edited content version 153

26.10.2022 05:25 ~ AlessiaGuggisberg

Edited content version 151

26.10.2022 05:23 ~ AlessiaGuggisberg

Edited content version 149

26.10.2022 05:22 ~ AlessiaGuggisberg

Edited content version 147

26.10.2022 05:22 ~ AlessiaGuggisberg

Edited content version 145

26.10.2022 05:20 ~ AlessiaGuggisberg

Edited content version 143

26.10.2022 05:20 ~ AlessiaGuggisberg

Edited content version 141

26.10.2022 05:19 ~ AlessiaGuggisberg

Edited content version 139

26.10.2022 05:18 ~ AlessiaGuggisberg

Edited content version 137

21.10.2022 09:02 ~ AlessiaGuggisberg

Edited content version 135

21.10.2022 09:00 ~ AlessiaGuggisberg

Edited content version 133

21.10.2022 08:56 ~ AlessiaGuggisberg

Edited content version 131

21.10.2022 08:47 ~ AlessiaGuggisberg

Edited content version 129

21.10.2022 08:41 ~ AlessiaGuggisberg

Edited content version 127

21.10.2022 05:12 ~ AlessiaGuggisberg

Edited content version 125

21.10.2022 04:54 ~ AlessiaGuggisberg

Edited content version 123

20.10.2022 05:34 ~ AlessiaGuggisberg

Edited content version 120

20.10.2022 05:33 ~ AlessiaGuggisberg

Edited content version 118

20.10.2022 05:32 ~ AlessiaGuggisberg

Edited content version 116

20.10.2022 05:30 ~ AlessiaGuggisberg

Edited content version 114

20.10.2022 05:29 ~ AlessiaGuggisberg

Edited content version 112

19.10.2022 11:45 ~ AlessiaGuggisberg

Edited content version 110

19.10.2022 11:42 ~ AlessiaGuggisberg

Edited content version 108

19.10.2022 11:33 ~ AlessiaGuggisberg

Edited content version 106

19.10.2022 11:26 ~ AlessiaGuggisberg

Edited content version 104

19.10.2022 11:06 ~ AlessiaGuggisberg

Edited content version 102

19.10.2022 11:06 ~ AlessiaGuggisberg

Edited content version 100

19.10.2022 11:04 ~ AlessiaGuggisberg

Edited content version 98

19.10.2022 10:00 ~ AlessiaGuggisberg

Edited content version 96

19.10.2022 10:00 ~ AlessiaGuggisberg

Edited content version 91

19.10.2022 09:55 ~ AlessiaGuggisberg

Edited content version 89

19.10.2022 09:52 ~ AlessiaGuggisberg

Edited content version 87

19.10.2022 09:43 ~ AlessiaGuggisberg

Edited content version 85

19.10.2022 09:39 ~ AlessiaGuggisberg

Edited content version 83

19.10.2022 09:36 ~ AlessiaGuggisberg

Edited content version 81

19.10.2022 09:36 ~ AlessiaGuggisberg

Edited content version 79

19.10.2022 09:35 ~ AlessiaGuggisberg

Edited content version 77

19.10.2022 09:31 ~ AlessiaGuggisberg

Edited content version 75

19.10.2022 09:29 ~ AlessiaGuggisberg

Edited content version 73

19.10.2022 09:27 ~ AlessiaGuggisberg

Edited content version 71

19.10.2022 09:20 ~ AlessiaGuggisberg

Edited content version 69

19.10.2022 09:20 ~ AlessiaGuggisberg

Edited content version 67

19.10.2022 09:18 ~ AlessiaGuggisberg

Edited content version 65

19.10.2022 09:18 ~ AlessiaGuggisberg

Edited content version 63

19.10.2022 09:16 ~ AlessiaGuggisberg

Edited content version 61

19.10.2022 09:15 ~ AlessiaGuggisberg

Edited content version 59

19.10.2022 09:12 ~ AlessiaGuggisberg

Edited content version 57

19.10.2022 09:11 ~ AlessiaGuggisberg

Edited content version 55

19.10.2022 09:09 ~ AlessiaGuggisberg

Edited content version 53

19.10.2022 09:04 ~ AlessiaGuggisberg

Edited content version 51

19.10.2022 06:44 ~ AlessiaGuggisberg

Edited content version 49

19.10.2022 06:28 ~ AlessiaGuggisberg

Edited content version 47

18.10.2022 06:53 ~ AlessiaGuggisberg

Edited content version 42

18.10.2022 06:48 ~ AlessiaGuggisberg

Edited content version 39

18.10.2022 06:39 ~ AlessiaGuggisberg

Edited content version 35

18.10.2022 06:15 ~ AlessiaGuggisberg

Edited content version 33

18.10.2022 06:04 ~ AlessiaGuggisberg

Edited content version 31

18.10.2022 05:55 ~ AlessiaGuggisberg

Edited content version 28

18.10.2022 05:52 ~ AlessiaGuggisberg

Edited content version 24

18.10.2022 05:51 ~ AlessiaGuggisberg

Edited content version 22

18.10.2022 05:49 ~ AlessiaGuggisberg

Edited content version 20

18.10.2022 05:49 ~ AlessiaGuggisberg

Edited content version 18

18.10.2022 05:47 ~ AlessiaGuggisberg

Edited content version 13

18.10.2022 05:46 ~ AlessiaGuggisberg

Edited content version 11

18.10.2022 05:45 ~ AlessiaGuggisberg

Edited content version 8

18.10.2022 05:39 ~ AlessiaGuggisberg

Edited content version 6

18.10.2022 05:39 ~ AlessiaGuggisberg

Repository updated

04.10.2022 16:31 ~ AlessiaGuggisberg

Joined the team

04.10.2022 16:31 ~ AlessiaGuggisberg

Challenge posted

04.10.2022 15:08 ~ AlessiaGuggisberg
 
Contributed 1 year ago by AlessiaGuggisberg for GLAMhack 2022
All attendees, sponsors, partners, volunteers and staff at our hackathon are required to agree with the Hack Code of Conduct. Organisers will enforce this code throughout the event. We expect cooperation from all participants to ensure a safe environment for everybody.

Creative Commons LicenceThe contents of this website, unless otherwise stated, are licensed under a Creative Commons Attribution 4.0 International License.

GLAMhack 2022