div.ProseMirror

SimpleSDMLayers.jl - JuliaCon 2020 demo

We present SimpleSDMLayers.jl, a package offering a basis for species distribution modelling (SDM) in Julia.

This brief demo is a companion to our JuliaCon 2020 poster, available here. We will quickly go over the features presented in the poster, and show how easy SimpleSDMLayers.jl is to use.

A script version of the demo is also available in this repository, which also contains all resources used for the poster.

For more details and advanced examples, check out our complete documentation.

Setup

Before getting started, we need to install the two core packages for the demo: SimpleSDMLayers.jl and GBIF.jl.

] add SimpleSDMLayers GBIF
51.4s
Julia

We then have to load them, along with a few supporting packages (already installed in NextJournal). Note that this may take a while to precompile the first time we use the packages.

using SimpleSDMLayers
using GBIF
using Plots
using StatsPlots
using Statistics
using StatsBase
122.2s
Julia

1 - Basic example

SimpleSDMLayers.jl makes it very easy to access climate and land cover data from sources that are frequently used in SDM studies.

For example, accessing average annual temperature data from the WorldClim 2.1 database can be done in just one call (note that it has to download and extract the data when first called):

# Get world temperature
temperature = worldclim(1)
22.7s
Julia
SimpleSDMPredictor{Float32}(Union{Nothing, Float32}[-31.0171 -31.6215 … -32.8125 -31.6203; -30.3919 -31.6348 … -32.8101 -30.9953; … ; nothing nothing … nothing nothing; nothing nothing … nothing nothing], -180.0, 180.0, -89.8333, 89.8333)

The SimpleSDMPredictor type is the core of the package. The layer data is stored in a grid, easy to manipulate with any function working on Array elements:

temperature.grid
1.7s
Julia

And the layer is defined by it's bounding coordinates:

temperature.left, temperature.right, temperature.bottom, temperature.top
0.7s
Julia
(-180.0, 180.0, -89.8333, 89.8333)

As displayed on the poster, SimpleSDMLayers.jl makes it incredibly simple to visualize the data:

# Map temperature
plot(temperature)
# Add some labels
plot!(xguide = "Longitude", yguide = "Latitude", colorbar_title = "Average temperature (°C)")
10.6s
Julia

2 - Common manipulations

SimpleSDMLayers.jl provides support for common manipulations in SDM workflows.

For example, we can clip a layer to a region of interest simply by providing the bounding coordinates:

# Coordinates clipping
temperature_europe = temperature[left = -11.2, right = 30.6, bottom = 29.2, top = 71.0]
0.2s
Julia
SimpleSDMPredictor{Float32}(Union{Nothing, Float32}[nothing nothing … 21.4432 21.5599; nothing nothing … 21.5678 21.5185; … ; nothing nothing … nothing nothing; nothing nothing … nothing nothing], -11.3333, 30.6667, 29.1127, 71.0349)

This returns a smaller layer, which we can plot separately:

plot(temperature_europe, frame = :box, ticks = false, colorbar = false)
1.9s
Julia

Another useful feature we might want to try is to perform sliding window operations. For instance, we might want to get the average temperature value in a 100 km radius:

averaged = slidingwindow(temperature_europe, Statistics.mean, 100.0)
409.0s
Julia
SimpleSDMPredictor{Any}(Any[nothing nothing … 20.6183 20.77; nothing nothing … 20.6141 20.723; … ; nothing nothing … nothing nothing; nothing nothing … nothing nothing], -11.3333, 30.6667, 29.1127, 71.0349)
plot(averaged, frame = :box, ticks = false, colorbar = false)
3.5s
Julia

Finally, we can also coarsen the layer resolution (i.e. aggregating grid cells) based on a given function:

coarsened = coarsen(temperature_europe, Statistics.mean, (4,4))
0.4s
Julia
SimpleSDMPredictor{Float32}(Union{Nothing, Float32}[nothing 19.0173 … 20.1159 20.7778; nothing nothing … 20.3862 20.707; … ; nothing nothing … -0.308302 0.0751787; nothing nothing … -0.229659 -0.0535543], -11.3333, 30.6667, 29.1127, 71.0349)
plot(coarsened, frame = :box, ticks = false, colorbar = false)
0.9s
Julia

