From d3d758d5546533bed4fead7489f0f8b1602a7fd2 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 18:40:22 +0100 Subject: [PATCH 01/19] add tests and Travis CI configuration --- .Rbuildignore | 4 +++ .travis.yml | 5 +++ DESCRIPTION | 6 ++-- tests/testthat.R | 4 +++ tests/testthat/test_boxes.R | 72 +++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test_boxes.R 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..8d139ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r + +language: R +sudo: false +cache: packages diff --git a/DESCRIPTION b/DESCRIPTION index 7142aae..7bb5143 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Imports: dplyr, httr, magrittr -Suggests: +Suggests: maps, maptools, readr, @@ -17,7 +17,9 @@ Suggests: knitr, rmarkdown, lubridate, - units + units, + testthat, + covr Author: Norwin Roosen Maintainer: Norwin Roosen Description: Download data (environmental measurements, sensorstations) from the diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..f580690 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library("testthat") +library("opensensmapr") + +test_check("opensensmapr") diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R new file mode 100644 index 0000000..c1d8892 --- /dev/null +++ b/tests/testthat/test_boxes.R @@ -0,0 +1,72 @@ +context("boxes") + +check_api <- function() { + 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("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", { + check_api() + + 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 boxes 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")) +}) From ef3fb7f4bb63cea128ca649574e65e699fc84168 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 21:48:46 +0100 Subject: [PATCH 02/19] add function wrapper for default endpoint --- NAMESPACE | 1 + R/api.R | 12 ++++++++++-- R/box.R | 4 ++-- R/box_utils.R | 3 ++- R/counts.R | 2 +- R/measurement.R | 4 ++-- README.md | 19 +++++++++++++++---- man/osem_box.Rd | 2 +- man/osem_boxes.Rd | 4 ++-- man/osem_counts.Rd | 2 +- man/osem_endpoint.Rd | 14 ++++++++++++++ man/osem_measurements.Rd | 7 +++---- tests/testthat/test_boxes.R | 4 ++-- tests/testthat/test_counts.R | 17 +++++++++++++++++ 14 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 man/osem_endpoint.Rd create mode 100644 tests/testthat/test_counts.R 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/api.R b/R/api.R index 1726aea..6f112ff 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) + 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 } diff --git a/R/box.R b/R/box.R index dcc2b73..3854459 100644 --- a/R/box.R +++ b/R/box.R @@ -38,7 +38,7 @@ #' osem_boxes = function (exposure = NA, model = NA, grouptag = NA, date = NA, from = NA, to = NA, phenomenon = NA, - endpoint = 'https://api.opensensemap.org', + endpoint = osem_endpoint(), progress = T) { # error, if phenomenon, but no time given @@ -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..ab9b483 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) } 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..fd12197 100644 --- a/R/measurement.R +++ b/R/measurement.R @@ -59,7 +59,7 @@ osem_measurements.default = function (x, ...) { 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() %>% @@ -84,7 +84,7 @@ osem_measurements.bbox = function (x, phenomenon, exposure = NA, 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() %>% diff --git a/README.md b/README.md index 0cdd517..8a56770 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # opensensmapr + +[![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,6 +16,7 @@ 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`. @@ -39,17 +44,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. +[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/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..369bb66 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 = T) } \arguments{ \item{exposure}{Only return boxes with the given exposure ('indoor', 'outdoor', 'mobile')} 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..d30b809 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 diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R index c1d8892..79b8fd0 100644 --- a/tests/testthat/test_boxes.R +++ b/tests/testthat/test_boxes.R @@ -52,10 +52,10 @@ test_that("endpoint can be (mis)configured", { expect_error(osem_boxes(endpoint = "http://not.the.opensensemap.org"), "resolve host") }) -test_that("a response with no matches returns empty sensebox data.frame", { +test_that("a response with no matches returns empty sensebox data.frame and a warning", { check_api() - boxes <- osem_boxes(grouptag = "does_not_exist") + suppressWarnings(boxes <- osem_boxes(grouptag = "does_not_exist")) expect_true(is.data.frame(boxes)) expect_true(any("sensebox" %in% class(boxes))) }) diff --git a/tests/testthat/test_counts.R b/tests/testthat/test_counts.R new file mode 100644 index 0000000..90de81d --- /dev/null +++ b/tests/testthat/test_counts.R @@ -0,0 +1,17 @@ +context("counts") + +check_api <- function() { + 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) +}) From 171297677038fb8e3a295977919a9d52133871c2 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 21:49:24 +0100 Subject: [PATCH 03/19] fix travis tests --- .travis.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8d139ac..a9e1036 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,22 @@ 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()' From 542f2205eb44ba2d71aad471676d33db56073815 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 21:49:51 +0100 Subject: [PATCH 04/19] add tests for phenomena and box --- tests/testthat/test_box.R | 30 ++++++++++++++++++++++++++ tests/testthat/test_phenomena.R | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/testthat/test_box.R create mode 100644 tests/testthat/test_phenomena.R diff --git a/tests/testthat/test_box.R b/tests/testthat/test_box.R new file mode 100644 index 0000000..bf3ef2f --- /dev/null +++ b/tests/testthat/test_box.R @@ -0,0 +1,30 @@ +context("box") + +check_api <- function() { + 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 box can be converted to sf object", { + check_api() + + box <- osem_box(boxes$X_id[[1]]) + box_sf <- sf::st_as_sf(box) + + expect_true(sf::st_is_simple(box_sf)) + expect_true("sf" %in% class(box_sf)) +}) + +test_that("a box converted to sf object keeps all attributes", { + check_api() + + box <- osem_box(boxes$X_id[[1]]) + box_sf <- sf::st_as_sf(box) + + expect_true(all(names(box) %in% names(box_sf))) +}) diff --git a/tests/testthat/test_phenomena.R b/tests/testthat/test_phenomena.R new file mode 100644 index 0000000..5e1c5ac --- /dev/null +++ b/tests/testthat/test_phenomena.R @@ -0,0 +1,38 @@ +context("phenomena") + +check_api <- function() { + 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") +}) From c18d74a21036c7eca2bc80a83d875731f0fa3e1c Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 21:50:10 +0100 Subject: [PATCH 05/19] add codecov configuration --- codecov.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 codecov.yml 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% From 8e1fb7ad10cf9fa1341ad1857819868eb36c2ec1 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 22:13:45 +0100 Subject: [PATCH 06/19] add appveyor config --- appveyor.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 appveyor.yml 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 From 9a81816922d9e69b898de9635eeb7e316d471818 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 22:14:12 +0100 Subject: [PATCH 07/19] add first test for measurements --- tests/testthat/test_measurements.R | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/testthat/test_measurements.R diff --git a/tests/testthat/test_measurements.R b/tests/testthat/test_measurements.R new file mode 100644 index 0000000..14bcd54 --- /dev/null +++ b/tests/testthat/test_measurements.R @@ -0,0 +1,25 @@ +context("measurements") + +check_api <- function() { + 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 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,] + 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)) +}) From b852f6aac3918bd6904d07992c5c33b8decc7b87 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 22:14:32 +0100 Subject: [PATCH 08/19] add coc --- CONDUCT.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CONDUCT.md 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/ From 3f7ef00caae1300ccc2cf0e4d033b782e4b4f053 Mon Sep 17 00:00:00 2001 From: nuest Date: Sun, 14 Jan 2018 22:29:56 +0100 Subject: [PATCH 09/19] minor reformatting of README --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8a56770..4b76fca 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [![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. +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 @@ -18,8 +17,8 @@ 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 @@ -49,8 +48,8 @@ This project adheres to semantic versioning, for changes in recent versions plea ## 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 From 8fdec0c13fbb1f5cc30fbe159ec90b0499bd9324 Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 09:23:29 +0100 Subject: [PATCH 10/19] use more powerful Authors@R in DESCRIPTION --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7bb5143..06a1f2c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,8 +20,8 @@ Suggests: units, testthat, covr -Author: Norwin Roosen -Maintainer: Norwin Roosen +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 From 1f591b311eea8aa596f5ca554eb786818f993c77 Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 09:23:59 +0100 Subject: [PATCH 11/19] bump bugfix version (no user-side changes) --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 06a1f2c..d46a43f 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: From bc71b9c0dea187a3fda0d89f281d6cb7a9aa36be Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 12:08:43 +0100 Subject: [PATCH 12/19] minor improvements and re-generate docs --- R/api.R | 2 +- R/box.R | 4 ++-- R/measurement.R | 4 ++-- man/opensensmapr.Rd | 9 +++++++++ man/osem_measurements.Rd | 2 +- tests/testthat/test_boxes.R | 2 +- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/R/api.R b/R/api.R index 6f112ff..89c3bcb 100644 --- a/R/api.R +++ b/R/api.R @@ -15,7 +15,7 @@ get_boxes_ = function (..., endpoint) { response = osem_request_(endpoint, path = c('boxes'), ...) if (length(response) == 0) { - warning('no boxes found for this query') + warning('no senseBoxes found for this query') return(osem_as_sensebox(as.data.frame(response))) } diff --git a/R/box.R b/R/box.R index 3854459..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)} @@ -39,7 +39,7 @@ osem_boxes = function (exposure = NA, model = NA, grouptag = NA, date = NA, from = NA, to = NA, phenomenon = NA, endpoint = osem_endpoint(), - progress = T) { + progress = TRUE) { # error, if phenomenon, but no time given if (!is.na(phenomenon) && is.na(date) && is.na(to) && is.na(from)) diff --git a/R/measurement.R b/R/measurement.R index fd12197..666fea3 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 #' @@ -130,7 +130,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' 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_measurements.Rd b/man/osem_measurements.Rd index d30b809..8b1535d 100644 --- a/man/osem_measurements.Rd +++ b/man/osem_measurements.Rd @@ -37,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} diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R index 79b8fd0..ccc4915 100644 --- a/tests/testthat/test_boxes.R +++ b/tests/testthat/test_boxes.R @@ -63,7 +63,7 @@ test_that("a response with no matches returns empty sensebox data.frame and a wa test_that("a response with no matches gives a warning", { check_api() - expect_warning(osem_boxes(grouptag = "does_not_exist"), "no boxes found") + expect_warning(osem_boxes(grouptag = "does_not_exist"), "no senseBoxes found") }) test_that("data.frame can be converted to sensebox data.frame", { From 1d6db7ae2168602930f9602f5d2e18bb6e955f93 Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 12:08:54 +0100 Subject: [PATCH 13/19] add measurement tests and skip failing box test --- tests/testthat.R | 1 + tests/testthat/test_box.R | 2 + tests/testthat/test_measurements.R | 68 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/tests/testthat.R b/tests/testthat.R index f580690..e0e7eed 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,4 +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 index bf3ef2f..a213a9b 100644 --- a/tests/testthat/test_box.R +++ b/tests/testthat/test_box.R @@ -23,6 +23,8 @@ test_that("a box can be converted to sf object", { test_that("a box converted to sf object keeps all attributes", { check_api() + skip("FIXME") + box <- osem_box(boxes$X_id[[1]]) box_sf <- sf::st_as_sf(box) diff --git a/tests/testthat/test_measurements.R b/tests/testthat/test_measurements.R index 14bcd54..3cbe6a3 100644 --- a/tests/testthat/test_measurements.R +++ b/tests/testthat/test_measurements.R @@ -10,12 +10,56 @@ 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() + + 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)) @@ -23,3 +67,27 @@ test_that("measurements of specific boxes can be retrieved for one phenomenon an 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 = "Temperatur") + 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)) +}) + From 5f6dc7c0b5b4a03f2083bc978d9fad41f7d177c5 Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 12:18:19 +0100 Subject: [PATCH 14/19] fix docs --- man/osem_boxes.Rd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/osem_boxes.Rd b/man/osem_boxes.Rd index 369bb66..cb845fa 100644 --- a/man/osem_boxes.Rd +++ b/man/osem_boxes.Rd @@ -6,7 +6,7 @@ \usage{ osem_boxes(exposure = NA, model = NA, grouptag = NA, date = NA, from = NA, to = NA, phenomenon = NA, endpoint = osem_endpoint(), - progress = T) + 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 From d82b538b7a7a0a1b593657a073eaef8f888ca6b0 Mon Sep 17 00:00:00 2001 From: nuest Date: Mon, 15 Jan 2018 13:11:59 +0100 Subject: [PATCH 15/19] increase test coverage --- R/measurement.R | 5 +++-- tests/testthat/test_boxes.R | 5 +++++ tests/testthat/test_measurements.R | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/R/measurement.R b/R/measurement.R index 666fea3..f3aa657 100644 --- a/R/measurement.R +++ b/R/measurement.R @@ -106,8 +106,9 @@ 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)) || diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R index ccc4915..0638a39 100644 --- a/tests/testthat/test_boxes.R +++ b/tests/testthat/test_boxes.R @@ -17,6 +17,11 @@ test_that("a list of all boxes can be retrieved and returns a sensebox data.fram 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() diff --git a/tests/testthat/test_measurements.R b/tests/testthat/test_measurements.R index 3cbe6a3..ffbc701 100644 --- a/tests/testthat/test_measurements.R +++ b/tests/testthat/test_measurements.R @@ -91,3 +91,7 @@ test_that("measurements can be retrieved for a time period", { 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") +}) From e7203cea8115b625f469675477f8585414e1c9e2 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 15 Jan 2018 13:17:25 +0100 Subject: [PATCH 16/19] make examples not spam test logs --- R/measurement.R | 8 ++++---- man/osem_measurements.Rd | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/R/measurement.R b/R/measurement.R index f3aa657..97738bc 100644 --- a/R/measurement.R +++ b/R/measurement.R @@ -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,11 +50,11 @@ 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, @@ -79,7 +79,7 @@ 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, diff --git a/man/osem_measurements.Rd b/man/osem_measurements.Rd index 8b1535d..08bc4d3 100644 --- a/man/osem_measurements.Rd +++ b/man/osem_measurements.Rd @@ -63,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') @@ -79,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{ From 909e4de36d6376cf6f1c42c0da7fddfd8cdc716d Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 15 Jan 2018 14:51:26 +0100 Subject: [PATCH 17/19] lint --- DESCRIPTION | 2 +- R/00utils.R | 2 +- R/api.R | 2 +- R/measurement.R | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d46a43f..c169762 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Imports: dplyr, httr, magrittr -Suggests: +Suggests: maps, maptools, readr, 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 89c3bcb..ff7911e 100644 --- a/R/api.R +++ b/R/api.R @@ -60,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/measurement.R b/R/measurement.R index 97738bc..53937c9 100644 --- a/R/measurement.R +++ b/R/measurement.R @@ -106,9 +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)) || - (!is.na(params$to) && is.na(params$from)) - ) 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)) || @@ -161,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 From 6d2f6f67e1eb77d93c6c950685ccea6451a73ef7 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 15 Jan 2018 15:23:26 +0100 Subject: [PATCH 18/19] add some tests, fix failing tests --- R/box_utils.R | 2 +- R/measurement_utils.R | 2 +- tests/testthat/test_box.R | 25 ++++++++++----------- tests/testthat/test_boxes.R | 33 ++++++++++++++++++++++++++++ tests/testthat/test_measurements.R | 35 +++++++++++++++++++++++++----- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/R/box_utils.R b/R/box_utils.R index ab9b483..92460d1 100644 --- a/R/box_utils.R +++ b/R/box_utils.R @@ -107,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/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/tests/testthat/test_box.R b/tests/testthat/test_box.R index a213a9b..b6ca332 100644 --- a/tests/testthat/test_box.R +++ b/tests/testthat/test_box.R @@ -10,23 +10,20 @@ try({ boxes <- osem_boxes() }) -test_that("a box can be converted to sf object", { +test_that("a single box can be retrieved by ID", { check_api() - + box <- osem_box(boxes$X_id[[1]]) - box_sf <- sf::st_as_sf(box) - - expect_true(sf::st_is_simple(box_sf)) - expect_true("sf" %in% class(box_sf)) + + 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("a box converted to sf object keeps all attributes", { + +test_that("[.sensebox maintains attributes", { check_api() - - skip("FIXME") - - box <- osem_box(boxes$X_id[[1]]) - box_sf <- sf::st_as_sf(box) - - expect_true(all(names(box) %in% names(box_sf))) + + 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 index 0638a39..d7021ab 100644 --- a/tests/testthat/test_boxes.R +++ b/tests/testthat/test_boxes.R @@ -75,3 +75,36 @@ 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_measurements.R b/tests/testthat/test_measurements.R index ffbc701..e4600fc 100644 --- a/tests/testthat/test_measurements.R +++ b/tests/testthat/test_measurements.R @@ -21,14 +21,16 @@ test_that("measurements can be retrieved for a phenomenon", { 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") }) @@ -70,10 +72,10 @@ test_that("measurements of specific boxes can be retrieved for one phenomenon an 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 = "Temperatur") + 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)) @@ -82,11 +84,22 @@ test_that("measurements can be retrieved for a bounding box", { 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)) }) @@ -95,3 +108,13 @@ test_that("both from and to are required when requesting measurements, error oth 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))) +}) From 7e7b9bcb12af2ff58d69b083f96786c7f8cf3049 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 15 Jan 2018 15:25:18 +0100 Subject: [PATCH 19/19] skip API dependant tests on CRAN --- tests/testthat/test_box.R | 2 ++ tests/testthat/test_boxes.R | 18 ++++++++++-------- tests/testthat/test_counts.R | 6 ++++-- tests/testthat/test_measurements.R | 13 +++++++------ tests/testthat/test_phenomena.R | 10 ++++++---- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tests/testthat/test_box.R b/tests/testthat/test_box.R index b6ca332..4d92141 100644 --- a/tests/testthat/test_box.R +++ b/tests/testthat/test_box.R @@ -1,6 +1,8 @@ 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") diff --git a/tests/testthat/test_boxes.R b/tests/testthat/test_boxes.R index d7021ab..c0d925e 100644 --- a/tests/testthat/test_boxes.R +++ b/tests/testthat/test_boxes.R @@ -1,6 +1,8 @@ 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") @@ -8,7 +10,7 @@ check_api <- function() { 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)) @@ -24,21 +26,21 @@ test_that("both from and to are required when requesting boxes, error otherwise" 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")) @@ -46,20 +48,20 @@ test_that("box query can combine exposure and model filter", { 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))) @@ -67,7 +69,7 @@ test_that("a response with no matches returns empty sensebox data.frame and a wa test_that("a response with no matches gives a warning", { check_api() - + expect_warning(osem_boxes(grouptag = "does_not_exist"), "no senseBoxes found") }) diff --git a/tests/testthat/test_counts.R b/tests/testthat/test_counts.R index 90de81d..f4c7d6a 100644 --- a/tests/testthat/test_counts.R +++ b/tests/testthat/test_counts.R @@ -1,6 +1,8 @@ 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") @@ -8,9 +10,9 @@ check_api <- function() { 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 index e4600fc..fb85bd7 100644 --- a/tests/testthat/test_measurements.R +++ b/tests/testthat/test_measurements.R @@ -1,6 +1,8 @@ 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") @@ -10,10 +12,9 @@ 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)) @@ -41,7 +42,7 @@ test_that("data.frame can be converted to 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) @@ -49,7 +50,7 @@ test_that("columns can be specified for phenomena", { 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) @@ -57,14 +58,14 @@ test_that("measurements can be retrieved for a phenomenon and exposure", { 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)) diff --git a/tests/testthat/test_phenomena.R b/tests/testthat/test_phenomena.R index 5e1c5ac..5cf7682 100644 --- a/tests/testthat/test_phenomena.R +++ b/tests/testthat/test_phenomena.R @@ -1,6 +1,8 @@ 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") @@ -13,18 +15,18 @@ try({ 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)) })