#' Portfolio methods
#'
#' `write_portfolio()` writes a processed biodiversity portfolio to disk.
#' Portfolio data will only be serialized to disk as a GeoPackage including
#' two tables: `metadata` and `indicators`. The `metadata` tables
#' includes, among other simple variables the geometries and a primary
#' key called `assetid`. The 'indicators' tables includes the foreign key
#' `assetid`, a column called `indicator` giving the name of the original
#' indicator as well as the standard indicator columns `datetime`, `variable`,
#' `unit`, and `value`. For convenience, use `read_portfolio()` to read
#' such a portfolio GeoPackage back into R.
#'
#' @param x A portfolio object processed with `mapme.biodiversity`.
#' @param dsn A file path for the output file (must end with `gpkg`).
#' @param ... Additional arguments supplied to `write_sf()` or `read_sf()`
#' @return `write_portfolio()` returns `dsn`, invisibly.
#' @name portfolio
#' @export
write_portfolio <- function(x,
dsn,
...) {
assetid <- NULL
x <- .check_portfolio(x)
inds_cols <- .indicators_col(x)
if (length(inds_cols) == 0) {
stop("No calculated indicators have been found")
}
if (tools::file_ext(dsn) != "gpkg") {
warning("Can serialze portoflio only to GeoPackage.")
dsn <- gsub(tools::file_ext(dsn), "gpkg", dsn)
}
metadata <- dplyr::select(x, -dplyr::all_of(names(inds_cols)))
write_sf(metadata, dsn, "metadata", ...)
data <- st_drop_geometry(dplyr::select(x, assetid, dplyr::all_of(names(inds_cols))))
purrr::walk(names(inds_cols), function(ind) {
tmp <- dplyr::select(data, assetid, dplyr::all_of(ind))
tmp <- tidyr::unnest(tmp, dplyr::all_of(ind), keep_empty = FALSE)
tmp[["indicator"]] <- ind
vars <- c("assetid", "indicator", "datetime", "variable", "unit", "value")
if (!all(vars %in% names(tmp))) {
msg <- sprintf("Indicator '%s' contained no valid values. Dropping it from the output.", ind)
warning(msg)
return()
}
write_sf(tmp[, vars], dsn, "indicators", append = TRUE, ...)
})
return(invisible(dsn))
}
#' `read_portfolio()` is used to read a portfolio object that was previously
#' written to disk via `write_portfolio()` back into R as an `sf` object.
#' It should be directed against a GeoPackage which was the output
#' of `write_portfolio()`, otherwise the function is very likely to fail.
#' All available indicators will be read back into R as nested list columns
#' reflecting the output once `calc_indicators()` has been called.
#'
#' @param src A character vector pointing to a GeoPackage that has been
#' previously written to disk via `write_portfolio()`
#' @return `read_portfolio()` returns an `sf` object object with nested list
#' columns for every indicator found in the GeoPackage source file.
#' @name portfolio
#' @export
#'
read_portfolio <- function(src, ...) {
assetid <- indicator <- NULL
all_layers <- st_layers(src)
if (!identical(all_layers[["name"]], c("metadata", "indicators"))) {
stop(sprintf(
"Input file at '%s' does not seem to be a proper portfolio file written with 'write_portfolio()'",
src
))
}
metadata <- read_sf(src, layer = "metadata")
data <- read_sf(src, layer = "indicators")
data <- tidyr::nest(data, data = c(-assetid, -indicator))
is_null <- purrr::map_lgl(data[["data"]], function(x) all(is.na(x)))
data[["data"]][is_null] <- list(NULL)
data <- tidyr::pivot_wider(data, id_cols = assetid, names_from = indicator, values_from = data)
metadata <- dplyr::left_join(metadata, data, by = "assetid")
.geom_last(st_as_sf(tibble::as_tibble(metadata)))
}
#' Transform portfolio to long
#'
#' `portfolio_long()` transforms a portfolio to long-format, potentially
#' dropping geometries in the process.
#'
#' @param indicators If NULL (the default), all indicator columns will be detected
#' and transformed automatically. If a character vector is supplied, only
#' those indicators will be transformed.
#' @param drop_geoms A logical, indicating if geometries should be dropped.
#'
#' @return `portfolio_long()` returns the portfolio object in long-format.
#' @name portfolio
#' @export
#'
portfolio_long <- function(x, indicators = NULL, drop_geoms = FALSE) {
.check_portfolio(x)
if (is.null(indicators)) {
indicators <- names(.indicators_col(x))
}
stopifnot(all(indicators %in% names(x)))
is_null <- sapply(indicators, function(ind) {
all(sapply(x[[ind]], is.null))
})
if (all(is_null)) {
warning("All indicator columns contained 'NULL'.")
return(x[, -which(names(x) %in% indicators)])
}
if (any(is_null)) {
x <- x[, -.indicators_col(x)[is_null]]
indicators <- names(.indicators_col(x))
}
if (drop_geoms) x <- st_drop_geometry(x)
ind_cols <- c("indicator", "datetime", "variable", "unit", "value")
all_cols <- names(x)
other_cols <- all_cols[-which(all_cols %in% indicators)]
x_long <- purrr::map(indicators, function(ind) {
indicator <- tidyr::unnest(x, cols = {{ ind }})
indicator["indicator"] <- ind
indicator <- indicator[, c(other_cols, ind_cols)]
}) %>%
purrr::list_rbind()
if (!drop_geoms) {
x_long <- .geom_last(st_as_sf(x_long))
}
x_long
}
#' Transform portfolio to wide
#'
#' `portfolio_wide()` transforms a portfolio to wide-format, potentially
#' dropping geometries in the process.
#'
#' @return `portfolio_wide()` returns the portfolio object in wide-format.
#' @name portfolio
#' @export
#'
portfolio_wide <- function(x, indicators = NULL, drop_geoms = FALSE) {
assetid <- NULL
.check_portfolio(x)
if (is.null(indicators)) {
indicators <- names(.indicators_col(x))
}
stopifnot(all(indicators %in% names(x)))
is_null <- sapply(indicators, function(ind) {
all(sapply(x[[ind]], is.null))
})
if (all(is_null)) {
warning("All indicator columns contained 'NULL'.")
return(x[, -which(names(x) %in% indicators)])
}
if (any(is_null)) {
x <- x[, -.indicators_col(x)[is_null]]
indicators <- names(.indicators_col(x))
}
if (drop_geoms) x <- st_drop_geometry(x)
indicators_wide <- purrr::map(indicators, function(indicator) {
indicator_assets <- x[[indicator]]
indicator_assets <- purrr::map(indicator_assets, function(asset) {
if (is.null(asset)) {
return(NULL)
}
asset[["indicator"]] <- indicator
tidyr::pivot_wider(
asset,
names_from = c(indicator, datetime, variable, unit),
names_sep = "_", values_from = value
)
})
names(indicator_assets) <- x[["assetid"]]
purrr::list_rbind(indicator_assets, names_to = "assetid")
})
indicators_wide <- purrr::reduce(indicators_wide, dplyr::left_join, by = "assetid")
indicators_wide[["assetid"]] <- as.numeric(indicators_wide[["assetid"]])
x_wide <- x %>%
dplyr::select(-{{ indicators }}) %>%
dplyr::left_join(indicators_wide, by = "assetid")
if (!drop_geoms) {
x_wide <- .geom_last(st_as_sf(x_wide))
}
x_wide
}
.check_portfolio <- function(x, verbose = mapme_options()[["verbose"]]) {
stopifnot(inherits(x, "sf"))
sf_col <- attr(x, "sf_column")
if (st_crs(x) != st_crs(4326)) {
message("CRS of x is not EPSG:4326. Attempting to transform.")
x <- st_transform(x, 4326)
}
if (any(!unique(st_geometry_type(x)) %in% c("POLYGON", "MULTIPOLYGON"))) {
stop("Only assets of type 'POLYGON' and 'MULTIPOLYGON' are supported.")
}
if (!inherits(x, "tbl_df")) {
x <- st_as_sf(tibble::as_tibble(x))
}
has_assetid <- "assetid" %in% names(x)
if (!has_assetid) x[["assetid"]] <- 1:nrow(x)
is_unique <- length(unique(x[["assetid"]])) == nrow(x)
if (!is_unique) {
msg <- paste("Found a column named 'assetid' with non-unique identifiers.",
"Overwritting its values.",
sep = " "
)
message(msg)
x[["assetid"]] <- 1:nrow(x)
}
st_geometry(x) <- sf_col
x
}
.indicators_col <- function(x) {
inds <- which(sapply(x, is.list))
is_sf <- inherits(x, "sf")
if (is_sf && length(inds) == 1) {
msg <- paste("No calculated indicators have been found.",
"Cannot write as a portfolio.",
sep = " "
)
stop(msg)
}
if (is_sf) {
inds[-which(names(inds) == attributes(x)[["sf_column"]])]
} else {
inds
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.