3 - Integration with GBIF.jl

What makes SimpleSDMLayers.jl even more interesting is its integration with another package from EcoJulia, GBIF.jl. GBIF is a database of species occurrences, and is a common source of data for SDM studies.

For example, let's say we are interested in the Belted Kingfisher. Getting occurrences in very simple:

# Specify taxon
kingfisher = GBIF.taxon("Megaceryle alcyon", strict=true)
# Get occurrences
kf_occurrences = occurrences(kingfisher)
18.9s
Julia
GBIF records: downloaded 20 out of 100000

The GBIF API returns 20 occurrences at the time. Let's add a few more:

# Get some more occurrences
for i in 1:9
  occurrences!(kf_occurrences)
end
kf_occurrences
2.0s
Julia
GBIF records: downloaded 200 out of 100000

Now that we have the observations, we could want to plot them to visualize where the species was seen. But first, an useful trick will be to clip our world-scale temperature layer and to restrict it to the region where the species was actually seen.

temperature_clip = clip(temperature, kf_occurrences)
4.1s
Julia
SimpleSDMPredictor{Float32}(Union{Nothing, Float32}[nothing nothing … 25.8376 25.7509; nothing nothing … 25.8436 25.783; … ; -4.01707 -4.39457 … -5.40037 -5.50537; -2.43286 -3.47303 … -5.48641 -4.01971], -137.833, -64.0, 5.98889, 59.8889)

Now we can start by plotting the temperature values as a background. Note that we can use more specific functions, such as contour():

contour(temperature_clip, fill=true, colorbar_title = "Average temperature(°C)", xguide = "Longitude", yguide = "Latitude")
2.6s
Julia

The longitude() and latitudes() allow us to extract the coordinates from the GBIF occurrences:

longitudes(kf_occurrences)
1.4s
Julia
200-element Array{Float64,1}: -81.476 -118.681 -81.4749 -122.456 -96.5409 -77.2353 -82.2618 -117.142 -122.254 -94.7266 ⋮ -122.023 -81.193 -122.229 -84.12 -122.279 -114.49 -121.508 -76.8468 -94.7263

Hence, the occurrences can be mapped simply by scattering them over the temperature layer:

scatter!(longitudes(kf_occurrences), latitudes(kf_occurrences), 
         label = "Kingfisher occurrences", legend = :bottomleft, 
         c=:white, msc=:orange)
4.3s
Julia

4 - Workflow example

SimpleSDMLayers.jl is a base on which you can easily build your own SDM workflows. Here is an example with our own implementation of the BIOCLIM model:

bioclim_path = tempname()
download("https://raw.githubusercontent.com/gabrieldansereau/juliacon-2020-poster/master/bioclim.jl", bioclim_path)
include(bioclim_path)
2.8s
Julia
bioclim (generic function with 1 method)

First, we will get the 19 WorldClim variables (again, it might have to download and extract the data):

# Get all worldclim variables
vars = worldclim(1:19);
84.5s
Julia

Then we can apply our the model in a few steps:

# Get predictions for each variable
vars_predictions = [bioclim(v, kf_occurrences) for v in vars];
3.5s
Julia
# Get minimum prediction per site
sdm_predictions = reduce(min, vars_predictions)
1.6s
Julia
SimpleSDMResponse{Float32}(Union{Nothing, Float32}[0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; nothing nothing … nothing nothing; nothing nothing … nothing nothing], -180.0, 180.0, -89.8333, 89.8333)
# Set value to nothing if prediction is zero
replace!(x -> isnothing(x) || iszero(x) ? nothing : x, sdm_predictions.grid);
0.2s
Julia

The result is also a layer, which can be visualized as before:

# Map plain background
plot(temperature_clip, c = :lightgrey,
     xguide = "Longitude", yguide = "Latitude")
# Map predictions
plot!(clip(sdm_predictions, kf_occurrences), c = :viridis, 
      clim = (minimum(sdm_predictions), maximum(sdm_predictions)),
      colorbar_title = "Predicted suitability score")
22.8s
Julia

Resources

For more information, have a look at these other resources:

Runtimes (1)