# 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() }