R/api.R

Defines functions osem_ensure_api_available osem_endpoint

Documented in osem_endpoint osem_ensure_api_available

# ==============================================================================
#  getters for the opensensemap API.
#  the awesome httr library does all the curling, query and response parsing.
#  for CSV responses (get_measurements) the readr package is a hidden dependency
# ==============================================================================

default_api = 'https://api.opensensemap.org'

#' Get the default openSenseMap API endpoint
#' @export
#' @return A character string with the HTTP URL of the openSenseMap API
osem_endpoint = function() default_api

#' Check if the given openSenseMap API endpoint is available
#' @param endpoint The API base URL to check, defaulting to \code{\link{osem_endpoint}}
#' @return \code{TRUE} if the API is available, otherwise \code{stop()} is called.
osem_ensure_api_available = function(endpoint = osem_endpoint()) {
  code = FALSE
  try({
    code = httr::status_code(httr::GET(endpoint, path='stats'))
  }, silent = TRUE)
  
  if (code == 200)
    return(TRUE)
  
  errtext = paste('The API at', endpoint, 'is currently not available.')
  if (code != FALSE)
    errtext = paste0(errtext, ' (HTTP code ', code, ')')
  if (endpoint == default_api)
    errtext = c(errtext, 'If the issue persists, please check back at https://status.sensebox.de/778247404 and notify support@sensebox.de')
  stop(paste(errtext, collapse='\n  '), call. = FALSE)
  FALSE
}

get_boxes_ = function (..., endpoint) {
  response = osem_get_resource(endpoint, path = c('boxes'), ...)

  if (length(response) == 0) {
    warning('no senseBoxes found for this query')
    return(osem_as_sensebox(as.data.frame(response)))
  }

  # parse each list element as sensebox & combine them to a single data.frame
  boxesList = lapply(response, parse_senseboxdata)
  df = dplyr::bind_rows(boxesList)
  df$exposure = df$exposure %>% as.factor()
  df$model    = df$model %>% as.factor()
  if (!is.null(df$grouptag)){
    df$grouptag = df$grouptag %>% as.factor()
  }
  df
}

get_box_ = function (boxId, endpoint, ...) {
  osem_get_resource(endpoint, path = c('boxes', boxId), ..., progress = FALSE) %>%
    parse_senseboxdata()
}

parse_measurement_csv = function (resText) {
  # parse the CSV response manually & mute readr
  suppressWarnings({
    result = readr::read_csv(resText, col_types = readr::cols(
      # factor as default would raise issues with concatenation of multiple requests
      .default  = readr::col_character(),
      createdAt = readr::col_datetime(),
      value  = readr::col_double(),
      lat    = readr::col_double(),
      lon    = readr::col_double(),
      height = readr::col_double()
    ))
  })

  osem_as_measurements(result)
} 

get_measurements_ = function (..., endpoint) {
  osem_get_resource(endpoint, c('boxes', 'data'), ..., type = 'text') %>%
    parse_measurement_csv
}

get_stats_ = function (endpoint, cache) {
  result = osem_get_resource(endpoint, path = c('stats'), progress = FALSE, cache = cache)
  names(result) = c('boxes', 'measurements', 'measurements_per_minute')
  result
}

#' Get any resource from openSenseMap API, possibly cache the response
#'
#' @param host API host
#' @param path resource URL
#' @param ... All other parameters interpreted as request query parameters
#' @param type Passed to httr; 'parsed' to return an R object from the response, 'text for a raw response
#' @param progress Boolean whether to print download progress information
#' @param cache Optional path to a directory were responses will be cached. If not NA, no requests will be made when a request for the given is already cached.
#' @return Result of a Request to openSenseMap API
#' @noRd
osem_get_resource = function (host, path, ..., type = 'parsed', progress = TRUE, cache = NA) {
  query = list(...)
  if (!is.na(cache)) {
    filename = osem_cache_filename(path, query, host) %>% paste(cache, ., sep = '/')
    if (file.exists(filename))
      return(readRDS(filename))
  }

  res = osem_request_(host, path, query, type, progress)
  if (!is.na(cache)) saveRDS(res, filename)
  res
}

osem_cache_filename = function (path, query = list(), host = osem_endpoint()) {
  httr::modify_url(url = host, path = path, query = query) %>%
    digest::digest(algo = 'sha1') %>%
    paste('osemcache', ., 'rds', sep = '.')
}

#' Purge cached responses from the given cache directory
#'
#' @param location A path to the cache directory, defaults to the
#'   sessions' \code{tempdir()}
#' @return Boolean whether the deletion was successful
#'
#' @export
#' @examples
#' \dontrun{
#'   osem_boxes(cache = tempdir())
#'   osem_clear_cache()
#'
#'   cachedir = paste(getwd(), 'osemcache', sep = '/')
#'   dir.create(file.path(cachedir), showWarnings = FALSE)
#'   osem_boxes(cache = cachedir)
#'   osem_clear_cache(cachedir)
#' }
osem_clear_cache = function (location = tempdir()) {
  list.files(location, pattern = 'osemcache\\..*\\.rds') %>%
    lapply(function (f) file.remove(paste(location, f, sep = '/'))) %>%
    unlist() %>%
    all()
}

osem_request_ = function (host, path, query = list(), type = 'parsed', progress = TRUE) {
  # stop() if API is not available
  osem_ensure_api_available(host)
  
  progress = if (progress && !is_non_interactive()) httr::progress() else NULL
  res = httr::GET(host, progress, path = path, query = query)

  if (httr::http_error(res)) {
    content = httr::content(res, 'parsed', encoding = 'UTF-8')
    stop(if ('message' %in% names(content)) content$message else httr::status_code(res))
  }

  content = httr::content(res, type, encoding = 'UTF-8')
}
sensebox/opensensmapR documentation built on March 12, 2023, 8:09 a.m.