Skip to content

Calculating climate novelty

In this tutorial, we will download historical and projected future climate data, measure the climate novelty for each pixel, and provide a map showing this climate novelty.

julia
using SpeciesDistributionToolkit
julia
using Statistics
using CairoMakie

Accessing future data involves the Dates standard library, to make an explicit reference to years in the dataset.

julia
import Dates

Accessing historical climate data

In order to only load a reasonable amount of data, we will specify a bounding box for the area we are interested in:

julia
spatial_extent = (left = 23.42, bottom = 34.75, right = 26.41, top = 35.74)
(left = 23.42, bottom = 34.75, right = 26.41, top = 35.74)

Note that the bounding box is given in WGS84. Although layers can use any projection, we follow the GeoJSON specification and use WGS84 for point data. This includes species occurrence, and all polygons.

Finding bounding boxes

The bboxfinder website is a really nice tool to rapidly draw and get the coordinates for a bounding box.

We will get the BioClim data from CHELSA v1. CHELSA v1 offers access to the 19 original bioclim variable, and their projection under a variety of CMIP5 models/scenarios. These are pretty large data, and so this operation may take a while in terms of download/read time. The first time you run this command will download the data, and the next calls will read them from disk.

julia
dataprovider = RasterData(CHELSA1, BioClim)
RasterData{CHELSA1, BioClim}(CHELSA1, BioClim)

We can search the layer that correspond to annual precipitation and annual mean temperature in the list of provided layers:

julia
layers_code = findall(
    v -> contains(v, "Annual Mean Temperature") | contains(v, "Annual Precipitation"),
    layerdescriptions(dataprovider),
)
2-element Vector{String}:
 "BIO12"
 "BIO1"

The first step is quite simply to grab the reference state for the annual precipitation, by specifying the layer and the spatial extent:

julia
historical = [SDMLayer(dataprovider; layer = l, spatial_extent...) for l in layers_code];

Although we specificed a bounding box, the entire layer has been downloaded, so if we want to use it in another area, it will simply be read from the disk, which will be much faster.

We can have a little look at this dataset by checking the density of the values for the first layer (we can pass a layer to a Makie function directly):

Code for the figure
julia
f, ax, plt = hist(
    historical[1]; color = (:grey, 0.5),
    figure = (; size = (800, 300)),
    axis = (; xlabel = layerdescriptions(dataprovider)[layers_code[1]]),
    bins = 100,
)
ylims!(ax; low = 0)
hideydecorations!(ax)

Accessing future climate data

In the next step, we will download the projected climate data under RCP85. This requires setting up a projection object, which is composed of a scenario and a model. This information is used by the package to verify that this combination exists within the dataset we are working with.

julia
projection = Projection(RCP85, IPSL_CM5A_MR)
Projection{RCP85, IPSL_CM5A_MR}(RCP85, IPSL_CM5A_MR)

Future data are available for different years, so we will take a look at what years are available:

julia
available_timeperiods = SimpleSDMDatasets.timespans(dataprovider, projection)
2-element Vector{Pair{Year, Year}}:
 Year(2041) => Year(2060)
 Year(2061) => Year(2080)

Available scenarios and models

This website has a full description of each dataset, accessible from the top-level menu, which will list the scenarios, layers, models, etc., for each dataset.

If we do not specify an argument, the data retrieved will be the ones for the closest timespan. Getting the projected temperature is the same call as before, except we now pass an additional argument – the projection.

julia
projected = [
    SDMLayer(
        dataprovider,
        projection; 
        layer = l,
        spatial_extent...,
        timespan = last(available_timeperiods),
    ) for l in layers_code
];

Re-scaling the variables

In order to compare the variables, we will first re-scale them so that they have mean zero and unit variance. More accurately, because we want to compare the historical and projected data, we will use the mean and standard deviation of the historical data as the baseline:

julia
μ = mean.(historical)
σ = std.(historical)
2-element Vector{Float64}:
 135.6347881772658
  26.893110881710868
julia
cr_historical = (historical .- μ) ./ σ;
julia
cr_projected = (projected .- μ) ./ σ;

Note that these operations are applied to historical and projected, which are both vectors of layers. This is because we can broadcast operations on layers as if they were regular Julia arrays.

Measuring climate novelty

We will use a simple measure of climate novelty, which is defined as the smallest Euclidean distance between a cell in the raster and all the possible cells in the future raster.

To have a way to store the results, we will start by making a copy of one of the input rasters:

julia
Δclim = similar(cr_historical[1])
SDM Layer with 12834 Float64 cells
	Proj string: +proj=longlat +datum=WGS84 +no_defs
	Grid size: (119, 360)

And then, we can loop over the positions to find the minimum distance. To speed up the calculation, we will store the values of the projected layers in their own objects first:

julia
vals = values.(cr_projected)

for position in keys(cr_historical[1])
    dtemp = (cr_historical[1][position] .- vals[1]) .^ 2.0
    dprec = (cr_historical[2][position] .- vals[2]) .^ 2.0
    Δclim[position] = minimum(sqrt.(dtemp .+ dprec))
end

Because we have stored the information about the smallest possible distance directly inside the raster, we can plot it. Large values on this map indicate that this area will experience more novel climatic conditions under the scenario/model we have specified.

Code for the figure
julia
fig, ax, hm = heatmap(
    Δclim;
    colormap = :lipari,
    figure = (; size = (800, 400)),
    axis = (; aspect = DataAspect()),
)
Colorbar(fig[:, end + 1], hm)