-
Jannes Breier authoredJannes Breier authored
ecorisk.R 124.32 KiB
# written by Fabian Stenzel, based on work by Sebastian Ostberg
# 2022-2023 - stenzel@pik-potsdam.de
################# EcoRisk calc functions ###################
#' Wrapper for calculating the ecosystem change metric EcoRisk
#'
#' Function to read in data for ecorisk, and call the calculation function once,
#' if overtime is FALSE, or for each timeslice of length window years, if
#' overtime is TRUE
#'
#' @param path_ref folder of reference run
#' @param path_scen folder of scenario run
#' @param read_saved_data whether to read in previously saved data
#' (default: FALSE)
#' @param save_data file to save read in data to (default NULL)
#' @param save_ecorisk file to save EcoRisk data to (default NULL)
#' @param nitrogen include nitrogen outputs for pools and fluxes into EcoRisk
#' calculation (default FALSE)
#' @param weighting apply "old" (Ostberg-like), "new", or "equal" weighting of
#' vegetation_structure_change weights (default "equal")
#' @param varnames data.frame with names of output files (outname) and time res.
#' (timestep) -- can be specified to account for variable file names
#' (default NULL -- standard names as below)
#' @param time_span_reference vector of years to use as scenario period
#' @param time_span_scenario vector of years to use as scenario period
#' @param dimensions_only_local flag whether to use only local change component
#' for water/carbon/nitrogen fluxes and pools, or use an average of
#' local change, global change and ecosystem balance (default FALSE)
#' @param overtime logical: calculate ecorisk as time-series? (default: FALSE)
#' @param window integer, number of years for window length (default: 30)
#' @param debug write out all nitrogen state variables (default FALSE)
#'
#' @return list data object containing arrays of ecorisk_total,
#' vegetation_structure_change, local_change, global_importance,
#' ecosystem_balance, carbon_stocks, carbon_fluxes, water_fluxes
#' (+ nitrogen_stocks and nitrogen_fluxes)
#'
#' @export
ecorisk_wrapper <- function(path_ref,
path_scen,
read_saved_data = FALSE,
save_data = NULL,
save_ecorisk = NULL,
nitrogen = TRUE,
weighting = "equal",
varnames = NULL,
time_span_reference,
time_span_scenario,
dimensions_only_local = FALSE,
overtime = FALSE,
window = 30,
debug = FALSE,
external_variability = FALSE,
c2vr = NULL) {
# TODO: compare length time_span_reference and time_span_scenario
if (is.null(varnames)) {
message("variable name list not provided, using standard list, which might
not be applicable for this case ...")
varnames <- data.frame(
row.names = c(
"grid", "fpc", "fpc_bft", "cftfrac", "firec", "npp", "runoff",
"transp", "vegc", "firef", "rh", "harvestc", "rharvestc",
"pft_harvestc", "pft_rharvestc", "evap", "interc", "discharge",
"soilc", "litc", "swc", "vegn", "soilnh4", "soilno3",
"leaching", "n2o_denit", "n2o_nit", "n2_emis", "bnf",
"n_volatilization", "gpp", "res_storage", "lakevol", "ndepos",
"rd", "prec", "terr_area", "irrig", "nfert_agr", "nmanure_agr",
"firen", "harvestn", "rivervol", "irrig_stor"
),
outname = c(
"grid.bin.json", "fpc.bin.json", "fpc_bft.bin.json",
"cftfrac.bin.json", "firec.bin.json", "npp.bin.json",
"runoff.bin.json", "transp.bin.json", "vegc.bin.json",
"firef.bin.json", "rh.bin.json", "harvestc.bin.json",
"rharvestc.bin.json", "pft_harvest.pft.bin.json",
"pft_rharvest.pft.bin.json", "mevap.bin.json",
"interc.bin.json", "discharge.bin.json", "soilc.bin.json",
"litc.bin.json", "swc.bin.json", "vegn.bin.json",
"soilnh4.bin.json", "soilno3.bin.json", "mleaching.bin.json",
"n2o_denit.bin.json", "n2o_nit.bin.json", "n2_emis.bin.json",
"bnf.bin.json", "n_volatilization.bin.json", "gpp.bin.json",
"res_storage.bin.json", "lakevol.bin.json", "ndepos.bin.json",
"rd.bin.json", "mprec.bin.json", "terr_area.bin.json",
"irrig.bin.json", "nfert_agr.bin.json", "nmanure_agr.bin.json",
"firen.bin.json", "harvestn.bin.json", "rivervol.bin.json",
"irrig_stor.bin.json"
),
timestep = rep("Y", 44)
)
}
nyears <- length(time_span_reference)
nyears_scen <- length(time_span_scenario)
if (nyears < 30 || nyears_scen < 30) {
stop("Timespan in reference or scenario is smaller than 30 years.")
}
# translate varnames and folders to files_scenarios/reference lists
files_scenario <- list(
grid = paste0(path_scen, varnames["grid", "outname"]),
fpc = paste0(path_scen, varnames["fpc", "outname"]),
fpc_bft = paste0(path_scen, varnames["fpc_bft", "outname"]),
cftfrac = paste0(path_scen, varnames["cftfrac", "outname"]),
firec = paste0(path_scen, varnames["firec", "outname"]),
npp = paste0(path_scen, varnames["npp", "outname"]),
runoff = paste0(path_scen, varnames["runoff", "outname"]),
transp = paste0(path_scen, varnames["transp", "outname"]),
vegc = paste0(path_scen, varnames["vegc", "outname"]),
firef = paste0(path_scen, varnames["firef", "outname"]),
rh = paste0(path_scen, varnames["rh", "outname"]),
harvestc = paste0(path_scen, varnames["harvestc", "outname"]),
rharvestc = paste0(path_scen, varnames["rharvestc", "outname"]),
pft_harvestc = paste0(path_scen, varnames["pft_harvest", "outname"]),
pft_rharvestc = paste0(path_scen, varnames["pft_rharvest", "outname"]),
evap = paste0(path_scen, varnames["evap", "outname"]),
interc = paste0(path_scen, varnames["interc", "outname"]),
discharge = paste0(path_scen, varnames["discharge", "outname"]),
soilc = paste0(path_scen, varnames["soilc", "outname"]),
litc = paste0(path_scen, varnames["litc", "outname"]),
swc = paste0(path_scen, varnames["swc", "outname"]),
swc_vol = paste0(path_scen, varnames["swc_vol", "outname"]),
vegn = paste0(path_scen, varnames["vegn", "outname"]),
soilnh4 = paste0(path_scen, varnames["soilnh4", "outname"]),
soilno3 = paste0(path_scen, varnames["soilno3", "outname"]),
leaching = paste0(path_scen, varnames["leaching", "outname"]),
n2o_denit = paste0(path_scen, varnames["n2o_denit", "outname"]),
n2o_nit = paste0(path_scen, varnames["n2o_nit", "outname"]),
n2_emis = paste0(path_scen, varnames["n2_emis", "outname"]),
bnf = paste0(path_scen, varnames["bnf", "outname"]),
n_volatilization = paste0(path_scen, varnames["n_volatilization", "outname"]),
gpp = paste0(path_scen, varnames["gpp", "outname"]),
res_storage = paste0(path_scen, varnames["res_storage", "outname"]),
lakevol = paste0(path_scen, varnames["lakevol", "outname"]),
ndepos = paste0(path_scen, varnames["ndepos", "outname"]),
rd = paste0(path_scen, varnames["rd", "outname"]),
prec = paste0(path_scen, varnames["prec", "outname"]),
terr_area = paste0(path_scen, varnames["terr_area", "outname"]),
irrig = paste0(path_scen, varnames["irrig", "outname"]),
nfert_agr = paste0(path_scen, varnames["nfert_agr", "outname"]),
nmanure_agr = paste0(path_scen, varnames["nmanure_agr", "outname"]),
ndepos = paste0(path_scen, varnames["ndepos", "outname"]),
firen = paste0(path_scen, varnames["firen", "outname"]),
harvestn = paste0(path_scen, varnames["harvestn", "outname"]),
irrig_stor = paste0(path_scen, varnames["irrig_stor", "outname"]),
rivervol = paste0(path_scen, varnames["rivervol", "outname"]),
rootmoist = paste0(path_scen, varnames["rootmoist", "outname"])
)
files_reference <- list(
grid = paste0(path_ref, varnames["grid", "outname"]),
fpc = paste0(path_ref, varnames["fpc", "outname"]),
fpc_bft = paste0(path_ref, varnames["fpc_bft", "outname"]),
cftfrac = paste0(path_ref, varnames["cftfrac", "outname"]),
firec = paste0(path_ref, varnames["firec", "outname"]),
npp = paste0(path_ref, varnames["npp", "outname"]),
runoff = paste0(path_ref, varnames["runoff", "outname"]),
transp = paste0(path_ref, varnames["transp", "outname"]),
vegc = paste0(path_ref, varnames["vegc", "outname"]),
firef = paste0(path_ref, varnames["firef", "outname"]),
rh = paste0(path_ref, varnames["rh", "outname"]),
harvestc = paste0(path_ref, varnames["harvestc", "outname"]),
rharvestc = paste0(path_ref, varnames["rharvestc", "outname"]),
pft_harvestc = paste0(path_ref, varnames["pft_harvest", "outname"]),
pft_rharvestc = paste0(path_ref, varnames["pft_rharvest", "outname"]),
evap = paste0(path_ref, varnames["evap", "outname"]),
interc = paste0(path_ref, varnames["interc", "outname"]),
discharge = paste0(path_ref, varnames["discharge", "outname"]),
soilc = paste0(path_ref, varnames["soilc", "outname"]),
litc = paste0(path_ref, varnames["litc", "outname"]),
swc = paste0(path_ref, varnames["swc", "outname"]),
swc_vol = paste0(path_ref, varnames["swc_vol", "outname"]),
vegn = paste0(path_ref, varnames["vegn", "outname"]),
soilnh4 = paste0(path_ref, varnames["soilnh4", "outname"]),
soilno3 = paste0(path_ref, varnames["soilno3", "outname"]),
leaching = paste0(path_ref, varnames["leaching", "outname"]),
n2o_denit = paste0(path_ref, varnames["n2o_denit", "outname"]),
n2o_nit = paste0(path_ref, varnames["n2o_nit", "outname"]),
n2_emis = paste0(path_ref, varnames["n2_emis", "outname"]),
bnf = paste0(path_ref, varnames["bnf", "outname"]),
n_volatilization = paste0(path_ref, varnames["n_volatilization", "outname"]),
gpp = paste0(path_ref, varnames["gpp", "outname"]),
res_storage = paste0(path_ref, varnames["res_storage", "outname"]),
lakevol = paste0(path_ref, varnames["lakevol", "outname"]),
ndepos = paste0(path_ref, varnames["ndepos", "outname"]),
rd = paste0(path_ref, varnames["rd", "outname"]),
prec = paste0(path_ref, varnames["prec", "outname"]),
terr_area = paste0(path_ref, varnames["terr_area", "outname"]),
irrig = paste0(path_ref, varnames["irrig", "outname"]),
nfert_agr = paste0(path_ref, varnames["nfert_agr", "outname"]),
nmanure_agr = paste0(path_ref, varnames["nmanure_agr", "outname"]),
ndepos = paste0(path_ref, varnames["ndepos", "outname"]),
firen = paste0(path_ref, varnames["firen", "outname"]),
harvestn = paste0(path_ref, varnames["harvestn", "outname"]),
irrig_stor = paste0(path_ref, varnames["irrig_stor", "outname"]),
rivervol = paste0(path_ref, varnames["rivervol", "outname"]),
rootmoist = paste0(path_ref, varnames["rootmoist", "outname"])
)
if (overtime && (window != nyears)) stop("Overtime is enabled, but window \
length (", window, ") does not match the reference nyears.")
if (read_saved_data) {
if (!is.null(save_data)) {
message("Loading saved data from:", save_data)
load(file = save_data)
} else {
stop(
"save_data is not specified as parameter, ",
"nothing to load ... exiting"
)
}
} else {
# first read in all lpjml output files required for computing EcoRisks
returned_vars <- read_ecorisk_data(
files_reference = files_reference,
files_scenario = files_scenario,
save_file = save_data,
nitrogen = nitrogen,
time_span_reference = time_span_reference,
time_span_scenario = time_span_scenario,
debug = debug
)
# extract variables from return list object and give them proper names
state_ref <- returned_vars$state_ref
state_scen <- returned_vars$state_scen
fpc_ref <- returned_vars$fpc_ref
fpc_scen <- returned_vars$fpc_scen
bft_ref <- returned_vars$bft_ref
bft_scen <- returned_vars$bft_scen
cft_ref <- returned_vars$cft_ref
cft_scen <- returned_vars$cft_scen
lat <- returned_vars$lat
lon <- returned_vars$lon
cell_area <- returned_vars$cell_area
rm(returned_vars)
}
ncells <- length(cell_area)
slices <- (nyears_scen - window + 1)
ecorisk <- list(
ecorisk_total = array(0, dim = c(ncells, slices)),
vegetation_structure_change = array(0, dim = c(ncells, slices)),
local_change = array(0, dim = c(ncells, slices)),
global_importance = array(0, dim = c(ncells, slices)),
ecosystem_balance = array(0, dim = c(ncells, slices)),
c2vr = array(0, dim = c(4, ncells, slices)),
carbon_stocks = array(0, dim = c(ncells, slices)),
carbon_fluxes = array(0, dim = c(ncells, slices)),
carbon_total = array(0, dim = c(ncells, slices)),
water_total = array(0, dim = c(ncells, slices)),
water_fluxes = array(0, dim = c(ncells, slices)),
nitrogen_stocks = array(0, dim = c(ncells, slices)),
nitrogen_fluxes = array(0, dim = c(ncells, slices)),
nitrogen_total = array(0, dim = c(ncells, slices))
)
for (y in seq_len(slices)) {
message("Calculating time slice ", y, " of ", slices)
returned <- calc_ecorisk(
fpc_ref = fpc_ref,
fpc_scen = fpc_scen[, , y:(y + window - 1)],
bft_ref = bft_ref,
bft_scen = bft_scen[, , y:(y + window - 1)],
cft_ref = cft_ref,
cft_scen = cft_scen[, , y:(y + window - 1)],
state_ref = state_ref,
state_scen = state_scen[, y:(y + window - 1), ],
weighting = weighting,
lat = lat,
lon = lon,
cell_area = cell_area,
dimensions_only_local = dimensions_only_local,
nitrogen = nitrogen,
external_variability = external_variability,
c2vr = c2vr
)
ecorisk$ecorisk_total[, y] <- returned$ecorisk_total
ecorisk$vegetation_structure_change[, y] <- (
returned$vegetation_structure_change
)
ecorisk$local_change[, y] <- returned$local_change
ecorisk$global_importance[, y] <- returned$global_importance
ecorisk$ecosystem_balance[, y] <- returned$ecosystem_balance
ecorisk$c2vr[, , y] <- returned$c2vr
ecorisk$carbon_stocks[, y] <- returned$carbon_stocks
ecorisk$carbon_fluxes[, y] <- returned$carbon_fluxes
ecorisk$carbon_total[, y] <- returned$carbon_total
ecorisk$water_total[, y] <- returned$water_total
ecorisk$water_fluxes[, y] <- returned$water_fluxes
if (nitrogen) {
ecorisk$nitrogen_stocks[, y] <- returned$nitrogen_stocks
ecorisk$nitrogen_fluxes[, y] <- returned$nitrogen_fluxes
ecorisk$nitrogen_total[, y] <- returned$nitrogen_total
}
}
############## export and save data if requested #############
if (!(is.null(save_ecorisk))) {
message("Saving EcoRisk data to: ", save_ecorisk)
save(ecorisk, file = save_ecorisk)
}
#
###
return(ecorisk)
}
#' Calculate the ecosystem change metric EcoRisk between 2 sets of states
#'
#' Function to calculate the ecosystem change metric EcoRisk, based on
#' gamma/vegetation_structure_change
#' work from Sykes (1999), Heyder (2011), and Ostberg (2015,2018).
#' This is a reformulated version in R, not producing 100% similar values
#' than the C/bash version from Ostberg et al. 2018, but similar the methodology
#'
#' @param fpc_ref reference run data for fpc
#' @param fpc_scen scenario run data for fpc
#' @param bft_ref reference run data for fpc_bft
#' @param bft_scen scenario run data for fpc_bft
#' @param cft_ref reference run data for cftfrac
#' @param cft_scen scenario run data for cftfrac
#' @param state_ref reference run data for state variables
#' @param state_scen scenario run data for state variables
#' @param weighting apply "old" (Ostberg-like), "new", or "equal" weighting of
#' vegetation_structure_change weights (default "equal")
#' @param lat latitude array
#' @param lon longitude array
#' @param cell_area cellarea array
#' @param dimensions_only_local flag whether to use only local change component
#' for water/carbon/nitrogen fluxes and pools, or use an average of
#' local change, global change and ecosystem balance (default FALSE)
#' @param nitrogen include nitrogen outputs (default: TRUE)
#' @param external_variability include external change_to_variability_ratio?
#' (default: FALSE)
#' @param c2vr list with external change_to_variability_ratios for each
#' component (default: NULL)
#'
#' @return list data object containing arrays of ecorisk_total,
#' vegetation_structure_change, local_change, global_importance,
#' ecosystem_balance, carbon_stocks, carbon_fluxes, water_fluxes
#' (+ nitrogen_stocks and nitrogen_fluxes)
#'
#' @export
calc_ecorisk <- function(fpc_ref,
fpc_scen,
bft_ref,
bft_scen,
cft_ref,
cft_scen,
state_ref,
state_scen,
weighting = "equal",
lat,
lon,
cell_area,
dimensions_only_local = FALSE,
nitrogen = TRUE,
external_variability = FALSE,
c2vr = NULL) {
if (external_variability && is.null(c2vr)) {
stop("external_variability enabled, but not supplied (c2vr). Aborting.")
}
di_ref <- dim(fpc_ref)
di_scen <- dim(fpc_scen)
ncells <- di_ref[1]
nyears <- di_ref[3]
if (di_ref[3] != di_scen[3]) {
stop("Dimension year does not match between fpc_scen and fpc_ref.")
}
# calc vegetation_structure_change and variability of
# vegetation_structure_change within
# reference period S(vegetation_structure_change,
# sigma_vegetation_structure_change)
fpc_ref_mean <- apply(fpc_ref, c(1, 2), mean)
bft_ref_mean <- apply(bft_ref, c(1, 2), mean)
cft_ref_mean <- apply(cft_ref, c(1, 2), mean)
sigma_vegetation_structure_change_ref_list <- array(
0,
dim = c(ncells, nyears)
)
# calculate for every year of the reference period,
# vegetation_structure_change between that year and the average reference
# period year
# this gives the variability of vegetation_structure_change within the
# reference period
for (y in seq_len(nyears)) {
sigma_vegetation_structure_change_ref_list[, y] <- calc_delta_v( # nolint
fpc_ref = fpc_ref_mean,
fpc_scen = fpc_ref[, , y],
bft_ref = bft_ref_mean,
bft_scen = bft_ref[, , y],
cft_ref = cft_ref_mean,
cft_scen = cft_ref[, , y],
weighting = weighting
)
}
# calculate the std deviation over the reference period for each gridcell
vegetation_structure_changesd <- apply(
sigma_vegetation_structure_change_ref_list,
c(1),
stats::sd
)
# calculate vegetation_structure_change between average reference and average
# scenario period
vegetation_structure_change <- calc_delta_v(
fpc_ref = fpc_ref_mean,
fpc_scen = apply(fpc_scen, c(1, 2), mean),
bft_ref = bft_ref_mean,
bft_scen = apply(bft_scen, c(1, 2), mean),
cft_ref = cft_ref_mean,
cft_scen = apply(cft_scen, c(1, 2), mean),
weighting = weighting
)
#
####
############## calc EcoRisk components ################
# dimensions in the state vector
# 1 "vegetation_carbon_pool"
# 2 "soil_carbon_pool"
# 3 "carbon_influx"
# 4 "carbon_outflux"
# 5 "soil_water_pool"
# 6 "water_influx"
# 7 "water_outflux"
# 8 "other"
# 9 "vegetation_nitrogen_pool"
# 10 "soil_mineral_nitrogen_pool"
# 11 "nitrogen_influx"
# 12 "nitrogen_outflux"
delta_var <- s_change_to_var_ratio(
vegetation_structure_change,
vegetation_structure_changesd
)
nitrogen_dimensions <- c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")
all_dimensions <- dimnames(state_scen)$class
non_nitrogen_dimensions <- setdiff(all_dimensions, nitrogen_dimensions)
if (nitrogen) {
lc_raw <- calc_component(
ref = state_ref,
scen = state_scen,
local = TRUE,
cell_area = cell_area
) # local change
gi_raw <- calc_component(
ref = state_ref,
scen = state_scen,
local = FALSE,
cell_area = cell_area
) # global importance
eb_raw <- calc_ecosystem_balance(
ref = state_ref,
scen = state_scen
) # ecosystem balance
} else {
lc_raw <- calc_component(
ref = state_ref[, , non_nitrogen_dimensions],
scen = state_scen[, , non_nitrogen_dimensions],
local = TRUE,
cell_area = cell_area
) # local change
gi_raw <- calc_component(
ref = state_ref[, , non_nitrogen_dimensions],
scen = state_scen[, , non_nitrogen_dimensions],
local = FALSE,
cell_area = cell_area
) # global importance
eb_raw <- calc_ecosystem_balance(
ref = state_ref[, , non_nitrogen_dimensions],
scen = state_scen[, , non_nitrogen_dimensions]
) # ecosystem balance
}
if (dimensions_only_local == TRUE) {
# carbon stocks (local change)
cs <- calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
local = TRUE,
cell_area = cell_area
)$full
# carbon fluxes (local change)
cf <- calc_component(
ref = state_ref[, , c("carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("carbon_influx", "carbon_outflux")],
local = TRUE,
cell_area = cell_area
)$full
# total carbon (local change)
ct <- calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
local = TRUE,
cell_area = cell_area
)$full
# water fluxes (local change)
wf <- calc_component(
ref = state_ref[, , c("water_influx", "water_outflux")],
scen = state_scen[, , c("water_influx", "water_outflux")],
local = TRUE,
cell_area = cell_area
)$full
# total water (local change)
wt <- calc_component(
ref = state_ref[, , c("water_influx", "water_outflux", "soil_water_pool")],
scen = state_scen[, , c("water_influx", "water_outflux", "soil_water_pool")],
local = TRUE,
cell_area = cell_area
)$full
# nitrogen stocks (local change)
if (nitrogen) {
ns <- calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
local = TRUE,
cell_area = cell_area
)$full
# nitrogen fluxes (local change)
nf <- calc_component(
ref = state_ref[, , c("nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("nitrogen_influx", "nitrogen_outflux")],
local = TRUE,
cell_area = cell_area
)$full
# total nitrogen (local change)
nt <- calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
local = TRUE,
cell_area = cell_area
)$full
}
} else { # local == FALSE
cf <- (
calc_component(
ref = state_ref[, , c("carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("carbon_influx", "carbon_outflux")],
local = TRUE,
cell_area = cell_area
)$full + # carbon fluxes
calc_component(
ref = state_ref[, , c("carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("carbon_influx", "carbon_outflux")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("carbon_influx", "carbon_outflux")]
)$full
) / 3
# carbon stocks
cs <- (
calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool")]
)$full
) / 3
# carbon total
ct <- (
calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")],
scen = state_scen[, , c("vegetation_carbon_pool", "soil_carbon_pool", "carbon_influx", "carbon_outflux")]
)$full
) / 3
# water fluxes
wf <- (
calc_component(
ref = state_ref[, , c("water_influx", "water_outflux")],
scen = state_scen[, , c("water_influx", "water_outflux")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("water_influx", "water_outflux")],
scen = state_scen[, , c("water_influx", "water_outflux")],
local = FALSE,
cell_area = cell_area
)$full + calc_ecosystem_balance(
ref = state_ref[, , c("water_influx", "water_outflux")],
scen = state_scen[, , c("water_influx", "water_outflux")]
)$full
) / 3
# water total
wt <- (
calc_component(
ref = state_ref[, , c("water_influx", "water_outflux", "soil_water_pool")],
scen = state_scen[, , c("water_influx", "water_outflux", "soil_water_pool")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("water_influx", "water_outflux", "soil_water_pool")],
scen = state_scen[, , c("water_influx", "water_outflux", "soil_water_pool")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("water_influx", "water_outflux", "soil_water_pool")],
scen = state_scen[, , c("water_influx", "water_outflux", "soil_water_pool")]
)$full
) / 3
if (nitrogen) {
# nitrogen stocks (local change)
ns <- (
calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
local = FALSE, cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool")]
)$full
) / 3
# nitrogen fluxes (local change)
nf <- (
calc_component(
ref = state_ref[, , c("nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("nitrogen_influx", "nitrogen_outflux")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("nitrogen_influx", "nitrogen_outflux")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("nitrogen_influx", "nitrogen_outflux")]
)$full
) / 3
nt <- (
calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
local = TRUE,
cell_area = cell_area
)$full +
calc_component(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
local = FALSE,
cell_area = cell_area
)$full +
calc_ecosystem_balance(
ref = state_ref[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")],
scen = state_scen[, , c("vegetation_nitrogen_pool", "soil_mineral_nitrogen_pool", "nitrogen_influx", "nitrogen_outflux")]
)$full
) / 3
}
}
if (external_variability) {
delta <- vegetation_structure_change * c2vr["vs", ] # vegetation_structure_change
lc <- lc_raw$value * c2vr["lc", ]
gi <- gi_raw$value * c2vr["gi", ]
eb <- eb_raw$value * c2vr["eb", ]
} else {
delta <- vegetation_structure_change * delta_var # vegetation_structure_change
lc <- lc_raw$value * lc_raw$var
gi <- gi_raw$value * gi_raw$var
eb <- eb_raw$value * eb_raw$var
c2vr <- rbind(delta_var, lc_raw$var, gi_raw$var, eb_raw$var) # dim=(4,ncells)
dimnames(c2vr) <- list(component = c("vs", "lc", "gi", "eb"), cell = 0:(ncells - 1))
}
# calc total EcoRisk as the average of the 4 components
ecorisk_full <- (delta + lc + gi + eb) / 4 # check for NAs
if (nitrogen) {
ecorisk <- list(
ecorisk_total = ecorisk_full,
vegetation_structure_change = delta,
local_change = lc,
global_importance = gi,
ecosystem_balance = eb,
c2vr = c2vr,
carbon_stocks = cs,
carbon_fluxes = cf,
carbon_total = ct,
water_fluxes = wf,
water_stocks = NA,
water_total = wt,
nitrogen_stocks = ns,
nitrogen_fluxes = nf,
nitrogen_total = nt
)
} else {
ecorisk <- list(
ecorisk_total = ecorisk_full,
vegetation_structure_change = delta,
local_change = lc,
global_importance = gi,
ecosystem_balance = eb,
c2vr = c2vr,
carbon_stocks = cs,
carbon_fluxes = cf,
carbon_total = ct,
water_fluxes = wf,
water_stocks = NA,
water_total = wt,
nitrogen_stocks = NA,
nitrogen_fluxes = NA,
nitrogen_total = NA
)
}
###
return(ecorisk)
}
#' Read in output data from LPJmL to calculate the ecosystem change metric
#' EcoRisk
#'
#' Utility function to read in output data from LPJmL for calculation of EcoRisk
#'
#' @param files_reference folder of reference run
#' @param files_scenario folder of scenario run
#' @param save_file file to save read in data to (default NULL)
#' @param time_span_reference vector of years to use as scenario period
#' @param time_span_scenario vector of years to use as scenario period
#' @param nitrogen include nitrogen outputs for pools and fluxes into EcoRisk
#' calculation (default FALSE)
#' @param debug write out all nitrogen state variables (default FALSE)
#'
#' @return list data object containing arrays of state_ref, mean_state_ref,
#' state_scen, mean_state_scen, fpc_ref, fpc_scen, bft_ref, bft_scen,
#' cft_ref, cft_scen, lat, lon, cell_area
#'
#' @export
read_ecorisk_data <- function(
files_reference, # nolint
files_scenario,
save_file = NULL,
time_span_reference,
time_span_scenario,
nitrogen,
debug = FALSE) {
file_type <- tools::file_ext(files_reference$grid)
if (file_type %in% c("json", "clm")) {
# read grid
grid <- lpjmlkit::read_io(
files_reference$grid
)
# calculate cell area
cell_area <- drop(lpjmlkit::read_io(
filename = files_reference$terr_area
)$data) # in m2
lat <- grid$data[, , 2]
lon <- grid$data[, , 1]
ncells <- length(lat)
nyears <- length(time_span_scenario)
### read in lpjml output
# for vegetation_structure_change (fpc,fpc_bft,cftfrac)
message("Reading in fpc, fpc_bft, cftfrac")
cft_scen <- aperm(lpjmlkit::read_io(
files_scenario$cftfrac,
subset = list(year = as.character(time_span_scenario))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
bft_scen <- aperm(lpjmlkit::read_io(
files_scenario$fpc_bft,
subset = list(year = as.character(time_span_scenario))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
fpc_scen <- aperm(lpjmlkit::read_io(
files_scenario$fpc,
subset = list(year = as.character(time_span_scenario))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
if (file.exists(files_reference$cftfrac)) {
cft_ref <- aperm(lpjmlkit::read_io(
files_reference$cftfrac,
subset = list(year = as.character(time_span_reference))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
} else {
cft_ref <- cft_scen * 0
}
if (file.exists(files_reference$fpc_bft)) {
bft_ref <- aperm(lpjmlkit::read_io(
files_reference$fpc_bft,
subset = list(year = as.character(time_span_reference))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
} else {
bft_ref <- bft_scen * 0
}
fpc_ref <- aperm(lpjmlkit::read_io(
files_reference$fpc,
subset = list(year = as.character(time_span_reference))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum)), c(1, 3, 2)) %>%
suppressWarnings()
#### new input reading ###
metric_files <- system.file(
"extdata",
"metric_files.yml",
package = "biospheremetrics"
) %>%
yaml::read_yaml()
nclasses <- length(metric_files$metric$ecorisk_nitrogen$metric_class)
nstate_dimensions <- 0
for (i in seq_len(nclasses)) {
nstate_dimensions <- nstate_dimensions +
length(metric_files$metric$ecorisk_nitrogen$metric_class[[i]])
}
state_ref <- array(0, dim = c(ncells, nyears, nstate_dimensions))
state_scen <- array(0, dim = c(ncells, nyears, nstate_dimensions))
class_names <- seq_len(nstate_dimensions)
index <- 1
# iterate over main classes (carbon pools, water fluxes ...)
for (c in seq_len(nclasses)) {
classe <- metric_files$metric$ecorisk_nitrogen$metric_class[[c]]
nsubclasses <- length(classe)
# iterate over subclasses (vegetation carbon, soil water ...)
for (s in seq_len(nsubclasses)) {
subclass <- classe[s]
class_names[index] <- names(subclass)
vars <- split_sign(unlist(subclass))
for (v in seq_len(length(vars[, 1]))) {
path_scen_file <- files_scenario[[vars[v, "variable"]]]
if (file.exists(path_scen_file)) {
header_scen <- lpjmlkit::read_meta(filename = path_scen_file)
message(
"Reading in", path_scen_file, "with unit", header_scen$unit,
"-> as part of", class_names[index]
)
var_scen <- lpjmlkit::read_io(
path_scen_file,
subset = list(year = as.character(time_span_scenario))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum, band = sum), ) %>%
drop() %>%
suppressWarnings()
} else {
stop(paste("Couldn't read in:", path_scen_file, " - stopping!"))
}
path_ref_file <- files_reference[[vars[v, "variable"]]]
if (file.exists(path_ref_file)) {
header_ref <- lpjmlkit::read_meta(path_ref_file)
message(
"Reading in", path_ref_file, "with unit", header_ref$unit,
"-> as part of", class_names[index]
)
var_ref <- lpjmlkit::read_io(
path_ref_file,
subset = list(year = as.character(time_span_reference))
) %>%
lpjmlkit::transform(to = c("year_month_day")) %>%
lpjmlkit::as_array(aggregate = list(month = sum, band = sum)) %>%
drop() %>%
suppressWarnings()
} else {
stop(paste("Couldn't read in:", path_ref_file, " - stopping!"))
}
# if (window > 30){
# if (vars[v,"sign"] == "+"){
# state_scen[,,index,] <- state_scen[,,index,] + var_scen
# state_ref[,,index,] <- state_ref[,,index,] + var_ref
# } else { # vars[v,"sign"] == "-"
# state_scen[,,index,] <- state_scen[,,index,] - var_scen
# state_ref[,,index,] <- state_ref[,,index,] - var_ref
# }
# }else{
if (vars[v, "sign"] == "+") {
state_scen[, , index] <- state_scen[, , index] + var_scen
state_ref[, , index] <- state_ref[, , index] + var_ref
} else { # vars[v,"sign"] == "-"
state_scen[, , index] <- state_scen[, , index] - var_scen
state_ref[, , index] <- state_ref[, , index] - var_ref
}
# }
}
index <- index + 1
}
}
dimnames(state_scen) <- list(cell = 0:(ncells - 1), year = as.character(time_span_scenario), class = class_names)
dimnames(state_ref) <- list(cell = 0:(ncells - 1), year = as.character(time_span_reference), class = class_names)
} else if (file_type == "nc") { # to be added
stop(
"nc reading has not been updated to latest functionality. ",
"Please contact Fabian Stenzel"
)
} else {
stop("Unrecognized file type (", file_type, ")")
}
if (!(is.null(save_file))) {
message("Saving data to: ", save_file)
save(state_ref, state_scen, fpc_ref, fpc_scen,
bft_ref, bft_scen, cft_ref, cft_scen, lat, lon, cell_area,
file = save_file
)
}
return(
list(
state_ref = state_ref,
state_scen = state_scen,
fpc_ref = fpc_ref,
fpc_scen = fpc_scen,
bft_ref = bft_ref,
bft_scen = bft_scen,
cft_ref = cft_ref,
cft_scen = cft_scen,
lat = lat,
lon = lon,
cell_area = cell_area
)
)
}
#' Calculates changes in vegetation structure (vegetation_structure_change)
#'
#' Utility function to calculate changes in vegetation structure
#' (vegetation_structure_change) for calculation of EcoRisk
#'
#' @param fpc_ref reference fpc array (dim: [ncells,npfts+1])
#' @param fpc_scen scenario fpc array (dim: [ncells,npfts+1])
#' @param bft_ref reference bft array (dim: [ncells,nbfts])
#' @param bft_scen scenario bft array (dim: [ncells,nbfts])
#' @param cft_ref reference cft array (dim: [ncells,ncfts])
#' @param cft_scen scenario cft array (dim: [ncells,ncfts])
#' @param weighting apply "old" (Ostberg-like), "new", or "equal" weighting of
#' vegetation_structure_change weights (default "equal")
#'
#' @return vegetation_structure_change array of size ncells with the
#' vegetation_structure_change value [0,1] for each cell
#'
#' @examples
#' \dontrun{
#' vegetation_structure_change <- calc_delta_v(
#' fpc_ref = fpc_ref_mean,
#' fpc_scen = apply(fpc_scen, c(1, 2), mean),
#' bft_ref = bft_ref_mean,
#' bft_scen = apply(bft_scen, c(1, 2), mean),
#' cft_ref = cft_ref_mean,
#' cft_scen = apply(cft_scen, c(1, 2), mean),
#' weighting = "equal"
#' )
#' }
#' @export
calc_delta_v <- function(fpc_ref, # nolint
fpc_scen,
bft_ref,
bft_scen,
cft_ref,
cft_scen,
weighting = "equal") {
di <- dim(fpc_ref)
ncells <- di[1]
npfts <- di[2] - 1
fpc_ref[fpc_ref < 0] <- 0
fpc_scen[fpc_scen < 0] <- 0
bft_ref[bft_ref < 0] <- 0
bft_scen[bft_scen < 0] <- 0
cft_ref[cft_ref < 0] <- 0
cft_scen[cft_scen < 0] <- 0
if (npfts == 9) {
# barren = 1 - crop area - natural vegetation area +
# barren under bioenergy trees
barren_area_ref <- (
1 - rowSums(cft_ref) -
rowSums(fpc_ref[, 2:10]) * fpc_ref[, 1] +
rowSums(cft_ref[, c(16, 32)]) * (1 - rowSums(bft_ref[, c(1:4, 7:10)]))
)
barren_area_ref[barren_area_ref < 0] <- 0
tree_area_ref <- array(0, dim = c(ncells, 11))
# natural tree area fractions scaled by total natural frac
tree_area_ref[, 1:7] <- (
fpc_ref[, 2:8] * fpc_ref[, 1]
)
# fraction of rainfed tropical and temperate BE trees scaled by total
# rainfed bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_ref[, 8:9] <- (
cft_ref[, 16] * bft_ref[, 1:2] / rowSums(bft_ref[, c(1, 2, 4)])
)
# fraction of irrigated tropical and temperate BE trees scaled by total
# irrigated bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_ref[, 10:11] <- (
cft_ref[, 32] * bft_ref[, 7:8] / rowSums(bft_ref[, c(7, 8, 10)])
)
grass_area_ref <- array(0, dim = c(ncells, 20))
# natural grass
grass_area_ref[, 1:2] <- fpc_ref[, 9:10] * fpc_ref[, 1]
# crops
grass_area_ref[, 3:15] <- cft_ref[, 1:13] + cft_ref[, 17:29]
# managed grass rf
grass_area_ref[, 16] <- cft_ref[, 14]
# managed grass irr
grass_area_ref[, 17] <- cft_ref[, 30]
# bioenergy grass
grass_area_ref[, 18] <- cft_ref[, 15] + cft_ref[, 31]
# fraction of rainfed grass under bioenergy trees
grass_area_ref[, 19] <- (
cft_ref[, 16] * bft_ref[, 4] / rowSums(bft_ref[, c(1, 2, 4)])
)
# fraction of irrigated grass under bioenergy trees
grass_area_ref[, 20] <- (
cft_ref[, 32] * bft_ref[, 10] / rowSums(bft_ref[, c(7, 8, 10)])
)
# barren
barren_area_scen <- (
1 - rowSums(cft_scen) -
rowSums(fpc_scen[, 2:10]) * fpc_scen[, 1] +
rowSums(cft_scen[, c(16, 32)]) * (1 - rowSums(bft_scen[, c(1:4, 7:10)]))
)
barren_area_scen[barren_area_scen < 0] <- 0
tree_area_scen <- array(0, dim = c(ncells, 11))
# natural tree area fractions scaled by total natural frac
tree_area_scen[, 1:7] <- (
fpc_scen[, 2:8] * fpc_scen[, 1]
)
# fraction of rainfed tropical and temperate BE trees scaled by total
# rainfed bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_scen[, 8:9] <- (
cft_scen[, 16] * bft_scen[, 1:2] / rowSums(bft_scen[, c(1, 2, 4)])
)
# fraction of irrigated tropical and temperate BE trees scaled by total
# irrigated bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_scen[, 10:11] <- (
cft_scen[, 32] * bft_scen[, 7:8] / rowSums(bft_scen[, c(7, 8, 10)])
)
grass_area_scen <- array(0, dim = c(ncells, 20))
# natural grass
grass_area_scen[, 1:2] <- fpc_scen[, 9:10] * fpc_scen[, 1]
# crops
grass_area_scen[, 3:15] <- cft_scen[, 1:13] + cft_scen[, 17:29]
# managed grass rf
grass_area_scen[, 16] <- cft_scen[, 14]
# managed grass irr
grass_area_scen[, 17] <- cft_scen[, 30]
# bioenergy grass
grass_area_scen[, 18] <- cft_scen[, 15] + cft_scen[, 31]
# fraction of rainfed grass under bioenergy trees
grass_area_scen[, 19] <- (
cft_scen[, 16] * bft_scen[, 4] / rowSums(bft_scen[, c(1, 2, 4)])
)
# fraction of irrigated grass under bioenergy trees
grass_area_scen[, 20] <- (
cft_scen[, 32] * bft_scen[, 10] / rowSums(bft_scen[, c(7, 8, 10)])
)
# evergreenness, needleleavedness, tropicalness, borealness, naturalness
tree_attributes <- matrix(
c(
c(1, 0, 1, 0, 1), # 1 TrBE
c(0, 0, 1, 0, 1), # 2 TrBR
c(1, 1, 0, 0, 1), # 3 TeNE
c(1, 0, 0, 0, 1), # 4 TeBE
c(0, 0, 0, 0, 1), # 5 TeBS
c(1, 1, 0, 1, 1), # 6 BoNE
c(0, 0.25, 0, 1, 1), # 7 BoS (including larchs)
c(1, 0, 1, 0, 0), # 8 TrBi tropical bioenergy rainfed
c(0, 0, 0, 0, 0), # 9 TeBi temperate bioenergy rainfed
c(1, 0, 1, 0, 0), # 10 TrBi tropical bioenergy irrigated
c(0, 0, 0, 0, 0) # 11 TeBi temperate bioenergy irrigated
),
nrow = 11,
byrow = TRUE
)
if (weighting == "equal") {
tree_weights <- c(0.2, 0.2, 0.2, 0.2, 0.2)
# changed compared to Sebastian Ostberg's method
} else if (weighting == "new") {
tree_weights <- c(0.2, 0.2, 0.3, 0.3, 0.3) / 1.3
# Sebastian's method (no downscaling to weightsum 1)
} else if (weighting == "old") {
tree_weights <- c(0.2, 0.2, 0.3, 0.3, 0.3)
} else {
stop("Unknown method of weighting.")
}
grass_attributes <- array(0, dim = c(ncells, 20, 2))
# 1 C3grass
# 2 C4grass
# 3 TemperateCereals
# 4 Rice
# 5 Maize
# 6 TropicalCereals
# 7 Pulses
# 8 TemperateRoots
# 9 TropicalRoots
# 10 Sunflower
# 11 Soybean
# 12 Groundnut
# 13 Rapeseed
# 14 Sugarcane
# 15 Others
# 16 Managed grass rainfed
# 17 Managed grass irrigated
# 18 Bioenergy grass
# 19 Grass under rainfed Bioenergy trees
# 20 Grass under irrigated Bioenergy trees
# tropicalness
grass_attributes[, , 1] <- rep(
c(0, 1, 0, 1, 1, 1, 0.5, 0, 1, 0.5, 1, 1, 0.5, 1, 0.5, NA, NA, 1, NA, NA),
each = ncells
)
# naturalness
grass_attributes[, , 2] <- rep(
c(1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
each = ncells
)
# dynamic share of tropicalness for rf/irr grasslands taken from ratio of
# bioenergy grasses
dyn_grass_attributes <- cbind(
bft_scen[, 6] / rowSums(bft_scen[, 5:6]),
bft_scen[, 12] / rowSums(bft_scen[, 11:12])
)
dyn_grass_attributes[!is.finite(dyn_grass_attributes)] <- 0
# managed grass rf/irr
grass_attributes[, 16:17, 1] <- dyn_grass_attributes
# grass under biotrees rf/irr (taken from managed grass)
grass_attributes[, 19:20, 1] <- dyn_grass_attributes
if (weighting == "equal") {
grass_weights <- c(0.2, 0.2)
# changed compared to Sebastian Ostberg's method
} else if (weighting == "new") {
grass_weights <- c(0.5, 0.5)
# Sebastian Ostbergs's method (no downscaling to weightsum 1)
} else if (weighting == "old") {
grass_weights <- c(0.3, 0.3)
} else {
stop("Unknown method of weighting.")
}
} else if (npfts == 11) {
# barren = 1 - crop area - natural vegetation area +
# barren under bioenergy trees
barren_area_ref <- (
1 - rowSums(cft_ref) -
rowSums(fpc_ref[, 2:12]) * fpc_ref[, 1] +
rowSums(cft_ref[, c(16, 32)]) * (1 - rowSums(bft_ref[, c(4:9, 13:18)]))
)
barren_area_ref[barren_area_ref < 0] <- 0
tree_area_ref <- array(0, dim = c(ncells, 12))
# natural tree area fractions scaled by total natural frac
tree_area_ref[, 1:8] <- fpc_ref[, 2:9] * fpc_ref[, 1]
# fraction of rainfed tropical and temperate BE trees scaled by total
# rainfed bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_ref[, 9:10] <- (
cft_ref[, 16] * bft_ref[, 7:8] / rowSums(bft_ref[, 4:8])
)
# fraction of irrigated tropical and temperate BE trees scaled by total
# irrigated bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_ref[, 11:12] <- (
cft_ref[, 32] * bft_ref[, 16:17] / rowSums(bft_ref[, 13:17])
)
grass_area_ref <- array(0, dim = c(ncells, 21))
# natural grass
grass_area_ref[, 1:3] <- fpc_ref[, 10:12] * fpc_ref[, 1]
# crops
grass_area_ref[, 4:16] <- cft_ref[, 1:13] + cft_ref[, 17:29]
# managed grass rf
grass_area_ref[, 17] <- cft_ref[, 14]
# managed grass irr
grass_area_ref[, 18] <- cft_ref[, 30]
# bioenergy grass
grass_area_ref[, 19] <- cft_ref[, 15] + cft_ref[, 31]
# fraction of rainfed grass under bioenergy trees
grass_area_ref[, 20] <- (
cft_ref[, 16] * rowSums(bft_ref[, 4:6]) / rowSums(bft_ref[, 4:8])
)
# fraction of irrigated grass under bioenergy trees
grass_area_ref[, 21] <- (
cft_ref[, 32] * rowSums(bft_ref[, 13:15]) / rowSums(bft_ref[, 13:17])
)
# barren = 1 - crop area - natural vegetation area +
# barren under bioenergy trees
barren_area_scen <- (
1 - rowSums(cft_scen) -
rowSums(fpc_scen[, 2:12]) * fpc_scen[, 1] +
rowSums(cft_scen[, c(16, 32)]) * (1 - rowSums(bft_scen[, c(4:9, 13:18)]))
)
barren_area_scen[barren_area_scen < 0] <- 0
tree_area_scen <- array(0, dim = c(ncells, 12))
# natural tree area fractions scaled by total natural frac
tree_area_scen[, 1:8] <- fpc_scen[, 2:9] * fpc_scen[, 1]
# fraction of rainfed tropical and temperate BE trees scaled by total
# rainfed bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_scen[, 9:10] <- (
cft_scen[, 16] * bft_scen[, 7:8] / rowSums(bft_scen[, 4:8])
)
# fraction of irrigated tropical and temperate BE trees scaled by total
# irrigated bioenergy tree area and relative fpc of bioenergy trees and
# grass under bioenergy trees
tree_area_scen[, 11:12] <- (
cft_scen[, 32] * bft_scen[, 16:17] / rowSums(bft_scen[, 13:17])
)
grass_area_scen <- array(0, dim = c(ncells, 21))
# natural grass
grass_area_scen[, 1:3] <- fpc_scen[, 10:12] * fpc_scen[, 1]
# crops
grass_area_scen[, 4:16] <- cft_scen[, 1:13] + cft_scen[, 17:29]
# managed grass rf
grass_area_scen[, 17] <- cft_scen[, 14]
# managed grass irr
grass_area_scen[, 18] <- cft_scen[, 30]
# bioenergy grass
grass_area_scen[, 19] <- cft_scen[, 15] + cft_scen[, 31]
# fraction of rainfed grass under bioenergy trees
grass_area_scen[, 20] <- (
cft_scen[, 16] * rowSums(bft_scen[, 4:6]) / rowSums(bft_scen[, 4:8])
)
# fraction of irrigated grass under bioenergy trees
grass_area_scen[, 21] <- (
cft_scen[, 32] * rowSums(bft_scen[, 13:15]) / rowSums(bft_scen[, 13:17])
)
# evergreenness, needleleavedness, tropicalness, borealness, naturalness
tree_attributes <- matrix(
c(
c(1, 0, 1, 0, 1), # 1 TrBE
c(0, 0, 1, 0, 1), # 2 TrBR
c(1, 1, 0, 0, 1), # 3 TeNE
c(1, 0, 0, 0, 1), # 4 TeBE
c(0, 0, 0, 0, 1), # 5 TeBS
c(1, 1, 0, 1, 1), # 6 BoNE
c(0, 0, 0, 1, 1), # 7 BoBS
c(0, 1, 0, 1, 1), # 8 BoNS
c(1, 0, 1, 0, 0), # 9 TrBi tropical bioenergy rainfed
c(0, 0, 0, 0, 0), # 10 TeBi temperate bioenergy rainfed
c(1, 0, 1, 0, 0), # 11 TrBi tropical bioenergy irrigated
c(0, 0, 0, 0, 0) # 12 TeBi temperate bioenergy irrigated
),
nrow = 12,
byrow = TRUE
)
if (weighting == "equal") {
tree_weights <- c(0.2, 0.2, 0.2, 0.2, 0.2)
# changed compared to Sebastian Ostberg's method
} else if (weighting == "new") {
tree_weights <- c(0.2, 0.2, 0.3, 0.3, 0.3) / 1.3
# Sebastian Ostberg's method (no downscaling to weightsum 1)
} else if (weighting == "old") {
tree_weights <- c(0.2, 0.2, 0.3, 0.3, 0.3)
} else {
stop("Unknown method of weighting.")
}
grass_attributes <- array(0, dim = c(ncells, 21, 3))
# 1 C4grass tropic
# 2 C3grass temperate
# 3 C3grass polar
# 4 TemperateCereals
# 5 Rice
# 6 Maize
# 7 TropicalCereals
# 8 Pulses
# 9 TemperateRoots
# 10 TropicalRoots
# 11 Sunflower
# 12 Soybean
# 13 Groundnut
# 14 Rapeseed
# 15 Sugarcane
# 16 Others
# 17 Managed grass rainfed
# 18 Managed grass irrigated
# 19 Bioenergy grass
# 20 Grass under rainfed Bioenergy trees
# 21 Grass under irrigated Bioenergy trees
# tropicalness
grass_attributes[, , 1] <- rep(
c(1, 0, 0, 0, 1, 1, 1, 0.5, 0, 1, 0.5, 1, 1, 0.5, 1, 0.5, NA, NA, 1, NA, NA), # nolint
each = ncells
)
# borealness
grass_attributes[, , 2] <- rep(
c(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, 0, NA, NA),
each = ncells
)
# naturalness
grass_attributes[, , 3] <- rep(
c(1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
each = ncells
)
# dynamic share of tropicalness for grass under irr biotrees
dyn_trop_grass_attributes <- cbind(
# dynamic share of tropicalness for rf grasslands
bft_scen[, 1] / rowSums(bft_scen[, 1:3]),
# dynamic share of tropicalness for irr grasslands
bft_scen[, 10] / rowSums(bft_scen[, 10:12]),
# dynamic share of tropicalness for grass under rf biotrees
bft_scen[, 4] / rowSums(bft_scen[, 4:6]),
bft_scen[, 13] / rowSums(bft_scen[, 13:15])
)
dyn_trop_grass_attributes[!is.finite(dyn_trop_grass_attributes)] <- 0
# managed grass rf/irr, grass under biotrees rf/irr
grass_attributes[, c(17, 18, 20, 21), 1] <- dyn_trop_grass_attributes
# dynamic share of borealness for grass under irr biotrees
dyn_boreal_grass_attributes <- cbind(
# dynamic share of borealness for rf grasslands
bft_scen[, 3] / rowSums(bft_scen[, 1:3]),
# dynamic share of borealness for irr grasslands
bft_scen[, 12] / rowSums(bft_scen[, 10:12]),
# dynamic share of borealness for grass under rf biotrees
bft_scen[, 6] / rowSums(bft_scen[, 4:6]),
bft_scen[, 15] / rowSums(bft_scen[, 13:15])
)
dyn_boreal_grass_attributes[!is.finite(dyn_boreal_grass_attributes)] <- 0
# managed grass rf/irr, grass under biotrees rf/irr
grass_attributes[, c(17, 18, 20, 21), 2] <- dyn_boreal_grass_attributes
if (weighting == "equal") {
grass_weights <- c(0.2, 0.2, 0.2)
} else if (weighting == "old" || weighting == "new") {
grass_weights <- c(0.3333333, 0.3333333, 0.3333333)
} else {
stop("Unknown method of weighting.")
}
} else {
stop("Unknown number of pfts.")
}
# compute vegetation_structure_change
barren_v <- fBasics::rowMins(cbind(barren_area_ref, barren_area_scen))
trees_v <- fBasics::rowMins(
cbind(
rowSums(tree_area_ref, na.rm = TRUE),
rowSums(tree_area_scen, na.rm = TRUE)
)
)
grass_v <- fBasics::rowMins(
cbind(
rowSums(grass_area_ref, na.rm = TRUE),
rowSums(grass_area_scen, na.rm = TRUE)
)
)
inner_sum_trees <- (
# evergreenness
abs(
rowSums(tree_area_ref[, ] * rep(tree_attributes[, 1], each = ncells), na.rm = TRUE) - # nolint
rowSums(tree_area_scen[, ] * rep(tree_attributes[, 1], each = ncells), na.rm = TRUE) # nolint
) * tree_weights[1] +
# needleleavedness
abs(
rowSums(tree_area_ref[, ] * rep(tree_attributes[, 2], each = ncells), na.rm = TRUE) - # nolint
rowSums(tree_area_scen[, ] * rep(tree_attributes[, 2], each = ncells), na.rm = TRUE) # nolint
) * tree_weights[2] +
# tropicalness
abs(
rowSums(tree_area_ref[, ] * rep(tree_attributes[, 3], each = ncells), na.rm = TRUE) - # nolint
rowSums(tree_area_scen[, ] * rep(tree_attributes[, 3], each = ncells), na.rm = TRUE) # nolint
) * tree_weights[3] +
# borealness
abs(
rowSums(tree_area_ref[, ] * rep(tree_attributes[, 4], each = ncells), na.rm = TRUE) - # nolint
rowSums(tree_area_scen[, ] * rep(tree_attributes[, 4], each = ncells), na.rm = TRUE) # nolint
) * tree_weights[4] +
# naturalness
abs(
rowSums(tree_area_ref[, ] * rep(tree_attributes[, 5], each = ncells), na.rm = TRUE) - # nolint
rowSums(tree_area_scen[, ] * rep(tree_attributes[, 5], each = ncells), na.rm = TRUE) # nolint
) * tree_weights[5]
)
if (npfts == 9) {
inner_sum_grasses <- (
# tropicalness
abs(
rowSums(grass_area_ref[, ] * grass_attributes[, , 1], na.rm = TRUE) -
rowSums(grass_area_scen[, ] * grass_attributes[, , 1], na.rm = TRUE)
) * grass_weights[1] +
# naturalness
abs(
rowSums(grass_area_ref[, ] * grass_attributes[, , 2], na.rm = TRUE) -
rowSums(grass_area_scen[, ] * grass_attributes[, , 2], na.rm = TRUE)
) * grass_weights[2]
)
} else if (npfts == 11) {
inner_sum_grasses <- (
# tropicalness
abs(
rowSums(grass_area_ref[, ] * grass_attributes[, , 1], na.rm = TRUE) -
rowSums(grass_area_scen[, ] * grass_attributes[, , 1], na.rm = TRUE)
) * grass_weights[1] +
# borealness
abs(
rowSums(grass_area_ref[, ] * grass_attributes[, , 2], na.rm = TRUE) -
rowSums(grass_area_scen[, ] * grass_attributes[, , 2], na.rm = TRUE)
) * grass_weights[2] +
# naturalness
abs(
rowSums(grass_area_ref[, ] * grass_attributes[, , 3], na.rm = TRUE) -
rowSums(grass_area_scen[, ] * grass_attributes[, , 3], na.rm = TRUE)
) * grass_weights[3]
)
} else {
stop("Unknown number of pfts.")
}
vegetation_structure_change <- (
1 - barren_v -
trees_v * (1 - inner_sum_trees) -
grass_v * (1 - inner_sum_grasses)
)
vegetation_structure_change[vegetation_structure_change < 0] <- 0
vegetation_structure_change[!is.finite(vegetation_structure_change)] <- 0
return(vegetation_structure_change)
}
################# further EcoRisk utility functions ##################
t_sigmoid_trafo <- function(x) {
return(-1 / exp(3) + (1 + 1 / exp(3)) / (1 + exp(-6 * (x - 0.5))))
}
balance <- function(v1, v2) {
return(1 - sum(v1 * v2) / (sqrt(sum(v1 * v1)) * sqrt(sum(v2 * v2))))
}
std_cellwise <- function(a) {
return(apply(a, 1, stats::sd))
}
global_yearly_weighted_mean <- function(a, cell_area) {
# a is matrix with dim=c(cells,years)
# cell_area the corresponding cell_area array with dim=c(cells)
return(
sum(a * cell_area, na.rm = TRUE) /
(length(a[1, ]) * sum(cell_area, na.rm = TRUE))
)
}
globally_weighted_mean_foreach_var <- function(x, cell_area) { # nolint
# x is matrix with dim=c(ncells,vars)
# if vars==1 inflate x
le <- length(x)
if (le == length(cell_area)) dim(x) <- c(le, 1)
# cell_area the corresponding cell_area array to x with dim=c(ncells)
return(colSums(x * cell_area, na.rm = TRUE) / sum(cell_area, na.rm = TRUE))
}
s_change_to_var_ratio <- function(x, s) {
return(1 / (1 + exp(-4 * (x / s - 2))))
}
#' based on Heyder 2011 eq. 6-9; epsilon case handling from code
#' by Sebastian Ostberg (not documented in papers)
#' @param ref mean reference state vector of dimension c(ncells,variables)
#' @param scen mean scenario state vector of dimension c(ncells,variables)
#' @param epsilon threshold for variables to be treated as 0
#'
#' @returns the length of the difference vector for each cell
state_diff_local <- function(ref, scen, epsilon = 10^-4) {
# Ostberg code: case change_metric_lu_comparison_jun2013.c
di <- dim(ref)
# generally normalize the scenario state vector by the reference state
s_scen <- scen / ref
s_ref <- array(1, dim = di) # initialize
# for variables in places, where ref is small (<epsilon),
# but scen larger (Ostberg code, line 798)
# Sebastian set back scenario and reference vector, to keep the unscaled
# values (Ostberg code, line 804)
cells_ref0 <- abs(ref) < epsilon & abs(scen) > epsilon
s_scen[cells_ref0] <- scen[cells_ref0]
s_ref[cells_ref0] <- ref[cells_ref0]
# for variables in places, where ref and scen are small (<epsilon),
# return 0 (both are 1, difference is 0) (Ostberg code, line 809)
s_scen[abs(ref) < epsilon & abs(scen) < epsilon] <- 1 # no change
# normalize both state vectors by the sqrt(amount of state variables) to
# ensure length(s_ref)==1 (this is part of the weighting in the Ostberg
# code)
s_ref <- s_ref / sqrt(di[2])
s_scen <- s_scen / sqrt(di[2])
# length of the local difference vector s_scen (sl2) - s_ref (sl1)
return(sqrt(rowSums((s_scen - s_ref) * (s_scen - s_ref))))
}
#' c based on Heyder 2011 eq. 10-13
#'
#' @param ref mean reference state vector of dimension c(ncells,variables)
#' @param scen mean scenario state vector of dimension c(ncells,variables)
#' @param cell_area area of each cell as a vector of dim=c(ncells)
#' @param epsilon threshold for variables to be treated as 0
#'
#' @returns the length of the difference vector for each cell
state_diff_global <- function(ref, scen, cell_area, epsilon = 10^-4) {
di <- dim(ref)
ncells <- di[1]
global_mean_ref <- globally_weighted_mean_foreach_var(ref, cell_area)
global_mean_scen <- globally_weighted_mean_foreach_var(scen, cell_area)
# if global mean state in ref period is 0 (e.g. for landuse vars in pnv run?)
# take the mean scen state instead
cells_ref0 <- abs(global_mean_ref) < epsilon & abs(global_mean_scen) > epsilon
global_mean_ref[cells_ref0] <- global_mean_scen[cells_ref0]
# if both are 0 take 1, then the division is defined but 0 - 0 leads
# to no change, which is what EcoRisk should show
cells_both0 <- (
abs(global_mean_ref) < epsilon & abs(global_mean_scen) < epsilon
)
global_mean_ref[cells_both0] <- 1
norm <- rep(global_mean_ref, each = ncells)
dim(norm) <- dim(ref)
s_scen <- scen / norm
s_ref <- ref / norm
# normalize both state vectors by the sqrt(amount of state variables) to
# ensure length(s_ref)==1
# (this is part of the weighting in the Ostberg code)
s_ref <- s_ref / sqrt(di[2])
s_scen <- s_scen / sqrt(di[2])
# length of the difference vector s_scen (sl2) - s_ref (sl1) for each cell
return(sqrt(rowSums((s_scen - s_ref) * (s_scen - s_ref))))
}
calc_component <- function(ref, scen, local, cell_area) {
di <- dim(ref)
ncells <- di[1]
nyears <- di[2]
# calc mean ref and scen state
if (length(di) == 2) {
dim(ref) <- c(di, 1)
dim(scen) <- c(di, 1)
}
ref_mean <- apply(ref, c(1, 3), mean)
scen_mean <- apply(scen, c(1, 3), mean)
if (local) {
x <- t_sigmoid_trafo(state_diff_local(ref = ref_mean, scen = scen_mean))
} else {
x <- t_sigmoid_trafo(
state_diff_global(ref = ref_mean, scen = scen_mean, cell_area = cell_area)
)
}
# calculation of the change-to-variability ratio in my view is mathematically
# not correctly described in Heyder and Ostberg
# - the way I understand it: recalculate the c/g/b value for each year of the
# ref period compared to the mean of the ref period as "scenario" and then
# calc the variability (sd) of that
sigma_x_ref_list <- array(0, dim = c(ncells, nyears))
for (i in seq_len(nyears)) {
if (local) {
sigma_x_ref_list[, i] <- t_sigmoid_trafo(
state_diff_local(ref = ref_mean, scen = ref[, i, ])
)
} else {
sigma_x_ref_list[, i] <- t_sigmoid_trafo(
state_diff_global(
ref = ref_mean,
scen = ref[, i, ],
cell_area = cell_area
)
)
}
}
sigma_x_ref <- apply(sigma_x_ref_list, 1, stats::sd)
c2vr <- s_change_to_var_ratio(x, sigma_x_ref)
return(
list(
full = x * c2vr,
value = x,
var = c2vr
)
)
}
balance_shift <- function(ref, scen, epsilon = 10^-4) {
# param ref with dimension c(ncells,nvars)
# param scen with dimension c(ncells,nvars)
if (is.null(dim(ref))) dim(ref) <- c(length(ref), 1)
# first normalize as for local change
s_scen <- scen / ref
s_ref <- array(1, dim = dim(ref))
# for variables in places, where ref is small (<epsilon), but scen larger
# (Ostberg code, line 798/vector length calc in line 837)
# set back scenario vector, to keep the unscaled values (Ostberg code,
# line 805)
s_scen[abs(ref) < epsilon & abs(scen) > epsilon] <- (
scen[abs(ref) < epsilon & abs(scen) > epsilon]
)
# for variables in places, where ref and scen are small (<epsilon),
# set scen to 1 (both are 1, difference is 0 -- no change) (Ostberg code,
# line 809)
# results in no change
s_scen[abs(ref) < epsilon & abs(scen) < epsilon] <- 1
# absa(_ts) in Sebastians Ostberg's code
abs_ref <- sqrt(rowSums(s_ref * s_ref))
# absb(_ts) in Sebastian Ostberg's code
abs_scen <- sqrt(rowSums(s_scen * s_scen))
# =1-angle_ts
b1 <- 1 - (rowSums(s_ref * s_scen) / abs_ref / abs_scen) # todo: hier wird alles 0
# restrain to the maximum range for the acos function
b1[b1 < 0] <- 0
b1[b1 > 2] <- 2
angle <- acos(1 - b1) * 360 / 2 / pi
angle[b1 == 1] <- 0
b <- b1 * 2
b[angle > 60] <- 1
return(b)
}
calc_ecosystem_balance <- function(ref, scen) {
di <- dim(ref)
ncells <- di[1]
nyears <- di[2]
if (length(di) == 2) {
dim(ref) <- c(di, 1)
dim(scen) <- c(di, 1)
}
ref_mean <- apply(ref, c(1, 3), mean)
scen_mean <- apply(scen, c(1, 3), mean)
b <- balance_shift(ref = ref_mean, scen = scen_mean)
# calculation of the change-to-variability ratio in my view is mathematically
# not correctly described in Heyder and Ostberg
# - the way I understand it: recalculate the c/g/b value for each year of the
# ref period compared to the mean
# of the ref period as "scenario" and then calc the variability (sd) of that
sigma_b_ref_list <- array(0, dim = c(ncells, nyears))
for (i in seq_len(nyears)) {
sigma_b_ref_list[, i] <- balance_shift(ref = ref_mean, scen = ref[, i, ])
}
sigma_b_ref <- apply(sigma_b_ref_list, 1, stats::sd)
c2vr <- s_change_to_var_ratio(b, sigma_b_ref)
return(
list(
full = b * c2vr,
value = b,
var = c2vr
)
)
}
#' Create modified EcoRisk data file
#'
#' Function to create a modified EcoRisk data file where each reference cell is
#' compared to the average reference biome cell. The scenario period is
#' overwritten with the original reference period and all reference cells are
#' set to the average cell of the prescribed reference biome ref_biom
#'
#' @param data_file_in path to input data
#' @param data_file_out path to save modified data to
#' @param biome_classes_in biome classes object as returned from classify_biomes
#' @param ref_biom reference biome from biome classes that all cells should
#' be compared to
#'
#' @export
replace_ref_data_with_average_ref_biome_cell <- function(
# nolint
data_file_in,
data_file_out,
biome_classes_in,
ref_biom) {
if (data_file_in == data_file_out) {
stop(
"Same file for input and output of data, would overwrite ",
"original data. Aborting."
)
}
load(data_file_in)
ref_cells <- which(biome_classes_in$biome_id == ref_biom)
# first set all scen vars to the ref vars # [1:64240, 1:30, 1:10]
state_scen <- state_ref
fpc_scen <- fpc_ref
bft_scen <- bft_ref
cft_scen <- cft_ref
di_state <- dim(state_scen)
di_fpc <- dim(fpc_scen)
di_bft <- dim(bft_scen)
di_cft <- dim(cft_scen)
# now replace all ref cells with that of the mean ref biom cell
# FS 2022-08-10: keeping the year-to-year variation
if (length(ref_cells) == 1) {
av_year_state <- state_scen[ref_cells, , ]
fpc_ref <- rep(fpc_scen[ref_cells, , ], each = di_fpc[1])
bft_ref <- rep(bft_scen[ref_cells, , ], each = di_bft[1])
cft_ref <- rep(cft_scen[ref_cells, , ], each = di_cft[1])
} else {
av_year_state <- apply(state_scen[ref_cells, , ], c(2, 3), mean)
fpc_ref <- rep(
apply(fpc_scen[ref_cells, , ], c(2, 3), mean),
each = di_fpc[1]
)
bft_ref <- rep(
apply(bft_scen[ref_cells, , ], c(2, 3), mean),
each = di_bft[1]
)
cft_ref <- rep(
apply(cft_scen[ref_cells, , ], c(2, 3), mean),
each = di_cft[1]
)
}
state_ref <- rep(av_year_state, each = di_state[1])
dim(state_ref) <- di_state
dimnames(state_ref) <- dimnames(state_scen)
# is the same for each year, thus for the mean just take one year
# mean_state_ref <- rep(colMeans(av_year_state), each = di_state[1])
# FS: mean states were removed from data file, removing also here
dim(fpc_ref) <- di_fpc
dimnames(fpc_ref) <- dimnames(fpc_scen)
dim(bft_ref) <- di_bft
dimnames(bft_ref) <- dimnames(bft_scen)
dim(cft_ref) <- di_cft
dimnames(cft_ref) <- dimnames(cft_scen)
# and write out the modified data
# save(state_ref,mean_state_ref,state_scen,mean_state_scen,fpc_ref,fpc_scen,
# bft_ref,bft_scen,cft_ref,cft_scen,lat,lon,cell_area,file = data_file_out)
save(state_ref,
state_scen,
fpc_ref,
fpc_scen,
bft_ref,
bft_scen,
cft_ref,
cft_scen,
lat,
lon,
cell_area,
file = data_file_out
)
}
#' Create modified EcoRisk data for crosstable
#'
#' Function to create a modified EcoRisk data file where for each biome
#' the average scenario cell is compared to the average scenario cell of all
#' other biomes. This can then be used to compute a crosstable with the average
#' difference between each of them as in the SI of Ostberg et al. 2013
#' (Critical impacts of global warming on land ecosystems)
#'
#' @param data_file_in path to input data
#' @param data_file_out path to save modified data to
#' @param biome_classes_in biome classes object as returned from classify_biomes
#' @param pick_cells pick one specific cell as representative for the biome
#' instead of computing the average state
#' @param baseline_ref logical, use reference state as baseline?
#' default is FALSE - use scenario state
#'
#' @return c2vr array to be used in the ecorisk call
#'
#' @export
ecorisk_cross_table <- function(data_file_in,
data_file_out,
biome_classes_in,
pick_cells = NULL,
baseline_ref = FALSE) {
if (data_file_in == data_file_out) {
stop(
"Same file for input and output of data, would overwrite original data. ",
"Aborting."
)
}
load(data_file_in)
# calculate ecorisk to get the variability
ecorisk_c2vr <- drop(ecorisk_wrapper(
path_ref = NULL,
path_scen = NULL,
read_saved_data = TRUE,
nitrogen = TRUE,
varnames = vars_metrics,
weighting = "equal",
save_data = data_file_in,
save_ecorisk = NULL,
time_span_reference = as.character(1550:1579),
time_span_scenario = as.character(1985:2014),
dimensions_only_local = FALSE
)$c2vr)
if (baseline_ref) {
# save reference state vectors, they contain relevant data (scen can go)
state_sav <- state_ref
fpc_sav <- fpc_ref
bft_sav <- bft_ref
cft_sav <- cft_ref
} else {
# save scenario state vectors, they contain relevant data (ref can go)
state_sav <- state_scen
fpc_sav <- fpc_scen
bft_sav <- bft_scen
cft_sav <- cft_scen
}
nbiomes <- max(biome_classes_in$biome_id) # by default 19
state_ref <- array(0, dim = c(nbiomes, nbiomes, dim(state_sav)[2:3]))
state_scen <- state_ref
fpc_ref <- array(0, dim = c(nbiomes, nbiomes, dim(fpc_sav)[2:3]))
fpc_scen <- fpc_ref
bft_ref <- array(0, dim = c(nbiomes, nbiomes, dim(bft_sav)[2:3]))
bft_scen <- bft_ref
cft_ref <- array(0, dim = c(nbiomes, nbiomes, dim(cft_sav)[2:3]))
cft_scen <- cft_ref
c2vr <- array(0, dim = c(4, nbiomes))
# now replace all ref cells with that of the mean ref biome cell
for (b in sort(unique(biome_classes_in$biome_id))) {
ref_cells <- which(biome_classes_in$biome_id == b)
if (is.null(pick_cells)) {
if (length(ref_cells) == 1) {
# average over cells, keeping the average year-to-year variation
av_state <- state_sav[ref_cells, , ]
av_fpc <- fpc_sav[ref_cells, , ]
av_bft <- bft_sav[ref_cells, , ]
av_cft <- cft_sav[ref_cells, , ]
av_c2vr <- ecorisk_c2vr[, ref_cells]
} else {
# average over cells, keeping the average year-to-year variation
av_state <- apply(state_sav[ref_cells, , ], c(2, 3), mean)
av_fpc <- apply(fpc_sav[ref_cells, , ], c(2, 3), mean)
av_bft <- apply(bft_sav[ref_cells, , ], c(2, 3), mean)
av_cft <- apply(cft_sav[ref_cells, , ], c(2, 3), mean)
av_c2vr <- apply(ecorisk_c2vr[, ref_cells], c(1), mean)
}
} else {
av_state <- state_sav[pick_cells[b], , ]
av_fpc <- fpc_sav[pick_cells[b], , ]
av_bft <- bft_sav[pick_cells[b], , ]
av_cft <- cft_sav[pick_cells[b], , ]
av_c2vr <- ecorisk_c2vr[, pick_cells[b]]
}
state_ref[b, , , ] <- rep(av_state, each = nbiomes)
state_scen[, b, , ] <- rep(av_state, each = nbiomes)
mean_state_ref <- apply(state_ref, c(1, 3), mean)
mean_state_scen <- apply(state_scen, c(1, 3), mean)
fpc_ref[b, , , ] <- rep(av_fpc, each = nbiomes)
fpc_scen[, b, , ] <- rep(av_fpc, each = nbiomes)
bft_ref[b, , , ] <- rep(av_bft, each = nbiomes)
bft_scen[, b, , ] <- rep(av_bft, each = nbiomes)
cft_ref[b, , , ] <- rep(av_cft, each = nbiomes)
cft_scen[, b, , ] <- rep(av_cft, each = nbiomes)
c2vr[, b] <- av_c2vr
}
dim(state_ref) <- c(nbiomes * nbiomes, dim(state_sav)[2:3])
dim(state_scen) <- c(nbiomes * nbiomes, dim(state_sav)[2:3])
dim(fpc_ref) <- c(nbiomes * nbiomes, dim(fpc_sav)[2:3])
dim(fpc_scen) <- c(nbiomes * nbiomes, dim(fpc_sav)[2:3])
dim(bft_ref) <- c(nbiomes * nbiomes, dim(bft_sav)[2:3])
dim(bft_scen) <- c(nbiomes * nbiomes, dim(bft_sav)[2:3])
dim(cft_ref) <- c(nbiomes * nbiomes, dim(cft_sav)[2:3])
dim(cft_scen) <- c(nbiomes * nbiomes, dim(cft_sav)[2:3])
dimnames(state_ref) <- list(
cell = as.character(seq_len(nbiomes * nbiomes)),
year = dimnames(state_sav)$year,
class = dimnames(state_sav)$class
)
dimnames(state_scen) <- dimnames(state_ref)
dimnames(fpc_ref) <- list(
cell = as.character(seq_len(nbiomes * nbiomes)),
band = dimnames(fpc_sav)$band,
year = dimnames(fpc_sav)$year
)
dimnames(fpc_scen) <- dimnames(fpc_ref)
dimnames(bft_ref) <- list(
cell = as.character(seq_len(nbiomes * nbiomes)),
band = dimnames(bft_sav)$band,
year = dimnames(bft_sav)$year
)
dimnames(bft_scen) <- dimnames(bft_ref)
dimnames(cft_ref) <- list(
cell = as.character(seq_len(nbiomes * nbiomes)),
band = dimnames(cft_sav)$band,
year = dimnames(cft_sav)$year
)
dimnames(cft_scen) <- dimnames(cft_ref)
dimnames(c2vr) <- list(component = c("vs", "lc", "gi", "eb"), biome = biome_classes_in$biome_names)
lat <- rep(0, nbiomes * nbiomes)
lon <- rep(1, nbiomes * nbiomes)
cell_area <- rep(2, nbiomes * nbiomes)
# and write out the modified data
save(state_ref,
mean_state_ref,
state_scen,
mean_state_scen,
fpc_ref,
fpc_scen,
bft_ref,
bft_scen,
cft_ref,
cft_scen,
lat,
lon,
cell_area,
file = data_file_out
)
return(c2vr)
}
################# biome (dis-)aggregation functions ##################
#' Get biome names
#'
#' Returns biome names with variable length (abbreviated, short, or full)
#'
#' @param biome_name_length integer chose from 1,2,3 for abbreviated, short,
#' or full biome names
#'
#' @export
get_biome_names <- function(biome_name_length = 2) {
biome_mapping <- utils::read.csv(
file = system.file(
"extdata",
"biomes.csv",
package = "biospheremetrics"
),
sep = ";"
)
if (biome_name_length == 1) {
biome_class_names <- biome_mapping$abbreviation
} else if (biome_name_length == 2) {
biome_class_names <- biome_mapping$short_name
} else if (biome_name_length == 3) {
biome_class_names <- biome_mapping$name
} else {
stop(
"Value for parameter biome_name_length out of range 1,2,3 - ",
"was given as: ",
biome_name_length
)
}
return(biome_class_names)
}
#' Averages EcoRisk values across regions
#'
#' Returns the average value across either 4 regions or all (19) biomes for
#' EcoRisk and each of the subcomponents for each
#'
#' @param data List object, of which every item should be disaggregated
#' @param biome_class biome class list object as returned by classify_biomes
#' @param type string controlling whether to return minimum, mean, maximum
#' ("minmeanmax") or Q10,Q50,Q90 ("quantile") - default: "quantile"
#' @param classes string for into how many regions should be disaggregated
#' "4biomes" (tropics/temperate/boreal/arctic) or "allbiomes"
#'
#' @examples
#' \dontrun{
#' disaggregate_into_biomes(
#' ecorisk = ecorisk,
#' biome_class = biome_classes,
#' type = "quantile", classes = "4biomes"
#' )
#' }
#' @export
disaggregate_into_biomes <- function(data, # nolint
biome_class,
type = "quantile",
classes = "4biomes") {
di <- dim(data[[1]])
comp_names <- names(data)
if (type == "minmeanmax") {
type_names <- c("min", "mean", "max")
} else if (type == "quantile") {
type_names <- c("Q10", "Q50", "Q90")
}
if (length(di) > 1) {
slices <- di[2]
} else {
slices <- 1
}
if (classes == "4biomes") {
tropics <- c(1, 2, 9, 10, 11)
temperate <- c(3, 4, 5, 12, 13, 14)
boreal <- c(6, 7, 8)
arctic <- c(15, 16)
cell_list <- list(
tropical_cells = which(biome_class$biome_id %in% tropics),
temperate_cells = which(biome_class$biome_id %in% temperate),
boreal_cells = which(biome_class$biome_id %in% boreal),
arctic_cells = which(biome_class$biome_id %in% arctic)
)
nclasses <- 4
} else if (classes == "allbiomes") {
nclasses <- max(unique(biome_class$biome_id))
} else {
stop(
"Unknown parameter classes: ",
classes,
", should be either '4biomes' or 'allbiomes'"
)
}
data_dims <- length(data)
data_biomes <- array(0, dim = c(nclasses, data_dims, 3, slices))
if (classes == "4biomes") { # aggregate to trop/temp/boreal/arctic
for (s in seq_len(slices)) {
for (b in seq_len(nclasses)) {
for (cc in seq_len(data_dims)) {
if (type == "minmeanmax") {
data_biomes[b, cc, , s] <- c(
min(data[[cc]][cell_list[[b]], s], na.rm = TRUE),
mean(data[[cc]][cell_list[[b]], s], na.rm = TRUE),
max(data[[cc]][cell_list[[b]], s], na.rm = TRUE)
)
} else if (type == "quantile") {
data_biomes[b, cc, , s] <- c(
stats::quantile(
data[[cc]][cell_list[[b]], s],
probs = c(0.1, 0.5, 0.9),
na.rm = TRUE
)
)
} else {
stop(paste(
"type", type,
"unknown. please choose either 'quantile' or 'minmeanmax'"
))
} # end if
} # end for
} # end for
} # end for
biome_names <- c("tropics", "temperate", "boreal", "arctic")
dimnames(data_biomes) <- list(biome_names, comp_names, type_names, seq_len(slices))
} else if (classes == "allbiomes") { # calculate all biomes separately
for (s in seq_len(slices)) {
for (b in seq_len(nclasses)) {
for (cc in seq_len(data_dims)) {
if (type == "minmeanmax") {
data_biomes[b, cc, , s] <- c(
min(data[[cc]][which(biome_class$biome_id == b), s], na.rm = TRUE),
mean(data[[cc]][which(biome_class$biome_id == b), s], na.rm = TRUE), # nolint
max(data[[cc]][which(biome_class$biome_id == b), s], na.rm = TRUE)
)
} else if (type == "quantile") {
data_biomes[b, cc, , s] <- c(
stats::quantile(
data[[cc]][which(biome_class$biome_id == b), s],
probs = c(0.1, 0.5, 0.9),
na.rm = TRUE
)
)
} else {
stop(paste(
"type", type,
"unknown. please choose either 'quantile' or 'minmeanmax'"
))
} # end if
} # end for
} # end for
} # end for
biome_names <- biome_class$biome_names
dimnames(data_biomes) <- list(
biome_names,
comp_names,
type_names,
seq_len(slices)
)
} else {
stop(
"Unknown parameter classes: ",
classes,
", should be either '4biomes' or 'allbiomes'"
)
}
return(drop(data_biomes))
}
#' Calculate ecorisk with each biomes average cell
#'
#' Function to calculate ecorisk with each biomes average cell
#' as a measure of internal variability
#'
#' @param biome_classes biome classes object as returned by classify biomes,
#' calculated for data_file_base
#' @param data_file_base base EcoRisk to compute differences with (only ref is
#' relevant)
#' @param intra_biome_distrib_file file to additionally write results to
#' @param create create new modified files, or read already existing ones?
#' @param res how finegrained the distribution should be (resolution)
#' @param plotting whether plots for each biome should be created
#' @param plot_folder folder to plot into
#' @param time_span_reference suitable 30 year reference period (e.g.
#' c(1901,1930), c(1550,1579))
#' @return data object with distibution - dim: c(biomes,ecorisk_variables,bins)
#'
#' @export
calculate_within_biome_diffs <- function(biome_classes, # nolint
data_file_base,
intra_biome_distrib_file,
create = FALSE,
nitrogen = TRUE,
res = 0.05,
plotting = FALSE,
plot_folder,
time_span_reference,
vars_ecorisk,
ecorisk_components = 13) {
biomes_abbrv <- get_biome_names(1)
# nbiomes, nEcoRiskvars, nHISTclasses
intra_biome_distrib <- array(
0,
dim = c(length(biome_classes$biome_names), ecorisk_components, 1 / res)
)
# start
for (b in sort(unique(biome_classes$biome_id))) {
filebase <- strsplit(data_file_base, "_data.RData")[[1]]
message(
"Calculating differences with biome ", b, " (",
biome_classes$biome_names[b], ")"
)
data_file <- paste0(
filebase, "_compared_to_average_", biomes_abbrv[b], "_data.RData"
)
ecorisk_file <- paste0(
filebase, "_compared_to_average_", biomes_abbrv[b], "_gamma.RData"
)
if (create) {
replace_ref_data_with_average_ref_biome_cell(
data_file_in = data_file_base,
data_file_out = data_file,
biome_classes_in = biome_classes,
ref_biom = b
)
ecorisk <- ecorisk_wrapper(
# does not need to be specified, as data is read from file
path_ref = NULL,
# does not need to be specified, as data is read from file
path_scen = NULL,
read_saved_data = TRUE,
nitrogen = nitrogen,
weighting = "equal",
save_data = data_file,
save_ecorisk = ecorisk_file,
varnames = vars_ecorisk,
time_span_reference = time_span_reference,
time_span_scenario = time_span_reference,
dimensions_only_local = FALSE
)
} else {
# contains ecorisk list object
load(ecorisk_file)
}
# compute average values per focus biom
ref_cells <- which(biome_classes$biome_id == b)
for (v in seq_len(10)) {
intra_biome_distrib[b, v, ] <- graphics::hist(
ecorisk[[v]][ref_cells],
breaks = seq(0, 1, res), plot = FALSE
)$counts
intra_biome_distrib[b, v, ] <- (
intra_biome_distrib[b, v, ] / sum(intra_biome_distrib[b, v, ])
)
}
if (plotting) {
plot_ecorisk_map(
file = paste0(
plot_folder, "/compare_ecorisk_to_", biomes_abbrv[b], ".png"
),
focus_biome = b, biome_classes = biome_classes$biome_id,
data = ecorisk$ecorisk_total, title = biome_classes$biome_names[b],
legendtitle = "",
eps = FALSE,
title_size = 2,
leg_yes = TRUE
)
plot_ecorisk_map(
file = paste0(
plot_folder, "/compare_vegetation_structure_change_to_", biomes_abbrv[b], ".png" # nolint
),
focus_biome = b, biome_classes = biome_classes$biome_id,
data = ecorisk$vegetation_structure_change,
title = biome_classes$biome_names[b],
legendtitle = "",
eps = FALSE,
title_size = 2,
leg_yes = TRUE
)
plot_ecorisk_map(
file = paste0(
plot_folder, "/compare_gi_to_", biomes_abbrv[b], ".png"
),
focus_biome = b,
biome_classes = biome_classes$biome_id,
data = ecorisk$global_importance,
title = biome_classes$biome_names[b],
legendtitle = "",
eps = FALSE,
title_size = 2,
leg_yes = TRUE
)
plot_ecorisk_map(
file = paste0(
plot_folder, "/compare_lc_to_", biomes_abbrv[b], ".png"
),
focus_biome = b,
biome_classes = biome_classes$biome_id,
data = ecorisk$local_change,
title = biome_classes$biome_names[b],
legendtitle = "",
eps = FALSE,
title_size = 2,
leg_yes = TRUE
)
plot_ecorisk_map(
file = paste0(
plot_folder, "/compare_eb_to_", biomes_abbrv[b], ".png"
),
focus_biome = b,
biome_classes = biome_classes$biome_id,
data = ecorisk$ecosystem_balance,
title = biome_classes$biome_names[b],
legendtitle = "",
eps = FALSE,
title_size = 2,
leg_yes = TRUE
)
} # end if plotting
}
ecorisk_dimensions <- names(ecorisk)
dim(intra_biome_distrib) <- c(biome = length(biome_classes$biome_names), variable = ecorisk_components, bin = 1 / res)
dimnames(intra_biome_distrib) <- list(
biome = biomes_abbrv, variable = ecorisk_dimensions, bin = seq(res, 1, res)
)
save(intra_biome_distrib, file = intra_biome_distrib_file)
return(intra_biome_distrib)
}
################# EcoRisk plotting functions ##################
#' Plot distribution of similarity within biomes
#'
#' Function to plot the distribution of similarity within biomes
#'
#' @param data data object with distibution - as returned by
#' calculateWithInBiomeDiffs for each subcategory of ecorisk.
#' dim: c(biomes,bins)
#' @param biomes_abbrv to mask the focus_biome from
#' @param scale scaling factor for distribution. defaults to 1
#' @param title character string title for plot, default empty
#' @param legendtitle character string legend title, default empty
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_biome_internal_distribution_to_screen <- function(
# nolint
data,
biomes_abbrv,
title = "",
legendtitle = "",
scale = 1,
palette = NULL) {
di <- dim(data)
bins <- di["bin"]
res <- 1 / bins
biomes <- di["biome"]
if (is.null(palette)) {
palette <- c(
"white", "steelblue1", "royalblue",
RColorBrewer::brewer.pal(7, "YlOrRd")
)
}
col_index <- floor(seq(res / 2, 1 - res / 2, res) * 10) + 1
graphics::par(mar = c(2, 4, 0, 0), oma = c(0, 0, 0, 0)) # bltr
graphics::plot(NA,
xlim = c(0, 1), ylim = c(0, 20), xlab = "EcoRisk",
main = title, axes = FALSE, ylab = ""
)
graphics::axis(side = 2, labels = FALSE, at = seq_len(biomes))
brks <- seq(0, 1, 0.1)
fields::image.plot(
legend.only = TRUE, col = palette,
useRaster = FALSE, breaks = brks, horizontal = TRUE,
lab.breaks = brks, legend.shrink = 0.925,
legend.args = list("", side = 3, font = 2, line = 1.5)
)
graphics::mtext(biomes_abbrv, side = 2, line = 1, at = seq_len(biomes), las = 2)
for (b in seq_len(biomes)) {
graphics::rect(
xleft = seq(0, 1 - res, res),
xright = seq(res, 1, res),
ybottom = b,
ytop = b + data[b, ] * scale,
col = palette[col_index]
)
}
}
#' Plot to file distribution of similarity within biomes
#'
#' Function to plot to file the distribution of similarity within biomes
#'
#' @param data data object with distibution - as returned by
#' calculateWithInBiomeDiffs. dim: c(biomes,bins)
#' @param file to write into
#' @param biomes_abbrv to mask the focus_biome from
#' @param scale scaling factor for distribution. defaults to 1
#' @param title character string title for plot, default empty
#' @param legendtitle character string legend title, default empty
#' @param eps write as eps or png (default: FALSE -> png)
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_biome_internal_distribution <- function(
# nolint
data,
file,
biomes_abbrv,
scale,
title = "",
legendtitle = "",
eps = FALSE,
palette = NULL) {
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len((length(file) - 1))], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 8,
height = 16, paper = "special"
)
} else {
grDevices::png(file,
width = 3, height = 6, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
plot_biome_internal_distribution_to_screen(
data = data, biomes_abbrv = biomes_abbrv, scale = scale, title = title,
legendtitle = legendtitle, palette = palette
)
grDevices::dev.off()
}
#' Plot EcoRisk map to screen
#'
#' Function to plot a global map of EcoRisk values [0-1] per grid cell to screen
#'
#' @param data folder of reference run
#' @param focus_biome highlight the biome with this id and desaturate all other
#' (default NULL -- no highlight)
#' @param biome_classes to mask the focus_biome from
#' @param title character string title for plot, default empty
#' @param legendtitle character string legend title
#' @param leg_yes logical. whether to plot legend or not. defaults to TRUE
#' @param leg_scale scaling factor for legend. defaults to 1
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_ecorisk_map_to_screen <- function(
data,
focus_biome = NULL,
biome_classes = NULL,
title = "",
legendtitle = "",
title_size = 1,
leg_yes = TRUE,
palette = NULL) {
brks <- seq(0, 1, 0.1)
data[data < brks[1]] <- brks[1]
data[data > brks[length(brks)]] <- brks[length(brks)]
if (is.null(palette)) {
palette <- c(
"white", "steelblue1", "royalblue",
RColorBrewer::brewer.pal(7, "YlOrRd")
)
}
if (!is.null(focus_biome)) {
focus <- data
focus[!(biome_classes == focus_biome)] <- NA
palette_low_sat <- grDevices::adjustcolor(palette, alpha.f = 0.25)
ra_f <- terra::rast(ncols = 720, nrows = 360)
ra_f[terra::cellFromXY(ra_f, cbind(lon, lat))] <- focus
}
ra <- terra::rast(ncols = 720, nrows = 360)
ra[terra::cellFromXY(ra, cbind(lon, lat))] <- data
range <- range(data)
extent <- terra::ext(c(-180, 180, -60, 90))
graphics::par(mar = c(0, 0, 1, 3), oma = c(0, 0, 0, 0), bty = "n")
if (is.null(focus_biome)) {
terra::plot(ra,
ext = extent, breaks = brks, col = palette, main = "",
legend = FALSE, axes = FALSE
)
} else {
terra::plot(ra,
ext = extent, breaks = brks, col = palette_low_sat,
main = "", legend = FALSE, axes = FALSE
)
terra::plot(ra_f,
ext = extent, breaks = brks, col = palette, main = "",
legend = FALSE, axes = FALSE, add = TRUE
)
}
title(main = title, line = -2, cex.main = title_size)
maps::map("world", add = TRUE, res = 0.4, lwd = 0.25, ylim = c(-60, 90))
if (leg_yes) {
fields::image.plot(
legend.only = TRUE, col = palette, breaks = brks, zlim = range,
lab.breaks = brks, legend.shrink = 0.7,
legend.args = list(legendtitle, side = 3, font = 2, line = 1)
) # removed zlim
}
}
#' Plot EcoRisk map to file
#'
#' Function to plot a global map of EcoRisk values [0-1] per grid cell to file
#'
#' @param data folder of reference run
#' @param file to write into
#' @param focus_biome highlight the biome with this id and desaturate all other
#' (default NULL -- no highlight)
#' @param biome_classes to mask the focus_biome from
#' @param title character string title for plot, default empty
#' @param legendtitle character string legend title
#' @param eps write as eps or png
#' @param leg_yes logical. whether to plot legend or not. defaults to TRUE
#' @param leg_scale scaling factor for legend. defaults to 1
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_ecorisk_map <- function(
data,
file,
focus_biome = NULL,
biome_classes = NULL,
title = "",
legendtitle = "",
eps = FALSE,
title_size = 1,
leg_yes = TRUE,
palette = NULL) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 22,
height = 8.5, paper = "special"
)
} else {
grDevices::png(file,
width = 7.25, height = 3.5, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
plot_ecorisk_map_to_screen(
data = data,
focus_biome = focus_biome,
biome_classes = biome_classes,
title = title,
legendtitle = legendtitle,
title_size = title_size,
leg_yes = leg_yes,
palette = palette
)
grDevices::dev.off()
}
#' Plot radial EcoRisk plot to screen
#'
#' Function to plot an aggregated radial status of EcoRisk values [0-1]
#' for the different sub-categories to screen
#'
#' @param data EcoRisk data array c([nEcoRiskcomponents],
#' 3[min,mean,max])
#' @param title character string title for plot, default empty
#' @param zoom scaling factor for circle plot. defaults to 1
#' @param type plot type, 'legend1' for variable and color legend,
#' 'legend2' for value legend, or 'regular' (default setting)
#' for the regular EcoRisk plot
#' @param title_size scaling factor for tile. defaults to 1
#'
#' @return None
#'
#' @export
plot_ecorisk_radial_to_screen <- function(data, # nolint
title = "",
zoom = 1.0,
type = "regular",
title_size = 2,
titleline = -2,
use_quantile = TRUE) {
ecorisk_dims <- length(data[, 1])
if (ecorisk_dims == 8) {
# names <- c(
# ecorisk = "ecorisk", deltav = "vegetation\nstructure",
# local = "local\nchange", global = "global\nimportance",
# balance = "ecosystem\nbalance", cstocks = "carbon\nstocks",
# cfluxes = "carbon fluxes", wfluxes = "water fluxes"
# )
# take the names of the ecorisk list dimensions, removing "_total"
names <- gsub("_", "\n", gsub("_total", "", dimnames(data)[[1]]))
# c(blue-green, yellow, violet, red, blue, orange, green, pink, grey,
# purple, green-blue, yellow-orange)
set <- RColorBrewer::brewer.pal(12, "Set3")
colz <- set[c(4, 7, 8, 11, 1, 10, 5, 6)]
# ecorisk vs lc gi eb ct wt nt
angles <- matrix(
c(90, 270, 216, 252, 180, 216, 144, 180, 108, 144, -18, 18, -54, -18, 18, 54),
byrow = TRUE,
nrow = length(colz)
)
} else {
stop("Unknown number of dimensions for ecorisk data:", ecorisk_dims)
}
graphics::par(oma = c(0, 0, 0, 0), mar = c(0, 0, 0, 0))
graphics::plot(c(-zoom, zoom), c(-zoom, zoom),
type = "n", axes = FALSE,
ann = FALSE, asp = 1, main = ""
)
graphics::title(main = title, line = titleline, cex.main = title_size)
if (type == "legend1") {
circlize::draw.sector(0, 360, rou1 = 1)
ro <- c(1, 1.1, 0.8, 1.1, 0.8, 1, 1, 1, 1, 1, 1)
for (i in seq_along(angles[, 1])) {
circlize::draw.sector(
start.degree = angles[i, 1] + 90,
end.degree = angles[i, 2] + 90,
col = colz[i],
clock.wise = FALSE,
rou1 = 0,
rou2 = ro[i],
border = "black"
)
}
if (ecorisk_dims == 8) {
graphics::text(names,
x = c(1.1, 1.0, 0.2, -0.8, -1.6, -0.25, 0.7, -1.3),
y = c(-0.15, -0.9, -1.3, -1.3, -0.9, 1.2, 1, 1),
adj = 0
)
} else {
stop("Unknown number of dimensions for ecorisk data:", ecorisk_dims)
}
# line lc
circlize::draw.sector(
start.degree = (angles[3, 1] + angles[3, 2]) / 2 + 90,
end.degree = (angles[3, 1] + angles[3, 2]) / 2 + 90,
rou1 = 0.7,
rou2 = 1.1
)
# line ecorisk
circlize::draw.sector(
start.degree = -9,
end.degree = -9,
rou1 = 0.9,
rou2 = 1.05
)
# line eb
circlize::draw.sector(
start.degree = (angles[5, 1] + angles[5, 2]) / 2 + 90,
end.degree = (angles[5, 1] + angles[5, 2]) / 2 + 90, rou1 = 0.7,
rou2 = 1.1
)
circlize::draw.sector(
start.degree = 180,
end.degree = 180,
clock.wise = FALSE,
rou1 = -1.2,
rou2 = 1.2,
border = "black",
lwd = 2
)
} else if (type == "legend2") {
graphics::text("+", x = 0, y = 0)
circlize::draw.sector(0, 360, rou1 = 1)
circlize::draw.sector(0, 360, rou1 = 0.65)
circlize::draw.sector(0, 360, rou1 = 0.3)
# sector
circlize::draw.sector(
start.degree = -18 + 90,
end.degree = 18 + 90,
clock.wise = FALSE,
rou1 = 0.55,
border = "black"
)
# uncertainty arrow
circlize::draw.sector(
start.degree = 90,
end.degree = 90,
clock.wise = FALSE,
rou1 = 0.4,
rou2 = 0.8,
border = "black"
)
# uncertainty lower
circlize::draw.sector(
start.degree = -9 + 90,
end.degree = 9 + 90,
clock.wise = FALSE,
rou1 = 0.8,
rou2 = 0.8,
border = "black"
)
# uncertainty upper
circlize::draw.sector(
start.degree = -9 + 90,
end.degree = 9 + 90,
clock.wise = FALSE,
rou1 = 0.4,
rou2 = 0.4,
border = "black"
)
# 0.3
circlize::draw.sector(
start.degree = 270 - 270,
end.degree = 270 - 270,
clock.wise = FALSE,
rou1 = 0.3,
rou2 = 1.3,
border = "black",
lty = "dashed"
)
# 0.65
circlize::draw.sector(
start.degree = 280 - 270,
end.degree = 280 - 270,
clock.wise = FALSE,
rou1 = 0.65,
rou2 = 1.3,
border = "black",
lty = "dashed"
)
# 1.0
circlize::draw.sector(
start.degree = 290 - 270,
end.degree = 290 - 270,
clock.wise = FALSE,
rou1 = 1,
rou2 = 1.3,
border = "black",
lty = "dashed"
)
graphics::text(c("0.3", "0.65", "1"),
x = c(1.4, 1.45, 1.25),
y = c(0, 0.25, 0.45)
)
# plot how the whiskers are calculated
# quantile case
if (use_quantile) {
graphics::text(c("Q90", "Q50", "Q10"),
x = c(-0.3, -0.29, -0.26),
y = c(0.8, 0.48, 0.35),
cex = 0.7
)
# minmeanmax case
} else {
graphics::text(c("max", "mean", "min"),
x = c(-0.3, -0.29, -0.26),
y = c(0.8, 0.48, 0.35),
cex = 0.7
)
}
} else if (type == "regular") {
circlize::draw.sector(180, 360, rou1 = 1, col = "gray80")
for (i in seq_along(angles[, 1])) {
mangle <- mean(angles[i, ])
if (i == 1) mangle <- -98
dmin <- data[i, 1]
dmedian <- data[i, 2]
dmax <- data[i, 3]
circlize::draw.sector(
start.degree = angles[i, 1] + 90,
end.degree = angles[i, 2] + 90, col = colz[i],
rou1 = dmedian,
clock.wise = FALSE,
border = "black"
)
# uncertainty arrow
circlize::draw.sector(
start.degree = mangle + 90,
end.degree = mangle + 90,
clock.wise = FALSE,
rou1 = dmin,
rou2 = dmax,
border = "black"
)
circlize::draw.sector(
start.degree = mangle - 9 + 90,
end.degree = mangle + 9 + 90,
clock.wise = FALSE,
rou1 = dmin,
rou2 = dmin,
border = "black"
)
# uncertainty upper
circlize::draw.sector(
start.degree = mangle - 9 + 90,
end.degree = mangle + 9 + 90,
clock.wise = FALSE,
rou1 = dmax,
rou2 = dmax,
border = "black"
)
}
circlize::draw.sector(0, 360, rou1 = 1)
circlize::draw.sector(0, 360, rou1 = 0.6)
circlize::draw.sector(0, 360, rou1 = 0.3)
circlize::draw.sector(
start.degree = 180,
end.degree = 180,
clock.wise = FALSE,
rou1 = -1.2,
rou2 = 1.2,
border = "black",
lwd = 2
)
} else {
stop(
"Unknown type ", type,
". Please use 'legend1' for variable and color legend,
'legend2' for value legend, or 'regular' (default setting) ",
"for the regular ecorisk plot."
)
}
}
#' Plot radial EcoRisk plot to file
#'
#' Function to plot an aggregated radial status of EcoRisk values [0-1]
#' for the different sub-categories to file
#'
#' @param data EcoRisk data array c(4/19[biomes],[nEcoRiskcomponents],
#' 3[min,mean,max])
#' @param file to write into
#' @param title character string title for plot, default empty
#' @param type plot type, 'legend1' for variable and color legend,
#' 'legend2' for value legend, or 'regular' (default setting)
#' for the regular EcoRisk plot
#' @param eps write as eps or png
#' @param leg_yes logical. whether to plot legend or not. defaults to TRUE
#'
#' @return None
#'
#' @export
plot_ecorisk_radial <- function(data,
file,
title = "",
leg_yes = TRUE,
eps = FALSE,
use_quantile = TRUE) {
# param data EcoRisk data array c(8[ncomponents],3[min,median,max])
# param title title for plot
# param type plot type, 'legend1' for variable and color legend, 'legend2'
# for value legend, or 'regular' (default setting) for the regular EcoRisk
# plot
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (length(which(data < 0 | data > 1)) > 0) {
warning(
"There are values in data outside the expected EcoRisk range [0..1]." # nolint
)
}
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 22,
height = 8.5, paper = "special"
)
} else {
grDevices::png(file,
width = 7.25, height = 3.5, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
# adjust the margins, dependent on whether a legend should be plotted or not
graphics::par(fig = c(0, 0.7, 0, 1)) # , oma=c(0,0,0,0),mar=c(0,0,0,0))
# plot main EcoRisk radial
plot_ecorisk_radial_to_screen(
data = data, title = title, zoom = 1.0,
type = "regular"
)
if (leg_yes) {
graphics::par(fig = c(0.7, 1, 0, 0.5), new = TRUE)
plot_ecorisk_radial_to_screen(
data = data, title = "", zoom = 1.5,
type = "legend1"
)
graphics::par(fig = c(0.7, 1, 0.5, 1), new = TRUE)
plot_ecorisk_radial_to_screen(
data = data, title = "", zoom = 1.5,
type = "legend2", use_quantile = use_quantile
)
}
grDevices::dev.off()
}
#' Plot timeline of EcoRisk variables to screen
#'
#' Function to plot timeline of EcoRisk variables to screen
#'
#' @param data EcoRisk data array
#' c(4/19[biomes],8/10[nEcoRiskcomponents],3[min,mean,max],timeslices)
#' @param timerange of the data input
#' @param yrange range for y axis default c(0,1)
#' @param leg_yes plot legend (default TRUE)
#'
#' @return None
#'
#' @export
plot_overtime_to_screen <- function(data,
timerange,
yrange = c(0, 1),
leg_yes = TRUE,
leg_only = FALSE,
varnames = NULL) {
ecorisk_dims <- dim(data)[1]
if (is.null(varnames)) {
if (ecorisk_dims == 10) {
names <- c(
ecorisk = "ecorisk", deltav = "vegetation structure",
local = "local change", global = "global importance",
balance = "ecosystem balance", cstocks = "carbon stocks",
cfluxes = "carbon fluxes", wfluxes = "water fluxes",
nstocks = "nitrogen stocks", nfluxes = "nitrogen fluxes"
)
# c(blue-green, yellow, violet, red, blue, orange, green, pink, grey,
# purple, green-blue, yellow-orange)
set <- RColorBrewer::brewer.pal(12, "Set3")
colz <- set[c(4, 7, 8, 11, 1, 3, 10, 5, 12, 6)]
} else if (ecorisk_dims == 8) {
names <- c(
ecorisk = "ecorisk", deltav = "vegetation structure",
local = "local change", global = "global importance",
balance = "ecosystem balance", cstocks = "carbon stocks",
cfluxes = "carbon fluxes", wfluxes = "water fluxes"
)
colz <- c(
"darkgoldenrod", RColorBrewer::brewer.pal(5, "Greens")[5],
RColorBrewer::brewer.pal(6, "Set1")[seq(2, 6, by = 2)],
rev(RColorBrewer::brewer.pal(6, "Oranges")[c(4, 5)]),
RColorBrewer::brewer.pal(6, "PuBu")[6]
)
} else {
stop("Unknown number of dimensions for ecorisk data: ", ecorisk_dims)
}
} else {
names <- varnames
colz <- RColorBrewer::brewer.pal(length(names), "Set2")
}
years <- timerange[1]:timerange[2]
if (leg_only) {
graphics::plot(NA,
ylim = c(yrange[1], yrange[2]), cex.axis = 1,
axes = FALSE, xlab = "", ylab = ""
)
graphics::legend("center", legend = names, fill = colz, border = colz)
} else {
graphics::plot(NA,
xlim = timerange, ylim = c(yrange[1], yrange[2]),
cex.axis = 1, xlab = "", ylab = ""
)
for (i in seq_len(ecorisk_dims)) {
if (i == 1) {
graphics::lines(x = years, y = data[i, 2, ], col = colz[i], lwd = 4)
} else {
graphics::lines(x = years, y = data[i, 2, ], col = colz[i], lwd = 2)
}
}
if (leg_yes) graphics::legend("topleft", legend = names, fill = colz)
}
}
#' Plot timeline of EcoRisk variables as panel to file with 4/16 biomes
#'
#' Function to plot a panel of 4/16 timelines per biome aggregated EcoRisk
#' values [0-1]
#' to file
#'
#' @param data EcoRisk data array c(4/19[biomes],[nEcoRiskcomponents],
#' 3[min,mean,max])
#' @param biome_names names of biomes
#' @param file to write into
#' @param yrange range for y axis (default c(0,1))
#' @param timerange of the data input
#' @param eps write as eps or png
#'
#' @return None
#'
#' @export
plot_ecorisk_over_time_panel <- function(data, # nolint
biome_names,
file,
yrange = c(0, 1),
timerange,
eps = FALSE,
varnames = NULL) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (length(which(data < 0 | data > 1)) > 0) {
warning("Values in data outside the expected EcoRisk range [0..1].")
}
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 15,
height = 10, paper = "special"
)
} else {
grDevices::png(file,
width = 5.25, height = 3.5, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
d <- length(data[, 1, 1, 1])
graphics::par(oma = c(0, 0, 0, 0), mar = c(3, 2, 0.5, 0))
if (d == 16 | d == 4) {
k <- sqrt(d)
xs <- seq(0, 0.8, length.out = k + 1)
ys <- seq(0.98, 0, length.out = k + 1)
for (x in seq_len(k)) {
for (y in seq_len(k)) {
if (x == 1 & y == 1) {
graphics::par(
fig = c(xs[x], xs[x + 1], ys[y + 1], ys[y]),
xpd = TRUE
)
} else {
graphics::par(
fig = c(xs[x], xs[x + 1], ys[y + 1], ys[y]), xpd = TRUE,
new = TRUE
)
}
plot_overtime_to_screen(
data = data[(x - 1) * k + y, , , ],
timerange = timerange, yrange = yrange,
leg_yes = FALSE, varnames = varnames
)
graphics::mtext(
text = biome_names[(x - 1) * k + y], side = 3,
line = 0, cex = 1, font = 2
)
}
}
} else {
stop(paste("Unknown number of biomes: ", length(data[, 1, 1, 1])))
}
# legend
graphics::par(
fig = c(0.8, 1, 0.5, 1.0), new = TRUE, oma = c(0, 0, 0, 0),
mar = c(0, 0, 0, 0)
)
graphics::plot(NA, axes = FALSE, ylim = c(0, 1), xlim = c(0, 1))
if (d == 16) {
graphics::text(
x = 0.1,
y = seq(0.95, 0.05, length.out = length(get_biome_names(1))),
labels = paste0(get_biome_names(1), " : ", get_biome_names(2)),
cex = 0.7, adj = 0
)
}
graphics::par(
fig = c(0.8, 1, 0.0, 0.5), new = TRUE, oma = c(0, 0, 0, 0),
mar = c(0, 0, 0, 0)
)
if (is.null(varnames)) {
plot_overtime_to_screen(
data = data[1, , , ], timerange = timerange,
leg_yes = FALSE, leg_only = TRUE
)
} else {
graphics::plot(NA, axes = FALSE, ylim = c(0, 1), xlim = c(0, 1))
colz <- RColorBrewer::brewer.pal(length(varnames), "Set2")
graphics::legend("center", legend = varnames, fill = colz, cex = 1)
}
grDevices::dev.off()
}
#' Plot radial EcoRisk panel to file with 4/16 biomes
#'
#' Function to plot an aggregated radial status of EcoRisk values [0-1]
#' for the different sub-categories to file
#'
#' @param data EcoRisk data array c(4/19[biomes],[nEcoRiskcomponents],
#' 3[min,mean,max])
#' @param biome_names names of biomes
#' @param file to write into
#' @param use_quantile is it quantiles or minmeanmax data? - text for whiskers
#' @param eps write as eps or png
#'
#' @return None
#'
#' @export
plot_ecorisk_radial_panel <- function(data,
biome_names,
file,
use_quantile = TRUE,
eps = FALSE) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (length(which(data < 0 | data > 1)) > 0) {
warning("Values in data outside the expected EcoRisk range [0..1].")
}
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 15,
height = 10, paper = "special"
)
} else {
grDevices::png(file,
width = 5.25, height = 3.5, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
d <- length(data[, 1, 1])
if (d == 16 | d == 4) {
k <- sqrt(d)
xs <- seq(0, 0.6, length.out = k + 1)
ys <- seq(0.98, 0, length.out = k + 1)
for (x in seq_len(k)) {
for (y in seq_len(k)) {
if (x == 1 & y == 1) {
graphics::par(
fig = c(xs[x], xs[x + 1], ys[y + 1], ys[y]),
xpd = TRUE, oma = c(0, 0, 0, 0), mar = c(0, 0, 0, 0)
)
} else {
graphics::par(
fig = c(xs[x], xs[x + 1], ys[y + 1], ys[y]), xpd = TRUE,
new = TRUE
)
}
plot_ecorisk_radial_to_screen(
data = data[(x - 1) * k + y, , ],
title = "", zoom = 1.0, type = "regular"
)
graphics::mtext(
text = biome_names[(x - 1) * k + y], side = 3,
line = -0.5, cex = 1, font = 2
)
}
}
} else {
stop(paste("Unknown number of biomes: ", length(data[, 1, 1])))
}
# legend
graphics::par(fig = c(0.6, 1, 0.1, 0.6), new = TRUE)
plot_ecorisk_radial_to_screen(
data = data[1, , ], title = "",
zoom = 1.5, type = "legend1"
)
graphics::par(fig = c(0.6, 1, 0.5, 1.0), new = TRUE)
plot_ecorisk_radial_to_screen(
data = data[1, , ], title = "legend", zoom = 1.5,
type = "legend2", title_size = 1, use_quantile = use_quantile
)
grDevices::dev.off()
}
#' Plot biomes
#'
#' Function to plot biome classification
#'
#' @param biome_ids biome id as given by classify_biomes
#' @param biome_name_length length of biome names in legend: 1 - abbreviation,
#' 2 - short name, 3 - full biome name
#' @param order legend order: either "plants" to first have forests, then
#' grasslands, then tundra ..., or "zones" to go from north to south
#' (default: "plants")
#' @param title character string title for plot, default empty
#' @param title_size size of title in cex units (defaukt: 2)
#' @param leg_yes whether to plot legend (default: True)
#' @param leg_scale size of legend in cex units (default 0.5)
#'
#' @return None
#'
#' @export
plot_biomes_to_screen <- function(biome_ids,
biome_name_length = 1,
order_legend = "plants",
title = "",
title_size = 2,
leg_yes = TRUE,
leg_scale = 0.5) {
# setting up colors and biome names
colz <- c(
# warm
rev(RColorBrewer::brewer.pal(6, "YlOrBr")),
rev(RColorBrewer::brewer.pal(9, "YlGn")[c(3, 5, 7, 9)]),
# cold below forest
rev(RColorBrewer::brewer.pal(9, "GnBu"))[c(2:4, 6, 8, 9)],
# "lightblue" # Water
"white",
# Rocks & Ice
"lightgrey",
# montane Tundra/Grassland
"pink3"
)
if (order_legend == "plants") {
order_legend <- seq_len(19)
} else if (order_legend == "zones") {
order_legend <- c(
1, 2, 9, 10, 11, 3, 4, 5, 6, 12, 13, 14, 7, 8, 15, 16, 17, 18, 19
)
} else {
stop(
"Unknown value for parameter order_legend (plants or zones) - ",
"was given as: ", order_legend
)
}
biome_class_cols <- (
colz[c(1, 2, 7, 8, 9, 10, 13, 12, 3, 4, 5, 14, 15, 16, 19, 11, 6, 17, 18)]
)
biome_class_names <- get_biome_names(biome_name_length)
if (!(length(biome_class_names) == length(biome_class_cols))) {
stop("Size of biome class names and colors do not match -- should be 18.")
}
# plotting
brks <- seq(
min(biome_ids, na.rm = TRUE) - 0.5,
max(biome_ids, na.rm = TRUE) + 0.5,
1
)
ra <- terra::rast(ncols = 720, nrows = 360)
range <- range(biome_ids)
ra[terra::cellFromXY(ra, cbind(lon, lat))] <- biome_ids
extent <- terra::ext(c(-180, 180, -60, 90))
graphics::par(mar = c(0, 0, 0, 0), oma = c(0, 0, 0, 0), bty = "n")
terra::plot(ra,
ext = extent, breaks = brks, col = biome_class_cols,
main = "", legend = FALSE, axes = FALSE
)
graphics::title(main = title, line = -2, cex.main = title_size)
if (leg_yes) {
graphics::legend(
x = -180, y = 27, legend = biome_class_names[order_legend],
fill = biome_class_cols[order_legend],
col = biome_class_cols[order_legend],
cex = leg_scale, bg = "white", bty = "o"
)
}
maps::map("world", add = TRUE, res = 0.4, lwd = 0.25, ylim = c(-60, 90))
}
#' Plot biomes to file
#'
#' Function to plot biome classification to file
#'
#' @param biome_ids biome id as given by classify_biomes
#' @param biome_name_length length of biome names in legend: 1 - abbreviation,
#' 2 - short name, 3 - full biome name
#' @param order_legend legend order: either "plants" to first have forests, then
#' grasslands, then tundra ..., or "zones" to go from north to south
#' (default: "plants")
#' @param file to write into
#' @param title character string title for plot, default empty
#' @param title_size size of title in cex units (defaukt: 2)
#' @param leg_yes whether to plot legend (default: True)
#' @param leg_scale size of legend in cex units (default 0.5)
#' @param eps write as eps, replacing png in filename (default: True)
#'
#' @return None
#'
#' @export
plot_biomes <- function(biome_ids,
biome_name_length = 1,
order_legend = "plants",
file,
title = "",
title_size = 2,
leg_yes = TRUE,
leg_scale = 1,
eps = FALSE) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 22,
height = 8.5, paper = "special"
)
} else {
grDevices::png(file,
width = 7.25, height = 3.5, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
plot_biomes_to_screen(
biome_ids = biome_ids,
biome_name_length = biome_name_length,
order_legend = order_legend,
title = title,
title_size = title_size,
leg_yes = leg_yes,
leg_scale = leg_scale
)
grDevices::dev.off()
}
#' Plot radial EcoRisk plot to file with 4/16 biomes
#'
#' Function to plot an aggregated radial status of EcoRisk values [0-1]
#' for the different sub-categories to file
#'
#' @param data input data with dimension c(nbiome_classes,3) -- Q10,Q50,Q90 each
#' @param biome_class_names to write into
#' @param title character string title for plot, default empty
#' @param title_size character string title for plot
#' @param leg_scale character string title for plot
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_biome_averages_to_screen <- function(
data,
biome_class_names,
title = "",
title_size = 2,
leg_scale = 0.5,
palette = NULL) {
# setting up colors and biome names
brks <- seq(0, 1, 0.1)
data[data < brks[1]] <- brks[1]
data[data > brks[length(brks)]] <- brks[length(brks)]
if (is.null(palette)) {
palette <- c(
"white", "steelblue1", "royalblue", RColorBrewer::brewer.pal(7, "YlOrRd")
)
}
col_index <- floor(data[, 2] * 10) + 1
if (!(length(biome_class_names) == dim(data)[1])) {
stop("Size of biome class names and data input do not match.")
}
# plotting
graphics::plot(NA,
xlim = c(0, 1), ylim = c(0, 1), main = title, axes = FALSE,
cex.main = title_size, xlab = "", ylab = ""
)
graphics::legend(
x = 0, y = 1, legend = biome_class_names,
fill = palette[col_index], col = palette[col_index],
border = palette[col_index], cex = leg_scale,
bg = "white", bty = "o"
)
}
#' Plot radial EcoRisk plot to file with 4/16 biomes
#'
#' Function to plot an aggregated radial status of EcoRisk values [0-1]
#' for the different sub-categories to file
#'
#' @param data EcoRisk data array c(4[biomes],[nEcoRiskcomponents],
#' 3[min,median,max])
#' @param file to write into
#' @param biome_class_names to write into
#' @param title character string title for plot, default empty
#' @param title_size character string title for plot
#' @param leg_scale character string title for plot
#' @param eps write as eps, replacing png in filename (default: True)
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_biome_averages <- function(data,
file,
biome_class_names,
title = "",
title_size = 2,
leg_scale = 1,
eps = FALSE,
palette = NULL) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 22,
height = 8.5, paper = "special"
)
} else {
grDevices::png(file,
width = 4, height = 3, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
plot_biome_averages_to_screen(
data = data, biome_class_names = biome_class_names,
title = title, title_size = title_size,
leg_scale = leg_scale, palette = palette
)
grDevices::dev.off()
}
#' Plot crosstable showing (dis-)similarity between average biome pixels
#'
#' Function to plot a crosstable showing (dis-)similarity between average
#' biome pixels based on EcoRisk (former gamma) metric from LPJmL simulations
#'
#' @param data crosstable data as array with [nbiomes,nbiomes] and row/colnames
#' @param lmar left margin for plot in lines (default: 3)
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_ecorisk_cross_table_to_screen <- function(
# nolint
data,
lmar = 3,
palette = NULL) {
# data prep
data <- round(data, digits = 2)
x <- seq_len(ncol(data))
y <- seq_len(nrow(data))
centers <- expand.grid(y, x)
# coloring
if (is.null(palette)) {
palette <- c(
"white", "steelblue1", "royalblue", RColorBrewer::brewer.pal(7, "YlOrRd")
)
}
brks <- seq(0, 1, 0.1)
# plot margins
graphics::par(mar = c(0, lmar, 2, 0)) # bltr
graphics::image(x, y, t(data),
col = palette,
breaks = brks,
xaxt = "n",
yaxt = "n",
xlab = "",
ylab = "",
ylim = c(max(y) + 0.5, min(y) - 0.5)
)
graphics::text(centers[, 2], centers[, 1], c(data), col = "black")
# add margin text
graphics::mtext(attributes(data)$dimnames[[2]],
at = seq_len(ncol(data)),
padj = -1
)
graphics::mtext(attributes(data)$dimnames[[1]],
at = seq_len(nrow(data)),
side = 2,
las = 1,
adj = 1,
line = 1
)
# add black lines
graphics::abline(h = y + 0.5)
graphics::abline(v = x + 0.5)
}
#' Plot crosstable to file showing (dis-)similarity between average biome pixels
#'
#' Function to plot to file a crosstable showing (dis-)similarity between
#' average biome pixels based on EcoRisk (former Gamma) metric from LPJmL
#' simulations
#'
#' @param data crosstable data as array with [nbiomes,nbiomes] and row/colnames
#' @param file to write into
#' @param lmar left margin for plot in lines (default: 3)
#' @param eps write as eps or png
#' @param palette color palette to plot EcoRisk with, defaults to the Ostberg
#' color scheme white-blue-yellow-red
#'
#' @return None
#'
#' @export
plot_ecorisk_cross_table <- function(data,
file,
lmar = 3,
eps = FALSE,
palette = NULL) {
path_write <- dirname(file)
dir.create(file.path(path_write), showWarnings = FALSE, recursive = TRUE)
if (eps) {
file <- strsplit(file, ".", fixed = TRUE)[[1]]
file <- paste(c(file[seq_len(length(file) - 1)], "eps"), collapse = ".")
grDevices::ps.options(family = c("Helvetica"), pointsize = 18)
grDevices::postscript(file,
horizontal = FALSE, onefile = FALSE, width = 22,
height = 8.5, paper = "special"
)
} else {
grDevices::png(file,
width = 6, height = 3, units = "in", res = 300,
pointsize = 6, type = "cairo"
)
}
plot_ecorisk_cross_table_to_screen(
data = data,
lmar = lmar,
palette = palette
)
grDevices::dev.off()
}