You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
8.5 KiB
R
230 lines
8.5 KiB
R
# ==============================================================================
|
|
#
|
|
#' Fetch the Measurements of a Phenomenon on opensensemap.org
|
|
#'
|
|
#' Measurements can be retrieved either for a set of boxes, or through a spatial
|
|
#' bounding box filter. To get all measurements, the \code{default} function applies
|
|
#' a bounding box spanning the whole world.
|
|
#'
|
|
#' @param x Depending on the method, either
|
|
#' \enumerate{
|
|
#' \item a \code{chr} specifying the phenomenon, see \code{phenomenon}
|
|
#' \item a \code{\link[sf]{st_bbox}} to select sensors spatially,
|
|
#' \item a \code{sensebox data.frame} to select boxes from which
|
|
#' measurements will be retrieved,
|
|
#' }
|
|
#' @param ... see parameters below
|
|
#' @param phenomenon The phenomenon to retrieve measurements for
|
|
#' @param exposure Filter sensors by their exposure ('indoor', 'outdoor', 'mobile')
|
|
#' @param from A \code{POSIXt} like object to select a time interval
|
|
#' @param to A \code{POSIXt} like object to select a time interval
|
|
#' @param columns Select specific column in the output (see openSenseMap API documentation)
|
|
#' @param endpoint The URL of the openSenseMap API
|
|
#' @param progress Whether to print download progress information
|
|
#' @param cache Whether to cache the result, defaults to false.
|
|
#' If a valid path to a directory is given, the response will be cached there. Subsequent identical requests will return the cached data instead.
|
|
#'
|
|
#' @return An \code{osem_measurements data.frame} containing the
|
|
#' requested measurements
|
|
#'
|
|
#' @export
|
|
#' @seealso \href{https://docs.opensensemap.org/#api-Measurements-getDataMulti}{openSenseMap API documentation (web)}
|
|
#' @seealso \code{\link{osem_box}}
|
|
#' @seealso \code{\link{osem_boxes}}
|
|
#' @seealso \code{\link{osem_clear_cache}}
|
|
osem_measurements = function (x, ...) UseMethod('osem_measurements')
|
|
|
|
# ==============================================================================
|
|
#
|
|
#' @describeIn osem_measurements Get measurements from \strong{all} senseBoxes.
|
|
#' @export
|
|
#' @examples
|
|
#' \dontrun{
|
|
#' # get measurements from all boxes on the phenomenon 'PM10' from the last 48h
|
|
#' m = osem_measurements('PM10')
|
|
#'
|
|
#' # get measurements from all mobile boxes on the phenomenon 'PM10' from the last 48h
|
|
#' m = osem_measurements('PM10', exposure = 'mobile')
|
|
#'
|
|
#' # get measurements and cache them locally in the working directory.
|
|
#' # subsequent identical requests will load from the cache instead, ensuring
|
|
#' # reproducibility and saving time and bandwidth!
|
|
#' m = osem_measurements('PM10', exposure = 'mobile', cache = getwd())
|
|
#' m = osem_measurements('PM10', exposure = 'mobile', cache = getwd())
|
|
#'
|
|
#' # get measurements returning a custom selection of columns
|
|
#' m = osem_measurements('PM10', exposure = 'mobile', columns = c(
|
|
#' 'value',
|
|
#' 'boxId',
|
|
#' 'sensorType',
|
|
#' 'lat',
|
|
#' 'lon',
|
|
#' 'height'
|
|
#' ))
|
|
#' }
|
|
osem_measurements.default = function (x, ...) {
|
|
bbox = structure(c(-180, -90, 180, 90), class = 'bbox')
|
|
osem_measurements(bbox, x, ...)
|
|
}
|
|
|
|
# ==============================================================================
|
|
#
|
|
#' @describeIn osem_measurements Get measurements by a spatial filter.
|
|
#' @export
|
|
#' @examples
|
|
#' \dontrun{
|
|
#' # get measurements from sensors within a custom WGS84 bounding box
|
|
#' bbox = structure(c(7, 51, 8, 52), class = 'bbox')
|
|
#' m = osem_measurements(bbox, 'Temperatur')
|
|
#'
|
|
#' # construct a bounding box 12km around berlin using the sf package,
|
|
#' # and get measurements from stations within that box
|
|
#' library(sf)
|
|
#' library(units)
|
|
#' bbox2 = st_point(c(13.4034, 52.5120)) %>%
|
|
#' st_sfc(crs = 4326) %>%
|
|
#' st_transform(3857) %>% # allow setting a buffer in meters
|
|
#' st_buffer(set_units(12, km)) %>%
|
|
#' st_transform(4326) %>% # the opensensemap expects WGS 84
|
|
#' st_bbox()
|
|
#' m = osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor')
|
|
#'
|
|
#' # construct a bounding box from two points,
|
|
#' # and get measurements from stations within that box
|
|
#' points = st_multipoint(matrix(c(7.5, 7.8, 51.7, 52), 2, 2))
|
|
#' bbox3 = st_bbox(points)
|
|
#' m = osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor')
|
|
#' }
|
|
osem_measurements.bbox = function (x, phenomenon, exposure = NA,
|
|
from = NA, to = NA, columns = NA,
|
|
...,
|
|
endpoint = osem_endpoint(),
|
|
progress = TRUE,
|
|
cache = NA) {
|
|
bbox = x
|
|
environment() %>%
|
|
as.list() %>%
|
|
parse_get_measurements_params() %>%
|
|
paged_measurements_req()
|
|
}
|
|
|
|
# ==============================================================================
|
|
#
|
|
#' @describeIn osem_measurements Get measurements from a set of senseBoxes.
|
|
#' @export
|
|
#' @examples
|
|
#' \donttest{
|
|
#' # get measurements from a set of boxes
|
|
#' b = osem_boxes(grouptag = 'ifgi')
|
|
#' m4 = osem_measurements(b, phenomenon = 'Temperatur')
|
|
#'
|
|
#' # ...or a single box
|
|
#' b = osem_box('57000b8745fd40c8196ad04c')
|
|
#' m5 = osem_measurements(b, phenomenon = 'Temperatur')
|
|
#'
|
|
#' # get measurements from a single box on the from the last 40 days.
|
|
#' # requests are paged for long time frames, so the APIs limitation
|
|
#' # does not apply!
|
|
#' library(lubridate)
|
|
#' m1 = osem_measurements(
|
|
#' b,
|
|
#' 'Temperatur',
|
|
#' to = now(),
|
|
#' from = now() - days(40)
|
|
#' )
|
|
#' }
|
|
osem_measurements.sensebox = function (x, phenomenon, exposure = NA,
|
|
from = NA, to = NA, columns = NA,
|
|
...,
|
|
endpoint = osem_endpoint(),
|
|
progress = TRUE,
|
|
cache = NA) {
|
|
boxes = x
|
|
environment() %>%
|
|
as.list() %>%
|
|
parse_get_measurements_params() %>%
|
|
paged_measurements_req()
|
|
}
|
|
|
|
# ==============================================================================
|
|
#
|
|
#' Validates and parses the Parameters for use in \code{osem_measurements()}
|
|
#' and sets a default selection of columns, if unspecified.
|
|
#' Dates are not stringified!
|
|
#'
|
|
#' @param params A named \code{list} of parameters
|
|
#' @return A named \code{list} of parsed parameters.
|
|
#' @noRd
|
|
parse_get_measurements_params = function (params) {
|
|
if (is.symbol(params$phenomenon) || is.null(params$phenomenon) || is.na(params$phenomenon))
|
|
stop('Parameter "phenomenon" is required')
|
|
|
|
if (
|
|
(!is.na(params$from) && is.na(params$to)) ||
|
|
(!is.na(params$to) && is.na(params$from))
|
|
) stop('specify "from" only together with "to"')
|
|
|
|
if (
|
|
(!is.null(params$bbox) && !is.null(params$boxes)) ||
|
|
(is.null(params$bbox) && is.null(params$boxes))
|
|
) stop('Specify either "bbox" or "boxes"')
|
|
|
|
query = list(
|
|
endpoint = params$endpoint,
|
|
phenomenon = params$phenomenon,
|
|
progress = params$progress,
|
|
cache = params$cache
|
|
)
|
|
|
|
if (!is.null(params$boxes)) query$boxId = paste(params$boxes$X_id, collapse = ',')
|
|
if (!is.null(params$bbox)) query$bbox = paste(params$bbox, collapse = ',')
|
|
|
|
if (!is.na(params$from) && !is.na(params$to)) {
|
|
parse_dateparams(params$from, params$to) # only for validation sideeffect
|
|
query$`from-date` = date_as_utc(params$from)
|
|
query$`to-date` = date_as_utc(params$to)
|
|
}
|
|
|
|
if (!is.na(params$exposure)) query$exposure = params$exposure
|
|
if (!any(is.na(params$columns)))
|
|
query$columns = paste(params$columns, collapse = ',')
|
|
else
|
|
query$columns = 'value,createdAt,lon,lat,sensorId,unit'
|
|
|
|
query
|
|
}
|
|
|
|
paged_measurements_req = function (query) {
|
|
# no paged requests when dates are not provided
|
|
if (!all(c('from-date', 'to-date') %in% names(query)))
|
|
return(do.call(get_measurements_, query))
|
|
|
|
# auto paging: make a request for one 31day interval each (max supprted length)
|
|
# generate a list 31day intervals
|
|
from = query$`from-date`
|
|
to = query$`to-date`
|
|
dates = list()
|
|
while (from < to) {
|
|
in31days = from + as.difftime(31, units = 'days')
|
|
dates = append(dates, list(list(from = from, to = min(in31days, to))))
|
|
from = in31days + as.difftime(1, units = 'secs')
|
|
}
|
|
|
|
# use the dates as pages for multiple requests
|
|
df = lapply(dates, function(page) {
|
|
query$`from-date` = date_as_isostring(page$from)
|
|
query$`to-date` = date_as_isostring(page$to)
|
|
res = do.call(get_measurements_, query)
|
|
|
|
if (query$progress && !is_non_interactive())
|
|
cat(paste(query$`from-date`, query$`to-date`, sep = ' - '), '\n')
|
|
|
|
res
|
|
}) %>%
|
|
dplyr::bind_rows()
|
|
|
|
# coerce all character columns (sensorId, unit, ...) to factors AFTER binding
|
|
df[sapply(df, is.character)] = lapply(df[sapply(df, is.character)], as.factor)
|
|
df
|
|
}
|