Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
59d7d5f
Add downloadForestLossDrivers() and ship the source table in-package
mscrawford Sep 10, 2026
5b69ab0
Fix Africa tree cover loss (3 -> 39 Mha) and guard the table on read
mscrawford Sep 10, 2026
2524e25
Reassign MENA out of the Latin America source region, and Europe's 1%…
mscrawford Sep 10, 2026
0b54a9b
Bump version to 0.76.0
mscrawford Sep 10, 2026
95ceecb
Restore readForestLossDrivers's own man page
mscrawford Sep 10, 2026
9dd6a59
Add GFWLossByDriver: tree cover loss by country, year and driver
mscrawford Sep 10, 2026
2ea680f
Select the forest-loss driver source in calcForestLossShare/calcFores…
mscrawford Sep 10, 2026
e179f2c
Restrict f35_forest_lost_share to drivers MAgPIE does not model endog…
mscrawford Sep 10, 2026
f5ca996
Restrict f35_forest_lost_share to shifting cultivation
mscrawford Sep 11, 2026
3fd4047
buildLibrary
mscrawford Sep 11, 2026
9179650
Drop two checks from checkGFWLossByDriver that a stronger one dominates
mscrawford Sep 12, 2026
def0792
Retire calcForestDisturbances, whose only consumer was an unread MAgP…
mscrawford Sep 12, 2026
149662e
Tidy readForestLossDrivers: line length, comma-separated mapping file
mscrawford Sep 12, 2026
795523b
Use tree cover extent 2000 as the denominator of the GFW forest loss …
mscrawford Sep 12, 2026
2fe6a09
Rename calcForestFireLoss to calcForestLossByDriver
mscrawford Sep 14, 2026
9f6f91e
Remove lost braces from the downloadGFWLossByDriver route note
mscrawford Sep 14, 2026
c073b5f
Address review: remove SOURCE.md, the Curtis table check and the test…
mscrawford Sep 22, 2026
9323673
Merge upstream/master into fix/forestlossdrivers-source
mscrawford Sep 22, 2026
c2903c3
Bump version to 0.77.0 (lucode2::buildLibrary)
mscrawford Sep 22, 2026
80e0d28
Align the Curtis disaggregation weight year with the share denominator
mscrawford Sep 23, 2026
ec20f16
Fold the GFW read helpers into the read function and trim its guards
mscrawford Sep 23, 2026
7b85b43
Address review: Curtis as a madrat source, FRA denominator for GFW
mscrawford Sep 29, 2026
4a6df67
Merge upstream/master into fix/forestlossdrivers-source
mscrawford Sep 29, 2026
6f8b408
Bump version to 0.78.0 (lucode2::buildLibrary)
mscrawford Sep 29, 2026
1563aa9
Add Michael Crawford as author
mscrawford Sep 30, 2026
af2e7b7
Move Michael Crawford to the middle of the author list
mscrawford Sep 30, 2026
ce477f3
Read the corrected Curtis table from forest_loss_fixed.csv
mscrawford Oct 1, 2026
410607a
Merge branch 'master' into fix/forestlossdrivers-source
mscrawford Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .buildlibrary
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ValidationKey: '15601014'
ValidationKey: '15737320'
AcceptedWarnings:
- 'Warning: package ''.*'' was built under R version'
- 'Warning: namespace ''.*'' is not available and has been replaced'
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ cff-version: 1.2.0
message: If you use this software, please cite it using the metadata from this file.
type: software
title: 'mrland: MadRaT land data package'
version: 0.75.4
date-released: '2026-08-26'
version: 0.76.0
date-released: '2026-09-11'
abstract: The package provides land related data via the madrat framework.
authors:
- family-names: Dietrich
Expand Down
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Type: Package
Package: mrland
Title: MadRaT land data package
Version: 0.75.4
Date: 2026-08-26
Version: 0.76.0
Date: 2026-09-11
Authors@R: c(
person("Jan Philipp", "Dietrich", , "dietrich@pik-potsdam.de", role = c("aut", "cre")),
person("Abhijeet", "Mishra", role = "aut"),
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@ importFrom(terra,rast)
importFrom(terra,segregate)
importFrom(terra,terraOptions)
importFrom(terra,tmpFiles)
importFrom(utils,URLencode)
importFrom(utils,bibentry)
importFrom(utils,download.file)
importFrom(utils,head)
importFrom(utils,person)
importFrom(utils,read.csv)
importFrom(utils,read.csv2)
Expand Down
38 changes: 0 additions & 38 deletions R/calcForestDisturbances.R

This file was deleted.

31 changes: 0 additions & 31 deletions R/calcForestFireLoss.R

This file was deleted.

71 changes: 71 additions & 0 deletions R/calcForestLossByDriver.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Driver classes that reach f35_forest_lost_share. Module 35 resets the age structure of the
# "lost" area (the land stays forest) and module 52 books the flux as land-use-change CO2, so a
# class qualifies only if it leaves the land as forest, is not decided endogenously by MAgPIE,
# and is a land-use rather than a natural process. Of the eight GFW classes only shifting
# cultivation passes. The others are published by mrvalidation::calcValidTreeCoverLoss:
# permanent agriculture, hard commodities and settlements remove the forest, logging is
# MAgPIE's own timber harvest, wildfire and other natural disturbances are natural.
gfwModelDrivers <- c("shifting_cultivation")
Comment thread
mscrawford marked this conversation as resolved.
Outdated

#' @title calcForestLossByDriver
#'
#' @description Annual forest area loss by driver, Mha per year, from the Sims et al. (2025)
#' driver product (`source = "GFW"`, default) or the Curtis et al. (2018) table
#' (`source = "Curtis"`), with the FRA 2020 fire series bound on as `overall`.
#'
#' @param source `"GFW"` (default) or `"Curtis"`. Curtis computes as before, from the
#' corrected Curtis et al. (2018) table.
#' @param period Years averaged into the annual rate, GFW only (default 2015:2024). Curtis
#' carries a single 2001-2015 mean.
#'
#' @details For GFW only the classes in `gfwModelDrivers` are returned. `overall` is the FRA
#' fire series averaged over its own years (2000-2017), as before; GAMS loads it but never
#' reads it.
#'
#' @return MAgPIE object with forest area lost per year by driver, Mha
#' @author Abhijeet Mishra, Michael Crawford
#' @importFrom magclass mbind setNames dimSums getYears getItems
#' @importFrom madrat readSource

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#' @importFrom magclass mbind setNames dimSums getYears getItems
#' @importFrom madrat readSource

#' @seealso [readGFWLossByDriver()], [readForestLossDrivers()]
#' @examples
#' \dontrun{
#' calcOutput("ForestLossByDriver", aggregate = FALSE)
#' calcOutput("ForestLossByDriver", source = "Curtis", aggregate = FALSE)
#' }

calcForestLossByDriver <- function(source = "GFW", period = 2015:2024) {

fao <- setNames(readSource("FRA2020", subtype = "forest_fire", convert = TRUE), "overall")
fao <- dimSums(fao, dim = 2) / length(getYears(fao)) # mean annual area lost to fire

drivers <- switch(
source, # nolint: undesirable_function_linter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of disabling the linter source could be renamed

Suggested change
source, # nolint: undesirable_function_linter.
src,

"Curtis" = readSource("ForestLossDrivers"),
"GFW" = {
x <- readSource("GFWLossByDriver", convert = TRUE)
wanted <- paste0("y", period)
absent <- setdiff(wanted, getYears(x))
if (length(absent) > 0) {
stop("calcForestLossByDriver: the GFW record does not cover ", toString(absent),
". It runs ", min(getYears(x, TRUE)), "-", max(getYears(x, TRUE)), ".")
}
absent <- setdiff(gfwModelDrivers, getItems(x, 3))
if (length(absent) > 0) {
stop("calcForestLossByDriver: the GFW source does not carry driver class(es) ",
toString(absent), ".")
}
dimSums(x[, wanted, gfwModelDrivers], dim = 2) / length(wanted)
},
stop("calcForestLossByDriver: unknown source '", source, # nolint: undesirable_function_linter.
"'. Use \"GFW\" or \"Curtis\".")
)

out <- mbind(fao, drivers)

return(list(x = out,
weight = NULL,
unit = "Mha",
description = paste0("Forest area lost per year by driver (",
source, ")"), # nolint: undesirable_function_linter.
isocountries = FALSE))
}
85 changes: 57 additions & 28 deletions R/calcForestLossShare.R
Original file line number Diff line number Diff line change
@@ -1,36 +1,65 @@
#' @title calcForestLossShare
#'
#' @description Calculates which share of forest land is lost due to different drivers
#' @description Calculates which share of forest area is lost per year to each driver of
#' forest loss.
#'
#' @return MAgPIE object with share of area lost in forests due to different drivers
#' @author Abhijeet Mishra
#' @seealso
#' \code{\link{readForestLossDrivers}}
#'
#' @param source Driver data to use, `"GFW"` (default) or `"Curtis"`. See
#' [calcForestLossByDriver()].
#' @param period Years averaged to give the annual rate, for `source = "GFW"` only.
#'
#' @details For `source = "GFW"` numerator and denominator come from the same product at the
#' same canopy threshold: UMD tree cover loss by driver over UMD tree cover extent in 2000,
#' the base the loss is measured on. For `source = "Curtis"` the denominator stays FRA 2020
#' naturally regenerating forest area in 2010, reproducing the previous parameterisation
#' exactly. The `overall` column (FRA fire area) is divided by the same denominator as the
#' drivers of its source and exceeds 1 in a few savanna countries; GAMS loads it but never reads
#' it. Shares above 1 are clamped to 1; for a driver column this also warns with the country names.
#'
#' @return MAgPIE object with the share of forest area lost per year by driver
#' @author Abhijeet Mishra, Michael Crawford
#' @importFrom magclass setYears setNames getItems

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#' @importFrom magclass setYears setNames getItems

#' @importFrom utils head
#' @importFrom madrat calcOutput readSource

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#' @importFrom madrat calcOutput readSource

#' @seealso [calcForestLossByDriver()]
#' @examples
#'
#' \dontrun{
#' calcOutput("ForestLossShare",aggregate=FALSE)
#' \dontrun{
#' calcOutput("ForestLossShare", aggregate = FALSE)
#' calcOutput("ForestLossShare", source = "Curtis", aggregate = FALSE)
#' }
#'
#'

calcForestLossShare <- function(){

lost_area <- calcOutput("ForestFireLoss",aggregate=FALSE) # Convert=T returns Mha area lost to fire
forest_area <- setYears(setNames(readSource("FRA2020",subtype = "forest_area",convert = TRUE)[,"y2010","naturallyRegeneratingForest"],NULL),NULL) # Convert=T returns Mha area of forests
calcForestLossShare <- function(source = "GFW", period = 2015:2024) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
calcForestLossShare <- function(source = "GFW", period = 2015:2024) {
calcForestLossShare <- function(src = "GFW", period = 2015:2024) {

Renaming gets around the linter issues


lostArea <- calcOutput("ForestLossByDriver", source = source, # nolint: undesirable_function_linter.
period = period, aggregate = FALSE)

forestArea <- switch(
source, # nolint: undesirable_function_linter.
"GFW" = readSource("GFWLossByDriver", subtype = "extent", convert = TRUE),
"Curtis" = readSource("FRA2020", subtype = "forest_area",
convert = TRUE)[, "y2010", "naturallyRegeneratingForest"],
stop("calcForestLossShare: unknown source '", source, # nolint: undesirable_function_linter.
"'. Use \"GFW\" or \"Curtis\".")
)
forestArea <- setYears(setNames(forestArea, NULL), NULL)

lostShare <- lostArea / forestArea
lostShare[is.infinite(lostShare)] <- 0
lostShare[is.na(lostShare)] <- 0
# overall (FRA fire area over the driver denominator) exceeds 1 in savanna countries; GAMS
# never reads it. A driver share above 1 would mean the numerator and denominator disagree.
drivers <- setdiff(getItems(lostShare, 3), "overall")
over <- lostShare[, , drivers] > 1
if (any(over)) {
warning("calcForestLossShare: ", sum(over), " driver share(s) above 1 clamped to 1 (",
source, "): ", # nolint: undesirable_function_linter.
toString(utils::head(getItems(lostShare, 1)[apply(over, 1, any)], 5)), ".")
}
lostShare[lostShare > 1] <- 1

lost_share <- lost_area/forest_area
lost_share[is.infinite(lost_share)] <- 0
lost_share[is.na(lost_share)] <- 0
lost_share[lost_share>1] <- 1

out <- lost_share
weight <- forest_area

return(list(x=out,
weight=weight,
unit="1",
description="Area lost in forests by drivers",
isocountries=FALSE))
return(list(x = lostShare,
weight = forestArea,
unit = "1",
description = paste0("Share of forest area lost per year by driver (",
source, ")"), # nolint: undesirable_function_linter.
isocountries = FALSE))
}
62 changes: 62 additions & 0 deletions R/convertGFWLossByDriver.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# GADM codes that are not madrat ISO countries but sit inside one, so their loss is added
Comment thread
mscrawford marked this conversation as resolved.
Outdated
# to the host rather than discarded. Measured over 2001-2025 at a 30 per cent threshold:
# XKO 19 126 ha, ZNC 1740 ha, XAD 80 ha.
gfwIsoHosts <- c(XKO = "SRB", # Kosovo, inside Serbia in madrat's country set
ZNC = "CYP", # Northern Cyprus
XAD = "CYP") # Akrotiri and Dhekelia, the sovereign base areas on Cyprus

# Undocumented GADM placeholders for disputed territories, dropped rather than guessed at. Loss
# 2001-2025: Z01, Z06 and Z07 carry 169 451 ha together, 0.031 per cent of the global total.
# Extent 2000: 6.5 Mha over all nine, 0.16 per cent, mostly Z07 and Z01.
gfwIsoDropped <- sprintf("Z%02d", 1:9)

#' @title convertGFWLossByDriver
#'
#' @description Reconciles the GADM country set used by GFW with madrat's ISO set.
#'
#' GADM and madrat disagree on 6 codes carrying loss and on 39 madrat countries GFW does
#' not list; both are handled explicitly rather than by a silent inner join.
#'
#' @param x magpie object as returned by [readGFWLossByDriver()]
#' @param subtype `"loss"` or `"extent"`, as passed to the read function; the reconciliation is
#' the same for both
#' @return magpie object on madrat's ISO country set, unit Mha
#' @author Michael Crawford
#' @importFrom madrat toolCountryFill
#' @importFrom magclass getItems

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#' @importFrom madrat toolCountryFill
#' @importFrom magclass getItems

#' @seealso [readGFWLossByDriver()]
#' @examples
#' \dontrun{
#' a <- readSource("GFWLossByDriver", convert = TRUE)
#' }

convertGFWLossByDriver <- function(x, subtype = "loss") {

before <- sum(x)

for (code in names(gfwIsoHosts)) {
host <- gfwIsoHosts[[code]]
if (code %in% getItems(x, 1)) {
if (host %in% getItems(x, 1)) {
x[host, , ] <- x[host, , ] + x[code, , ]
} else {
getItems(x, 1)[getItems(x, 1) == code] <- host
next
}
x <- x[setdiff(getItems(x, 1), code), , ]
}
}

out <- toolCountryFill(x, fill = 0, no_remove_warning = gfwIsoDropped)

# A future version moving real loss onto a code madrat does not know must fail here.
lost <- (before - sum(out)) / before
if (lost > 0.005) {
stop("GFWLossByDriver: reconciling GADM with madrat's ISO set dropped ",
round(100 * lost, 3), " per cent of global loss, above the 0.5 per cent tolerance. ",
"Codes not in madrat: ",
toString(setdiff(getItems(x, 1), madrat::getISOlist())), ".")
}

return(out)
}
Loading