diff --git a/.Rbuildignore b/.Rbuildignore index d1a82a8..e42aee3 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -2,3 +2,7 @@ ^\.Rproj\.user$ ^CHANGES\.md$ ^tools*$ +^\.travis\.yml$ +^appveyor\.yml$ +^CONDUCT\.md$ +^codecov\.yml$ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a9e1036 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r + +language: R +sudo: false +cache: packages + +r: + - release + - devel + +r_github_packages: + - r-lib/covr + +before_install: + - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable --yes + - sudo apt-get --yes --force-yes update -qq + # units/udunits2 dependency: + - sudo apt-get install --yes libudunits2-dev + # sf dependencies: + - sudo apt-get install --yes libproj-dev libgeos-dev libgdal-dev + +after_success: + - Rscript -e 'covr::codecov()' + #- Rscript -e 'lintr::lint_package()' diff --git a/CONDUCT.md b/CONDUCT.md new file mode 100644 index 0000000..52a673e --- /dev/null +++ b/CONDUCT.md @@ -0,0 +1,25 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or +imagery, derogatory comments or personal attacks, trolling, public or private harassment, +insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed +from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the Contributor Covenant +(http:contributor-covenant.org), version 1.0.0, available at +http://contributor-covenant.org/version/1/0/0/ diff --git a/DESCRIPTION b/DESCRIPTION index 7142aae..c169762 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: opensensmapr Type: Package Title: Work with Sensor Data from opensensemap.org in R -Version: 0.3.2 +Version: 0.3.3 URL: http://github.com/noerw/opensensmapR BugReports: http://github.com/noerw/opensensmapR/issues Imports: @@ -17,9 +17,11 @@ Suggests: knitr, rmarkdown, lubridate, - units -Author: Norwin Roosen -Maintainer: Norwin Roosen + units, + testthat, + covr +Authors@R: c(person("Norwin", "Roosen", role = c("aut", "cre"), email = "bugs@nroo.de"), + person("Daniel", "Nuest", role = c("ctb"), email = "daniel.nuest@uni-muenster.de", comment = c(ORCID = "0000-0003-2392-6140"))) Description: Download data (environmental measurements, sensorstations) from the API of open data sensor web platform 'opensensemap.org' for analysis in R. This platform provides realtime data of more than 1000 low-cost sensor diff --git a/NAMESPACE b/NAMESPACE index 89b3a1a..cfff8db 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ export(osem_as_sensebox) export(osem_box) export(osem_boxes) export(osem_counts) +export(osem_endpoint) export(osem_measurements) export(osem_phenomena) export(st_as_sf.osem_measurements) diff --git a/R/00utils.R b/R/00utils.R index 9c60f28..fb042fb 100644 --- a/R/00utils.R +++ b/R/00utils.R @@ -29,7 +29,7 @@ dplyr_class_wrapper = function(callback) { #' the callstack. See https://stackoverflow.com/a/33108841 #' #' @noRd -isNonInteractive = function () { +is_non_interactive = function () { ff <- sapply(sys.calls(), function(f) as.character(f[1])) any(ff %in% c("knit2html", "render")) || !interactive() } diff --git a/R/api.R b/R/api.R index 1726aea..ff7911e 100644 --- a/R/api.R +++ b/R/api.R @@ -4,12 +4,19 @@ # for CSV responses (get_measurements) the readr package is a hidden dependency # ============================================================================== +#' Get the default openSenseMap API endpoint +#' @export +#' @return A character string with the HTTP URL of the openSenseMap API +osem_endpoint = function() { + 'https://api.opensensemap.org' +} + get_boxes_ = function (..., endpoint) { response = osem_request_(endpoint, path = c('boxes'), ...) if (length(response) == 0) { - warning('no boxes found for this query') - return(response) + 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 @@ -17,7 +24,8 @@ get_boxes_ = function (..., endpoint) { df = dplyr::bind_rows(boxesList) df$exposure = df$exposure %>% as.factor() df$model = df$model %>% as.factor() - df$grouptag = df$grouptag %>% as.factor() + if(!is.null(df$grouptag)) + df$grouptag = df$grouptag %>% as.factor() df } @@ -52,7 +60,7 @@ get_stats_ = function (endpoint) { } osem_request_ = function (host, path, ..., type = 'parsed', progress) { - progress = if (progress && !isNonInteractive()) httr::progress() else NULL + progress = if (progress && !is_non_interactive()) httr::progress() else NULL res = httr::GET(host, progress, path = path, query = list(...)) if (httr::http_error(res)) { diff --git a/R/box.R b/R/box.R index dcc2b73..49356d8 100644 --- a/R/box.R +++ b/R/box.R @@ -19,7 +19,7 @@ #' @param phenomenon Only return boxes that measured the given phenomenon in the #' time interval as specified through \code{date} or \code{from / to} #' @param endpoint The URL of the openSenseMap API instance -#' @param progress Whether to print download progress information +#' @param progress Whether to print download progress information defaults to \code{TRUE} #' @return A \code{sensebox data.frame} containing a box in each row #' #' @seealso \href{https://docs.opensensemap.org/#api-Measurements-findAllBoxes}{openSenseMap API documentation (web)} @@ -38,8 +38,8 @@ #' osem_boxes = function (exposure = NA, model = NA, grouptag = NA, date = NA, from = NA, to = NA, phenomenon = NA, - endpoint = 'https://api.opensensemap.org', - progress = T) { + endpoint = osem_endpoint(), + progress = TRUE) { # error, if phenomenon, but no time given if (!is.na(phenomenon) && is.na(date) && is.na(to) && is.na(from)) @@ -87,7 +87,7 @@ osem_boxes = function (exposure = NA, model = NA, grouptag = NA, #' # get a specific box by ID #' b = osem_box('57000b8745fd40c8196ad04c') #' -osem_box = function (boxId, endpoint = 'https://api.opensensemap.org') { +osem_box = function (boxId, endpoint = osem_endpoint()) { get_box_(boxId, endpoint = endpoint) } diff --git a/R/box_utils.R b/R/box_utils.R index d5d964f..92460d1 100644 --- a/R/box_utils.R +++ b/R/box_utils.R @@ -33,7 +33,8 @@ plot.sensebox = function (x, ..., mar = c(2,2,1,1)) { print.sensebox = function(x, ...) { important_columns = c('name', 'exposure', 'lastMeasurement', 'phenomena') data = as.data.frame(x) - print(data[important_columns], ...) + available_columns = important_columns %in% names(data) + print(data[available_columns], ...) invisible(x) } @@ -106,7 +107,7 @@ mutate.sensebox = dplyr_class_wrapper(osem_as_sensebox) #' @export `[.sensebox` = function(x, i, ...) { s = NextMethod('[') - mostattributes(s) = attributes(x) + mostattributes(s) = attributes(s) s } diff --git a/R/counts.R b/R/counts.R index 6636b1f..854b002 100644 --- a/R/counts.R +++ b/R/counts.R @@ -11,6 +11,6 @@ #' #' @export #' @seealso \href{https://docs.opensensemap.org/#api-Misc-getStatistics}{openSenseMap API documentation (web)} -osem_counts = function (endpoint = 'https://api.opensensemap.org') { +osem_counts = function(endpoint = osem_endpoint()) { get_stats_(endpoint) } diff --git a/R/measurement.R b/R/measurement.R index 48b69e9..53937c9 100644 --- a/R/measurement.R +++ b/R/measurement.R @@ -18,7 +18,7 @@ #' @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 oSeM documentation) +#' @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 #' @@ -36,7 +36,7 @@ osem_measurements = function (x, ...) UseMethod('osem_measurements') #' @export #' @examples #' # get measurements from all boxes -#' osem_measurements('Windrichtung') +#' m1 = osem_measurements('Windrichtung') #' osem_measurements.default = function (x, ...) { bbox = structure(c(-180, -90, 180, 90), class = 'bbox') @@ -50,16 +50,16 @@ osem_measurements.default = function (x, ...) { #' @examples #' # get measurements from sensors within a bounding box #' bbox = structure(c(7, 51, 8, 52), class = 'bbox') -#' osem_measurements(bbox, 'Temperatur') +#' m2 = osem_measurements(bbox, 'Temperatur') #' #' points = sf::st_multipoint(matrix(c(7.5, 7.8, 51.7, 52), 2, 2)) #' bbox2 = sf::st_bbox(points) -#' osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor') +#' m3 = osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor') #' osem_measurements.bbox = function (x, phenomenon, exposure = NA, from = NA, to = NA, columns = NA, ..., - endpoint = 'https://api.opensensemap.org', + endpoint = osem_endpoint(), progress = T) { bbox = x environment() %>% @@ -79,12 +79,12 @@ osem_measurements.bbox = function (x, phenomenon, exposure = NA, #' #' # ...or a single box #' b = osem_box('57000b8745fd40c8196ad04c') -#' osem_measurements(b, phenomenon = 'Temperatur') +#' m4 = osem_measurements(b, phenomenon = 'Temperatur') #' osem_measurements.sensebox = function (x, phenomenon, exposure = NA, from = NA, to = NA, columns = NA, ..., - endpoint = 'https://api.opensensemap.org', + endpoint = osem_endpoint(), progress = T) { boxes = x environment() %>% @@ -106,8 +106,10 @@ parse_get_measurements_params = function (params) { if (is.null(params$phenomenon) | is.na(params$phenomenon)) stop('Parameter "phenomenon" is required') - if (!is.na(params$from) && is.na(params$to)) - stop('specify "from" only together with "to"') + 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)) || @@ -130,7 +132,7 @@ parse_get_measurements_params = function (params) { } if (!is.na(params$exposure)) query$exposure = params$exposure - if (!is.na(params$columns)) + if (!any(is.na(params$columns))) query$columns = paste(params$columns, collapse = ',') else query$columns = 'value,createdAt,lon,lat,sensorId,unit' @@ -160,7 +162,7 @@ paged_measurements_req = function (query) { query$`to-date` = date_as_isostring(page$to) res = do.call(get_measurements_, query) - if (query$progress && !isNonInteractive()) + if (query$progress && !is_non_interactive()) cat(paste(query$`from-date`, query$`to-date`, sep = ' - '), '\n') res diff --git a/R/measurement_utils.R b/R/measurement_utils.R index 75d83d1..3291be9 100644 --- a/R/measurement_utils.R +++ b/R/measurement_utils.R @@ -43,7 +43,7 @@ mutate.osem_measurements = dplyr_class_wrapper(osem_as_measurements) #' @export `[.osem_measurements` = function(x, i, ...) { s = NextMethod('[') - mostattributes(s) = attributes(x) + mostattributes(s) = attributes(s) s } diff --git a/README.md b/README.md index 0cdd517..4b76fca 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # opensensmapr -This R package ingests data (environmental measurements, sensor stations) from -the API of opensensemap.org for analysis in R. + +[![CRAN status](http://www.r-pkg.org/badges/version/opensensmapr)](https://cran.r-project.org/package=opensensmapr) [![Travis build status](https://travis-ci.org/noerw/opensensmapR.svg?branch=master)](https://travis-ci.org/noerw/opensensmapR) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/noerw/opensensmapR?branch=master&svg=true)](https://ci.appveyor.com/project/noerw/opensensmapR) [![Coverage status](https://codecov.io/gh/noerw/opensensmapR/branch/master/graph/badge.svg)](https://codecov.io/github/noerw/opensensmapR?branch=master) + +This R package ingests data (environmental measurements, sensor stations) from the API of opensensemap.org for analysis in R. The package aims to be compatible with sf and the tidyverse. ## Installation + Right now, the package is not on CRAN. To install it from GitHub, run: ```r @@ -12,9 +15,10 @@ devtools::install_github('noerw/opensensmapr') ``` ## Usage + A verbose usage example is shown in the vignette [`osem-intro`](https://noerw.github.com/opensensmapR/inst/doc/osem-intro.html). -Each functions documentation can be viewed with `?`. An overview -is given in `?opensensmapr`. +Each functions documentation can be viewed with `?`. +An overview is given in `?opensensmapr`. In short, the following pseudocode shows the main functions for data retrieval: ```r @@ -39,17 +43,23 @@ osem_counts() Additionally there are some helpers: `summary.sensebox(), plot.sensebox(), st_as_sf.sensebox(), osem_as_sensebox(), [.sensebox(), filter.sensebox(), mutate.sensebox(), ...`. ## Changelog -This project adheres to semantic versioning, for changes in recent versions -please consult [CHANGES.md](CHANGES.md). + +This project adheres to semantic versioning, for changes in recent versions please consult [CHANGES.md](CHANGES.md). ## FAQ -- *Whats up with that package name?* idk, the R people seem to [enjoy][1] -[dropping][2] [vovels][3] so.. Unfortunately I couldn't fit the naming -convention to drop an `y` in there. + +- *Whats up with that package name?* idk, the R people seem to [enjoy][1] [dropping][2] [vovels][3] so.. +Unfortunately I couldn't fit the naming convention to drop an `y` in there. [1]: https://github.com/tidyverse/readr [2]: https://github.com/tidyverse/dplyr [3]: https://github.com/tidyverse/tidyr +## Development + +Please note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). +By participating in this project you agree to abide by its terms. + ## License + GPL-2.0 - Norwin Roosen diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..c6c1438 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,45 @@ +# DO NOT CHANGE the "init" and "install" sections below + +# Download script file from GitHub +init: + ps: | + $ErrorActionPreference = "Stop" + Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" + Import-Module '..\appveyor-tool.ps1' + +install: + ps: Bootstrap + +cache: + - C:\RLibrary + +# Adapt as necessary starting from here + +build_script: + - travis-tool.sh install_deps + +test_script: + - travis-tool.sh run_tests + +on_failure: + - 7z a failure.zip *.Rcheck\* + - appveyor PushArtifact failure.zip + +artifacts: + - path: '*.Rcheck\**\*.log' + name: Logs + + - path: '*.Rcheck\**\*.out' + name: Logs + + - path: '*.Rcheck\**\*.fail' + name: Logs + + - path: '*.Rcheck\**\*.Rout' + name: Logs + + - path: '\*_*.tar.gz' + name: Bits + + - path: '\*_*.zip' + name: Bits diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..8f36b6c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,12 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: auto + threshold: 1% diff --git a/man/opensensmapr.Rd b/man/opensensmapr.Rd index 72ddee2..1cd5da0 100644 --- a/man/opensensmapr.Rd +++ b/man/opensensmapr.Rd @@ -91,3 +91,12 @@ openSenseMap API: \url{https://api.opensensemap.org/} official openSenseMap API documentation: \url{https://docs.opensensemap.org/} } +\author{ +\strong{Maintainer}: Norwin Roosen \email{bugs@nroo.de} + +Other contributors: +\itemize{ + \item Daniel Nuest \email{daniel.nuest@uni-muenster.de} (0000-0003-2392-6140) [contributor] +} + +} diff --git a/man/osem_box.Rd b/man/osem_box.Rd index 656d5a9..f891654 100644 --- a/man/osem_box.Rd +++ b/man/osem_box.Rd @@ -4,7 +4,7 @@ \alias{osem_box} \title{Get a single senseBox by its ID} \usage{ -osem_box(boxId, endpoint = "https://api.opensensemap.org") +osem_box(boxId, endpoint = osem_endpoint()) } \arguments{ \item{boxId}{A string containing a senseBox ID} diff --git a/man/osem_boxes.Rd b/man/osem_boxes.Rd index 34bb92d..cb845fa 100644 --- a/man/osem_boxes.Rd +++ b/man/osem_boxes.Rd @@ -5,8 +5,8 @@ \title{Get a set of senseBoxes from the openSenseMap} \usage{ osem_boxes(exposure = NA, model = NA, grouptag = NA, date = NA, - from = NA, to = NA, phenomenon = NA, - endpoint = "https://api.opensensemap.org", progress = T) + from = NA, to = NA, phenomenon = NA, endpoint = osem_endpoint(), + progress = TRUE) } \arguments{ \item{exposure}{Only return boxes with the given exposure ('indoor', 'outdoor', 'mobile')} @@ -26,7 +26,7 @@ time interval as specified through \code{date} or \code{from / to}} \item{endpoint}{The URL of the openSenseMap API instance} -\item{progress}{Whether to print download progress information} +\item{progress}{Whether to print download progress information defaults to \code{TRUE}} } \value{ A \code{sensebox data.frame} containing a box in each row diff --git a/man/osem_counts.Rd b/man/osem_counts.Rd index eff45d9..cc7c9ef 100644 --- a/man/osem_counts.Rd +++ b/man/osem_counts.Rd @@ -4,7 +4,7 @@ \alias{osem_counts} \title{Get count statistics of the openSenseMap Instance} \usage{ -osem_counts(endpoint = "https://api.opensensemap.org") +osem_counts(endpoint = osem_endpoint()) } \arguments{ \item{endpoint}{The URL of the openSenseMap API} diff --git a/man/osem_endpoint.Rd b/man/osem_endpoint.Rd new file mode 100644 index 0000000..48ac109 --- /dev/null +++ b/man/osem_endpoint.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/api.R +\name{osem_endpoint} +\alias{osem_endpoint} +\title{Get the default openSenseMap API endpoint} +\usage{ +osem_endpoint() +} +\value{ +A character string with the HTTP URL of the openSenseMap API +} +\description{ +Get the default openSenseMap API endpoint +} diff --git a/man/osem_measurements.Rd b/man/osem_measurements.Rd index 7ec3b90..08bc4d3 100644 --- a/man/osem_measurements.Rd +++ b/man/osem_measurements.Rd @@ -12,12 +12,11 @@ osem_measurements(x, ...) \method{osem_measurements}{default}(x, ...) \method{osem_measurements}{bbox}(x, phenomenon, exposure = NA, from = NA, - to = NA, columns = NA, ..., endpoint = "https://api.opensensemap.org", - progress = T) + to = NA, columns = NA, ..., endpoint = osem_endpoint(), progress = T) \method{osem_measurements}{sensebox}(x, phenomenon, exposure = NA, - from = NA, to = NA, columns = NA, ..., - endpoint = "https://api.opensensemap.org", progress = T) + from = NA, to = NA, columns = NA, ..., endpoint = osem_endpoint(), + progress = T) } \arguments{ \item{x}{Depending on the method, either @@ -38,7 +37,7 @@ osem_measurements(x, ...) \item{to}{A \code{POSIXt} like object to select a time interval} -\item{columns}{Select specific column in the output (see oSeM documentation)} +\item{columns}{Select specific column in the output (see openSenseMap API documentation)} \item{endpoint}{The URL of the openSenseMap API} @@ -64,15 +63,15 @@ a bounding box spanning the whole world. \examples{ # get measurements from all boxes -osem_measurements('Windrichtung') +m1 = osem_measurements('Windrichtung') # get measurements from sensors within a bounding box bbox = structure(c(7, 51, 8, 52), class = 'bbox') -osem_measurements(bbox, 'Temperatur') +m2 = osem_measurements(bbox, 'Temperatur') points = sf::st_multipoint(matrix(c(7.5, 7.8, 51.7, 52), 2, 2)) bbox2 = sf::st_bbox(points) -osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor') +m3 = osem_measurements(bbox2, 'Temperatur', exposure = 'outdoor') # get measurements from a set of boxes b = osem_boxes(grouptag = 'ifgi') @@ -80,7 +79,7 @@ osem_measurements(b, phenomenon = 'Temperatur') # ...or a single box b = osem_box('57000b8745fd40c8196ad04c') -osem_measurements(b, phenomenon = 'Temperatur') +m4 = osem_measurements(b, phenomenon = 'Temperatur') } \seealso{ diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..e0e7eed --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,5 @@ +library("testthat") +library("opensensmapr") +library("sf") + +test_check("opensensmapr") diff --git a/tests/testthat/test_box.R b/tests/testthat/test_box.R new file mode 100644 index 0000000..4d92141 --- /dev/null +++ b/tests/testthat/test_box.R @@ -0,0 +1,31 @@ +context("box") + +check_api <- function() { + skip_on_cran() + + code <- NA + try(code <- httr::status_code(httr::GET(osem_endpoint()))) + if (is.na(code)) skip("API not available") +} + +try({ + boxes <- osem_boxes() +}) + +test_that("a single box can be retrieved by ID", { + check_api() + + box <- osem_box(boxes$X_id[[1]]) + + expect_true("sensebox" %in% class(box)) + expect_true("data.frame" %in% class(box)) + expect_true(nrow(box) == 1) + expect_true(box$X_id == boxes$X_id[[1]]) +}) + + +test_that("[.sensebox maintains attributes", { + check_api() + + expect_true(all(attributes(boxes[1:nrow(boxes), ]) %in% attributes(boxes))) +}) diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R new file mode 100644 index 0000000..c0d925e --- /dev/null +++ b/tests/testthat/test_boxes.R @@ -0,0 +1,112 @@ +context("boxes") + +check_api <- function() { + skip_on_cran() + + code <- NA + try(code <- httr::status_code(httr::GET(osem_endpoint()))) + if (is.na(code)) skip("API not available") +} + +test_that("a list of all boxes can be retrieved and returns a sensebox data.frame", { + check_api() + + boxes <- osem_boxes() + expect_true(is.data.frame(boxes)) + expect_true(is.factor(boxes$model)) + expect_true(is.character(boxes$name)) + expect_length(names(boxes), 14) + expect_true(any("sensebox" %in% class(boxes))) +}) + +test_that("both from and to are required when requesting boxes, error otherwise", { + expect_error(osem_boxes(from = as.POSIXct("2017-01-01")), "must be used together") + expect_error(osem_boxes(to = as.POSIXct("2017-01-01")), "must be used together") +}) + +test_that("a list of boxes with exposure filter returns only the requested exposure", { + check_api() + + boxes <- osem_boxes(exposure = "mobile") + expect_true(all(boxes$exposure == "mobile")) +}) + +test_that("a list of boxes with model filter returns only the requested model", { + check_api() + + boxes <- osem_boxes(model = "homeWifi") + expect_true(all(boxes$model == "homeWifi")) +}) + +test_that("box query can combine exposure and model filter", { + check_api() + + boxes <- osem_boxes(exposure = "mobile", model = "homeWifi") + expect_true(all(boxes$model == "homeWifi")) + expect_true(all(boxes$exposure == "mobile")) +}) + +test_that("a list of boxes with grouptype returns only boxes of that group", { + check_api() + + boxes <- osem_boxes(grouptag = "codeformuenster") + expect_true(all(boxes$grouptag == "codeformuenster")) +}) + +test_that("endpoint can be (mis)configured", { + check_api() + + expect_error(osem_boxes(endpoint = "http://not.the.opensensemap.org"), "resolve host") +}) + +test_that("a response with no matches returns empty sensebox data.frame and a warning", { + check_api() + + suppressWarnings(boxes <- osem_boxes(grouptag = "does_not_exist")) + expect_true(is.data.frame(boxes)) + expect_true(any("sensebox" %in% class(boxes))) +}) + +test_that("a response with no matches gives a warning", { + check_api() + + expect_warning(osem_boxes(grouptag = "does_not_exist"), "no senseBoxes found") +}) + +test_that("data.frame can be converted to sensebox data.frame", { + df <- osem_as_sensebox(data.frame(c(1,2), c("a", "b"))) + expect_equal(class(df), c("sensebox", "data.frame")) +}) + +test_that("boxes can be converted to sf object", { + check_api() + + boxes <- osem_boxes() + boxes_sf <- sf::st_as_sf(boxes) + + expect_true(all(sf::st_is_simple(boxes_sf))) + expect_true("sf" %in% class(boxes_sf)) +}) + +test_that("boxes converted to sf object keep all attributes", { + check_api() + + boxes <- osem_boxes() + boxes_sf <- sf::st_as_sf(boxes) + + # coord columns get removed! + cols <- names(boxes)[!names(boxes) %in% c('lon', 'lat')] + expect_true(all(cols %in% names(boxes_sf))) + + expect_true("sensebox" %in% class(boxes_sf)) +}) + +test_that("box retrieval does not give progress information in non-interactive mode", { + check_api() + + if (!opensensmapr:::is_non_interactive()) skip("interactive session") + + out <- capture.output(b <- osem_boxes()) + expect_length(out, 0) +}) + diff --git a/tests/testthat/test_counts.R b/tests/testthat/test_counts.R new file mode 100644 index 0000000..f4c7d6a --- /dev/null +++ b/tests/testthat/test_counts.R @@ -0,0 +1,19 @@ +context("counts") + +check_api <- function() { + skip_on_cran() + + code <- NA + try(code <- httr::status_code(httr::GET(osem_endpoint()))) + if (is.na(code)) skip("API not available") +} + +test_that("counts can be retrieved as a list of numbers", { + check_api() + + counts <- osem_counts() + + expect_true(is.list(counts)) + expect_true(is.numeric(unlist(counts))) + expect_length(counts, 3) +}) diff --git a/tests/testthat/test_measurements.R b/tests/testthat/test_measurements.R new file mode 100644 index 0000000..fb85bd7 --- /dev/null +++ b/tests/testthat/test_measurements.R @@ -0,0 +1,121 @@ +context("measurements") + +check_api <- function() { + skip_on_cran() + + code <- NA + try(code <- httr::status_code(httr::GET(osem_endpoint()))) + if (is.na(code)) skip("API not available") +} + +try({ + boxes <- osem_boxes() + }) + +test_that("measurements can be retrieved for a phenomenon", { + check_api() + + measurements <- osem_measurements(x = "Windgeschwindigkeit") + expect_true(is.data.frame(measurements)) + expect_true("osem_measurements" %in% class(measurements)) +}) + +test_that("measurement retrieval does not give progress information in non-interactive mode", { + check_api() + + if (!opensensmapr:::is_non_interactive()) skip("interactive session") + + out <- capture.output(measurements <- osem_measurements(x = "Licht")) + expect_length(out, 0) +}) + +test_that("a response with no matching senseBoxes gives an error", { + check_api() + + expect_error(osem_measurements(x = "Windgeschwindigkeit", exposure = "indoor"), "No senseBoxes found") +}) + +test_that("data.frame can be converted to measurements data.frame", { + df <- osem_as_measurements(data.frame(c(1,2), c("a", "b"))) + expect_equal(class(df), c("osem_measurements", "data.frame")) +}) + +test_that("columns can be specified for phenomena", { + check_api() + + cols <- c("value", "boxId", "boxName") + measurements <- osem_measurements(x = "Windgeschwindigkeit", columns = cols) + expect_equal(names(measurements), cols) +}) + +test_that("measurements can be retrieved for a phenomenon and exposure", { + check_api() + + measurements <- osem_measurements(x = "Temperatur", exposure = "unknown", + columns = c("value", "boxId", "boxName")) + expect_equal(nrow(measurements), 0) +}) + +test_that("measurements of specific boxes can be retrieved for one phenomenon and returns a measurements data.frame", { + check_api() + + # fix for subsetting + class(boxes) <- c("data.frame") + three_boxes <- boxes[1:3,] + class(boxes) <- c("sensebox", "data.frame") + three_boxes <- osem_as_sensebox(three_boxes) + phens <- names(osem_phenomena(three_boxes)) + + measurements <- osem_measurements(x = three_boxes, phenomenon = phens[[1]]) + expect_true(is.data.frame(measurements)) + expect_true("osem_measurements" %in% class(measurements)) +}) + +test_that("measurements can be retrieved for a bounding box", { + check_api() + + sfc <- sf::st_sfc(sf::st_linestring(x = matrix(data = c(7, 8, 50, 51), ncol = 2)), crs = 4326) + bbox <- sf::st_bbox(sfc) + measurements <- osem_measurements(x = bbox, phenomenon = "Windrichtung") + expect_true(all(unique(measurements$lat) > 50)) + expect_true(all(unique(measurements$lat) < 51)) + expect_true(all(unique(measurements$lon) < 8)) + expect_true(all(unique(measurements$lon) > 7)) +}) + +test_that("measurements can be retrieved for a time period", { + check_api() + + from_date <- as.POSIXct("2018-01-01 12:00:00") + to_date <- as.POSIXct("2018-01-01 13:00:00") + measurements <- osem_measurements(x = "Temperature", from = from_date, to = to_date) + + expect_true(all(measurements$createdAt < to_date)) + expect_true(all(measurements$createdAt > from_date)) +}) + +test_that("measurements can be retrieved for a time period > 31 days", { + check_api() + + from_date <- as.POSIXct("2017-11-01 12:00:00") + to_date <- as.POSIXct("2018-01-01 13:00:00") + measurements <- osem_measurements(x = "Windrichtung", from = from_date, to = to_date) + + expect_true(all(measurements$createdAt < to_date)) + expect_true(all(measurements$createdAt > from_date)) +}) + +test_that("both from and to are required when requesting measurements, error otherwise", { + expect_error(osem_measurements(x = "Temperature", from = as.POSIXct("2017-01-01")), "only together with") + expect_error(osem_measurements(x = "Temperature", to = as.POSIXct("2017-01-01")), "only together with") +}) + +test_that("[.osem_measurements maintains attributes", { + check_api() + + sfc <- sf::st_sfc(sf::st_linestring(x = matrix(data = c(7, 8, 50, 51), ncol = 2)), crs = 4326) + bbox <- sf::st_bbox(sfc) + m <- osem_measurements(x = bbox, phenomenon = "Windrichtung") + + expect_true(all(attributes(m[1:nrow(m), ]) %in% attributes(m))) +}) diff --git a/tests/testthat/test_phenomena.R b/tests/testthat/test_phenomena.R new file mode 100644 index 0000000..5cf7682 --- /dev/null +++ b/tests/testthat/test_phenomena.R @@ -0,0 +1,40 @@ +context("phenomena") + +check_api <- function() { + skip_on_cran() + + code <- NA + try(code <- httr::status_code(httr::GET(osem_endpoint()))) + if (is.na(code)) skip("API not available") +} + +try({ + boxes <- osem_boxes() + all_phen <- unique(unlist(boxes$phenomena)) +}) + +test_that("phenomena from boxes is a list of counts", { + check_api() + + phenomena <- osem_phenomena(boxes) + + expect_true(is.numeric(unlist(phenomena))) + expect_true(is.list(phenomena)) +}) + +test_that("phenomena from boxes has all phenomena", { + check_api() + + phenomena <- osem_phenomena(boxes) + + expect_true(all(all_phen %in% names(phenomena))) + expect_true(all(names(phenomena) %in% all_phen)) +}) + +test_that("phenomena from a not sensebox data.frame returns error", { + expect_error(osem_phenomena(list()), "no applicable method") + expect_error(osem_phenomena(data.frame()), "no applicable method") + boxes_df <- boxes + class(boxes_df) <- c("data.frame") + expect_error(osem_phenomena(boxes_df), "no applicable method") +})