R/round_coordinates.R

Defines functions round_coordinates

Documented in round_coordinates

#' Round coordinates to generalize camera trap locations
#'
#' Rounds deployment coordinates to a certain number of digits to
#' fuzzy/generalize camera trap locations.
#' This function can be used before publishing data in order to protect
#' sensitive species and/or prevent theft of active cameras.
#'
#' @param package A Camtrap DP, as read by [read_camtrap_dp()].
#' @param digits Number of decimal places to round coordinates to (`1`, `2` or
#'   `3`).
#' @return `package` with rounded coordinates as well as updated
#'   `coordinateUncertainty`.(in deployments) and `coordinatePrecision` (in
#'   metadata).
#' @family publication functions
#' @export
#' @importFrom dplyr %>% .data
#' @section Details:
#' Rounding coordinates is a recommended method to generalize sensitive
#' biodiversity information (see
#' [Section 4.2](https://doi.org/10.15468/doc-5jp4-5g10#s-generalization)
#' in Chapman 2020).
#' Choose a number of digits that aligns with the sensitivity of the data and
#' notice the effect on precision and uncertainty.
#' Publish the coordinates as is (i.e. do not use this function) if the data
#' are not sensitive.
#'
#' sensitivity | digits | coordinatePrecision | coordinateUncertainty
#' --- | --- | --- | ---
#' high | 1 | 0.1 | original uncertainty + 15691 m
#' medium | 2 | 0.01 | original uncertainty + 1570 m
#' low | 3 | 0.001 | original uncertainty + 157 m
#'
#' For records with `coordinateUncertainty = NA` the function will assume the
#' coordinates were obtained by GPS and use `30 m` as original uncertainty,
#' before adding uncertainty caused by rounding.
#' The added uncertainty is the largest possible value caused by rounding (see
#' [Table 3](https://doi.org/10.15468/doc-gg7h-s853#table-uncertainty) in
#' Chapman & Wieczorek 2020).
#' @examples
#' # Round coordinates of example package to 3 digits
#' mica <- round_coordinates(mica, 3)
#'
#' # coordinatePrecision is set in metadata
#' mica$coordinatePrecision
#'
#' # coordinateUncertainty is set in data: original uncertainty (or 30) + 157 m
#' mica$data$deployments$coordinateUncertainty
round_coordinates <- function(package, digits = 3) {
  assertthat::assert_that(
    digits %in% c(1, 2, 3),
    msg = "`digits` must be 1, 2 or 3."
  )

  deployments <- package$data$deployments

  # Detect original number of digits from coordinatePrecision or data
  original_precision <- package$coordinatePrecision
  if (!is.null(original_precision)) {
    original_digits <- -log10(original_precision) # 0.001 -> 3
    assertthat::assert_that(
      digits <= original_digits, # 0.1 > 0.01
      msg = glue::glue(
        "Can't round from {original_digits} to {digits} digits. ",
        "`{original_digits}` is derived from the ",
        "`package$coordinatePrecision={original_precision}`."
      )
    )
  } else {
    original_digits <-
      deployments %>%
      dplyr::mutate(
        lat_digits = nchar(stringr::str_extract(.data$latitude, "\\d+$"))
      ) %>%
      dplyr::summarize(max(.data$lat_digits)) %>%
      dplyr::pull()
    assertthat::assert_that(
      digits <= original_digits, # 0.1 > 0.01
      msg = glue::glue(
        "Can't round from {original_digits} to {digits} digits. ",
        "`{original_digits}` is the maximum number of decimals for latitude ",
        "in the data."
      )
    )
  }

  # Set uncertainties
  uncertainty <- c(15691, 1570, 157) # In order for 1, 2, 3, digits

  # Update longitude, latitude and coordinateUncertainty
  package$data$deployments <-
    dplyr::mutate(deployments,
      longitude = round(.data$longitude, digits),
      latitude = round(.data$latitude, digits),
      coordinateUncertainty = dplyr::case_when(
        # No uncertainty in data: assume 30, add rounding uncertainty
        is.na(.data$coordinateUncertainty) ~ 30 + uncertainty[digits],
        # No precision in metadata: original uncertainty, add rounding uncertainty
        is.null(original_precision) ~ .data$coordinateUncertainty + uncertainty[digits],
        # Otherwise: subtract old rounding uncertainty, add new rounding uncertainty
        TRUE ~ .data$coordinateUncertainty - uncertainty[original_digits] + uncertainty[digits]
      )
    )

  # Update coordinatePrecision
  package$coordinatePrecision <- 1 / 10^digits

  package
}
inbo/camtraptor documentation built on June 2, 2025, 5:17 a.m.