Skip to content

Commit

Permalink
doc: bioclim tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
tpoisot committed Oct 19, 2024
1 parent 1b7c101 commit de73d73
Showing 1 changed file with 42 additions and 19 deletions.
61 changes: 42 additions & 19 deletions docs/src/tutorials/bioclim.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# # The BIOCLIM model
# # Building the BIOCLIM model

# In this tutorial, we will build the BIOCLIM model of species distribution,
# using only basic functions from `SpeciesDistributionToolkit`.
Expand All @@ -7,23 +7,41 @@ using SpeciesDistributionToolkit
using CairoMakie
CairoMakie.activate!(; type = "png", px_per_unit = 2) #hide

# We will get the same occurrence and spatial data as in other examples in this
# documentation (*Sitta whiteheadi* in Corsica):
# ::: tip The BIOCLIM model
#
# The `SDeMo` package comes with an implementation of the BIOCLIM model that is much easier
# to use, and can actually be tuned to make predictions. This tutorial is meant to explore
# different operations on layers.
#
# :::

# ## Getting the data

spatial_extent = (left = 8.412, bottom = 41.325, right = 9.662, top = 43.060)
species = taxon("Sitta whiteheadi"; strict = false)
# We will get data on the occurrences of the New-Zealand long-tailed bat from GBIF, and then
# use data from CHELSA to estimate the climatic envelope of this species.

species = taxon("Chalinolobus tuberculatus"; strict = false)
query = [
"occurrenceStatus" => "PRESENT",
"hasCoordinate" => true,
"decimalLatitude" => (spatial_extent.bottom, spatial_extent.top),
"decimalLongitude" => (spatial_extent.left, spatial_extent.right),
"country" => "NZ",
"limit" => 300,
]
presences = occurrences(species, query...)
while length(presences) < count(presences)
occurrences!(presences)
end

# This species is endemic to New Zealand (and we limited the occurrences to this country
# anyway), so we can use the occurrence information to define a bounding box. Because the
# output of a GBIF queries uses the occurrences interface, we can simply get the latitude
# and longitude of each occurrence:

occ = filter(!ismissing, place(presences))
left, right = extrema(first.(occ))
bottom, top = extrema(last.(occ))
bbox = (; bottom, top, left, right)

# We will get our environmental variables from
# [CHELSA1](/datasets/CHELSA1#bioclim):

Expand All @@ -32,13 +50,11 @@ dataprovider = RasterData(CHELSA1, BioClim)
# The two layers we use to build this model are annual mean temperature and
# annual total precipitation:

temp = SDMLayer(dataprovider; layer = 1, spatial_extent...)
prec = SDMLayer(dataprovider; layer = 12, spatial_extent...)
temp = SDMLayer(dataprovider; layer = 1, bbox...)
prec = SDMLayer(dataprovider; layer = 12, bbox...)

#-
# ## The BIOCLIM model

# ::: details The BIOCLIM model
#
# The [BIOCLIM
# model](https://support.bccvl.org.au/support/solutions/articles/6000083201-bioclim)
# is an envelope model in which the percentile of an environmental condition in
Expand All @@ -47,17 +63,15 @@ prec = SDMLayer(dataprovider; layer = 12, spatial_extent...)
# possible value), and a species at the 1st and 99th percentile are considered
# to be equivalent. In other words, species should "prefer" their median
# environment.
#

# The score is calculated as
#

# ```math
# 2 \times (\frac{1}{2} - \|Q - \frac{1}{2} \|)
# ```
#

# where ``Q`` is the quantile for one variable at one pixel; the final score is
# the minimum value for each pixel across all variables.
#
# :::

# To calculate the scores of the BIOCLIM model, we need to get the quantiles for
# each variable by *only* considering the sites where the species is present:
Expand All @@ -82,13 +96,20 @@ current_figure() #hide
# then the transformation of the score, and finally the selection of the minimum
# value across all layers:

function BIOCLIM(layers::Vector{<:SDMLayer}, presences::T) where {T <: AbstractOccurrenceCollection}
function BC(
layers::Vector{<:SDMLayer},
presences::T,
) where {T <: AbstractOccurrenceCollection}
score = (Q) -> 2.0 .* (0.5 .- abs.(Q .- 0.5))
Q = [quantize(layer, presences) for layer in layers]
S = score.(Q)
return mosaic(minimum, S)
end

# BIOCLIM selects the minimum value to reflect the fact that an organism presence is likely
# to be prevented in areas where one of the environmental variables are far outside the
# range of observations for this species.

# ::: warning Be careful about the occurrence status!
#
# We have requested only presences from the GBIF API, but if we were to write a
Expand All @@ -97,9 +118,11 @@ end
#
# :::

# ## Making predictions

# We can now call this function to get the score at each pixel:

bc = BIOCLIM([temp, prec], presences)
bc = BC([temp, prec], presences)

# The interpretation of this score is, essentially, the most restrictive
# environmental condition found at this specific place. We can map this, and
Expand Down

0 comments on commit de73d73

Please sign in to comment.