From 315d186b484cf9cb30802a6789430fe9cdc3df5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 18:24:23 +0200 Subject: [PATCH 1/6] Keep retrieval latitude and fix column selection in read_soi_gld() - The default column selection listed LongitudeRemoved and LongitudeAttached twice and omitted LatitudeRemoved, so read_soi() wrote retrieval observations without latitude. - A character filter_col selected a column literally named "filter_col" and always failed; it now selects the given columns. - Data frames passed as data_file or order_file no longer fail the input check. - Read 99999 coordinates (unknown in the SOI database) as NA. - Read AccSamplingRate and PriorityMemory as numbers and PrioritySensor as text instead of logical. - Warn with the tag and error when read_soi() cannot read a tag instead of dropping its measurements silently. Co-Authored-By: Claude Opus 5.5 --- R/read_soi.R | 4 +++ R/read_soi_gld.R | 42 +++++++++++++++++++--------- man/read_soi_gld.Rd | 6 ++-- tests/testthat/test-read_soi_gld.R | 44 ++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 tests/testthat/test-read_soi_gld.R diff --git a/R/read_soi.R b/R/read_soi.R index fb89cdc..cc34f32 100644 --- a/R/read_soi.R +++ b/R/read_soi.R @@ -100,6 +100,10 @@ read_soi <- function( } }, error = function(e) { + cli_warn(c( + "!" = "Could not read the data of tag {.val {GDL_ID}} in {.file {directory}}. Its measurements are skipped.", + "x" = conditionMessage(e) + )) list() # Return empty list on error } ) diff --git a/R/read_soi_gld.R b/R/read_soi_gld.R index f4988b3..9a7fdd9 100644 --- a/R/read_soi_gld.R +++ b/R/read_soi_gld.R @@ -16,9 +16,9 @@ #' memory. Required when `access_file` is not provided. #' @param order_file Path to the GDL order file, or a data frame already loaded #' in memory. Required when `access_file` is not provided. -#' @param filter_col Logical. If `TRUE` (default), returns a predefined subset -#' of columns useful for package creation; if `FALSE`, returns all joined -#' columns. +#' @param filter_col Logical or character vector. If `TRUE` (default), returns +#' a predefined subset of columns useful for package creation; if `FALSE`, +#' returns all joined columns; if a character vector, returns these columns. #' #' @return A data frame combining GDL tag rows and order-level information. #' @@ -36,8 +36,11 @@ read_soi_gld <- function( order_file = NA, filter_col = TRUE ) { - if (!is.na(access_file)) { - if (!is.na(data_file) || !is.na(order_file)) { + # A file path or a data frame counts as provided + provided <- function(x) is.data.frame(x) || !(length(x) == 1 && is.na(x)) + + if (provided(access_file)) { + if (provided(data_file) || provided(order_file)) { cli_warn(c( "!" = "{.arg access_file} was provided alongside {.arg data_file} or {.arg order_file}.", ">" = "We'll use {.arg access_file}." @@ -47,7 +50,7 @@ read_soi_gld <- function( data_file <- data_order_file[[1]] order_file <- data_order_file[[2]] } else { - if (is.na(data_file) || is.na(order_file)) { + if (!provided(data_file) || !provided(order_file)) { cli_abort(c( "x" = "Need {.arg access_file} or both {.arg data_file} and {.arg order_file}." )) @@ -60,6 +63,20 @@ read_soi_gld <- function( d <- read_soi_gld_data(data_file) } + # 99999 encodes an unknown coordinate in the SOI database + d <- mutate( + d, + across( + any_of(c( + "LongitudeAttached", + "LatitudeAttached", + "LongitudeRemoved", + "LatitudeRemoved" + )), + ~ dplyr::if_else(.x == 99999, NA_real_, as.numeric(.x)) + ) + ) + if (is.data.frame(order_file)) { o <- order_file } else { @@ -105,8 +122,7 @@ read_soi_gld <- function( "LatitudeAttached", "UTC_Removed", "LongitudeRemoved", - "LongitudeRemoved", - "LongitudeAttached", + "LatitudeRemoved", # Information needed for package "RingNumber", @@ -118,7 +134,7 @@ read_soi_gld <- function( ) } } else { - gdl <- select(gdl, "filter_col") + gdl <- select(gdl, all_of(filter_col)) } return(gdl) @@ -178,7 +194,7 @@ read_soi_gld_orders <- function(order_file) { BreastHarnessDiameterHead = readr::col_double(), BreastHarnessDiameterTail = readr::col_double(), HarnessDescription = readr::col_character(), - AccSamplingRate = readr::col_logical(), + AccSamplingRate = readr::col_double(), AccXaxisEnabled = readr::col_logical(), AccYaxisEnabled = readr::col_logical(), AccZaxisEnabled = readr::col_logical(), @@ -195,7 +211,7 @@ read_soi_gld_orders <- function(order_file) { TRSchedule = readr::col_character(), LoggingSchedule = readr::col_character(), ActiveAfterWakeUp = readr::col_double(), - PrioritySensor = readr::col_logical(), + PrioritySensor = readr::col_character(), PriorityMemory = readr::col_double() ) ) |> @@ -283,8 +299,8 @@ read_soi_gld_data <- function(data_file) { Frequency = readr::col_double(), MagCalib = readr::col_character(), SleepCurrent = readr::col_double(), - PrioritySensor = readr::col_logical(), - PriorityMemory = readr::col_logical(), + PrioritySensor = readr::col_character(), + PriorityMemory = readr::col_double(), LongitudeRemoved = readr::col_double() ) ) diff --git a/man/read_soi_gld.Rd b/man/read_soi_gld.Rd index eb9c2a6..c7ebe48 100644 --- a/man/read_soi_gld.Rd +++ b/man/read_soi_gld.Rd @@ -22,9 +22,9 @@ memory. Required when \code{access_file} is not provided.} \item{order_file}{Path to the GDL order file, or a data frame already loaded in memory. Required when \code{access_file} is not provided.} -\item{filter_col}{Logical. If \code{TRUE} (default), returns a predefined subset -of columns useful for package creation; if \code{FALSE}, returns all joined -columns.} +\item{filter_col}{Logical or character vector. If \code{TRUE} (default), returns +a predefined subset of columns useful for package creation; if \code{FALSE}, +returns all joined columns; if a character vector, returns these columns.} } \value{ A data frame combining GDL tag rows and order-level information. diff --git a/tests/testthat/test-read_soi_gld.R b/tests/testthat/test-read_soi_gld.R new file mode 100644 index 0000000..f783e77 --- /dev/null +++ b/tests/testthat/test-read_soi_gld.R @@ -0,0 +1,44 @@ +test_that("read_soi_gld() keeps retrieval coordinates and handles column selection", { + d <- tibble( + OrderName = c("O1", "O1"), + GDL_ID = c("A1", "A2"), + GDL_Type = "GDL3pam", + Species = "Upupa epops", + UTC_Attached = as.POSIXct("2020-05-01", tz = "UTC"), + SiteAttached = "Site", + LongitudeAttached = 7.5, + LatitudeAttached = 46.2, + UTC_Removed = as.POSIXct("2021-05-01", tz = "UTC"), + LongitudeRemoved = c(8.31, 99999), + LatitudeRemoved = c(47.47, 99999), + RingNumber = c("R1", "R2"), + FirmwareVersion = "2.4", + HardwareVersion = "2.4", + TotalWeight = 1.5, + Remarks = NA_character_ + ) + o <- tibble( + OrderName = "O1", + NumberOrdered = 2L, + NumberDelivered = 2L, + GDL_Type = "GDL3pam", + Species = "Upupa epops", + Client = "SOI", + ResponsibleP3 = "P3", + ClientCategory = "own", + RemarksProduction = NA_character_, + RemarksOrder = NA_character_ + ) + + # Default selection keeps the retrieval latitude, and 99999 is read as unknown + gdl <- read_soi_gld(data_file = d, order_file = o) + expect_true(all(c("LongitudeRemoved", "LatitudeRemoved") %in% names(gdl))) + expect_equal(gdl$LatitudeRemoved, c(47.47, NA)) + expect_equal(gdl$LongitudeRemoved, c(8.31, NA)) + + # A character vector selects these columns + gdl <- read_soi_gld(data_file = d, order_file = o, filter_col = c("GDL_ID", "Client")) + expect_named(gdl, c("GDL_ID", "Client")) + + expect_error(read_soi_gld(data_file = d), "Need") +}) From c3987f99400acc24ac20e31bdab286abe23eb9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 18:24:35 +0200 Subject: [PATCH 2/6] Prepare v1.3.1 release metadata Co-Authored-By: Claude Opus 5.5 --- CITATION.cff | 2 +- DESCRIPTION | 2 +- NEWS.md | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 10d771d..fcc4aba 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,7 +8,7 @@ message: 'To cite package "GeoLocatoR" in publications use:' type: software license: GPL-3.0-or-later title: 'GeoLocatoR: R package for GeoLocator Data Package' -version: 1.3.0 +version: 1.3.1 identifiers: - type: url value: https://geopressure.org/GeoLocatoR/ diff --git a/DESCRIPTION b/DESCRIPTION index f1e44ff..a6d0c88 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: GeoLocatoR Title: R package for GeoLocator Data Package -Version: 1.3.0 +Version: 1.3.1 Authors@R: c( person("Raphaƫl", "Nussbaumer", , "rafnuss@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-8185-1020")), diff --git a/NEWS.md b/NEWS.md index f08207f..5310387 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,17 @@ +# GeoLocatoR v1.3.1 + +## Main + +- [Keep the retrieval latitude in `read_soi_gld()`](https://github.com/GeoPressure/GeoLocatoR/commit/315d186). The default column selection (`filter_col = TRUE`) listed `LongitudeRemoved` and `LongitudeAttached` twice and omitted `LatitudeRemoved`, so every retrieval observation created by `read_soi()` had no latitude. Packages created from SOI data with earlier versions should be recreated, or their retrieval latitudes filled. Coordinates recorded as `99999` (unknown in the SOI database) are now read as missing instead of being written as observations. + +## Minor + +- [Select the columns given in a character `filter_col`](https://github.com/GeoPressure/GeoLocatoR/commit/315d186) in `read_soi_gld()`, which previously always failed, and accept data frames for `data_file` and `order_file` as documented. +- [Read `AccSamplingRate`, `PrioritySensor` and `PriorityMemory` with their actual types](https://github.com/GeoPressure/GeoLocatoR/commit/315d186) (number, text and percentage) instead of logical, which turned their values into `NA`. +- [Warn when `read_soi()` cannot read the data of a tag](https://github.com/GeoPressure/GeoLocatoR/commit/315d186), naming the tag and the error, instead of silently leaving its measurements out. + +**Full Changelog**: + # GeoLocatoR v1.3.0 ## Main From d442338aeda3a3d4303f1a9fb7faee72c077ae20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 18:44:40 +0200 Subject: [PATCH 3/6] Export Access dates in ISO format in read_soi_gld() mdb-export wrote dates as MM/DD/YY HH:MM:SS, which readr parsed as NA, so every date read from the Access file on macOS/Linux was missing. Co-Authored-By: Claude Opus 5.5 --- NEWS.md | 1 + R/read_soi_gld.R | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5310387..8515874 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ ## Main - [Keep the retrieval latitude in `read_soi_gld()`](https://github.com/GeoPressure/GeoLocatoR/commit/315d186). The default column selection (`filter_col = TRUE`) listed `LongitudeRemoved` and `LongitudeAttached` twice and omitted `LatitudeRemoved`, so every retrieval observation created by `read_soi()` had no latitude. Packages created from SOI data with earlier versions should be recreated, or their retrieval latitudes filled. Coordinates recorded as `99999` (unknown in the SOI database) are now read as missing instead of being written as observations. +- Read dates from the Access file in `read_soi_gld()` on macOS and Linux. `mdb-export` wrote them as `MM/DD/YY HH:MM:SS`, which was parsed as `NA`, so every date (attachment, retrieval, RTC, first and last data, delivery) was missing and `read_soi()` created equipment and retrieval observations without a datetime. They are now exported in ISO format. Packages created from an Access file on these systems with earlier versions should be recreated. ## Minor diff --git a/R/read_soi_gld.R b/R/read_soi_gld.R index 9a7fdd9..87ecfdc 100644 --- a/R/read_soi_gld.R +++ b/R/read_soi_gld.R @@ -327,8 +327,14 @@ read_soi_gld_access <- function( } if (.Platform$OS.type == "unix") { + # mdb-export writes dates as MM/DD/YY by default, which readr cannot parse + # and turns into NA, so request ISO dates explicitly + date_args <- "-D '%Y-%m-%d' -T '%Y-%m-%d %H:%M:%S'" + # Export data - system(glue::glue("mdb-export '{access_file}' GDL_Data > {data_file}")) + system(glue::glue( + "mdb-export {date_args} '{access_file}' GDL_Data > {data_file}" + )) if (!file.exists(data_file)) { cli_abort(c( @@ -338,7 +344,9 @@ read_soi_gld_access <- function( } # Export orders - system(glue::glue("mdb-export '{access_file}' GDL_Orders > {order_file}")) + system(glue::glue( + "mdb-export {date_args} '{access_file}' GDL_Orders > {order_file}" + )) if (!file.exists(order_file)) { cli_abort(c( "x" = "The file {.file {order_file}} does not exist.", From a56c149f85e8452d91160d122b8c2eb8ebc4971d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 18:45:01 +0200 Subject: [PATCH 4/6] Link the Access date fix in the v1.3.1 release notes Co-Authored-By: Claude Opus 5.5 --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 8515874..1a41d8b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,7 +3,7 @@ ## Main - [Keep the retrieval latitude in `read_soi_gld()`](https://github.com/GeoPressure/GeoLocatoR/commit/315d186). The default column selection (`filter_col = TRUE`) listed `LongitudeRemoved` and `LongitudeAttached` twice and omitted `LatitudeRemoved`, so every retrieval observation created by `read_soi()` had no latitude. Packages created from SOI data with earlier versions should be recreated, or their retrieval latitudes filled. Coordinates recorded as `99999` (unknown in the SOI database) are now read as missing instead of being written as observations. -- Read dates from the Access file in `read_soi_gld()` on macOS and Linux. `mdb-export` wrote them as `MM/DD/YY HH:MM:SS`, which was parsed as `NA`, so every date (attachment, retrieval, RTC, first and last data, delivery) was missing and `read_soi()` created equipment and retrieval observations without a datetime. They are now exported in ISO format. Packages created from an Access file on these systems with earlier versions should be recreated. +- [Read dates from the Access file in `read_soi_gld()` on macOS and Linux](https://github.com/GeoPressure/GeoLocatoR/commit/d442338). `mdb-export` wrote them as `MM/DD/YY HH:MM:SS`, which was parsed as `NA`, so every date (attachment, retrieval, RTC, first and last data, delivery) was missing and `read_soi()` created equipment and retrieval observations without a datetime. They are now exported in ISO format. Packages created from an Access file on these systems with earlier versions should be recreated. ## Minor From 7fb59ecdaf49e813e465272cf5cbcafb80345cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 19:05:17 +0200 Subject: [PATCH 5/6] Allow <<- in jarl 0.7 and test read_soi_gld() CSV column types jarl 0.7.0 adds the undesirable_operator rule, which bans <<- by default and fails CI on existing code. Keep ::: and ->> banned. Co-Authored-By: Claude Opus 5.5 --- .Rbuildignore | 1 + jarl.toml | 3 +++ tests/testthat/test-read_soi_gld.R | 15 +++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 jarl.toml diff --git a/.Rbuildignore b/.Rbuildignore index 9a4dcb7..ec2ed9c 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -16,6 +16,7 @@ ^[.]?air[.]toml$ ^\.vscode$ ^jarl\.yml$ +^jarl\.toml$ ^data-raw$ ^AGENTS.md$ ^assets$ diff --git a/jarl.toml b/jarl.toml new file mode 100644 index 0000000..e5b38f3 --- /dev/null +++ b/jarl.toml @@ -0,0 +1,3 @@ +[lint.undesirable_operator] +# Allow `<<-`, used deliberately to update state from closures; keep `:::` and `->>` banned +operators = ["->>", ":::"] diff --git a/tests/testthat/test-read_soi_gld.R b/tests/testthat/test-read_soi_gld.R index f783e77..622769a 100644 --- a/tests/testthat/test-read_soi_gld.R +++ b/tests/testthat/test-read_soi_gld.R @@ -41,4 +41,19 @@ test_that("read_soi_gld() keeps retrieval coordinates and handles column selecti expect_named(gdl, c("GDL_ID", "Client")) expect_error(read_soi_gld(data_file = d), "Need") + + # CSV exports are read with their declared column types; partial exports warn about the + # columns they lack + data_file <- tempfile(fileext = ".csv") + order_file <- tempfile(fileext = ".csv") + on.exit(unlink(c(data_file, order_file)), add = TRUE) + write.csv(mutate(d, PrioritySensor = "LIGHT", PriorityMemory = 20), data_file, row.names = FALSE) + write.csv(mutate(o, AccSamplingRate = 25), order_file, row.names = FALSE) + gdl <- suppressWarnings( + read_soi_gld(data_file = data_file, order_file = order_file, filter_col = FALSE) + ) + expect_equal(gdl$LatitudeRemoved, c(47.47, NA)) + expect_identical(gdl$PrioritySensor, c("LIGHT", "LIGHT")) + expect_identical(gdl$PriorityMemory, c(20, 20)) + expect_identical(suppressWarnings(read_soi_gld_orders(order_file))$AccSamplingRate, 25) }) From 1fa4d32e04bcfa92a6f8e147fd0e47d256785fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Nussbaumer?= Date: Wed, 21 Oct 2026 19:09:03 +0200 Subject: [PATCH 6/6] Exclude the Access export from coverage and test the access_file branch The export needs an Access file and mdb-export or the Access ODBC driver, which CI does not have. Co-Authored-By: Claude Opus 5.5 --- R/read_soi_gld.R | 4 ++++ tests/testthat/test-read_soi_gld.R | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/R/read_soi_gld.R b/R/read_soi_gld.R index 87ecfdc..e3878ad 100644 --- a/R/read_soi_gld.R +++ b/R/read_soi_gld.R @@ -326,6 +326,9 @@ read_soi_gld_access <- function( )) } + # Exporting needs an Access file and mdb-export or the Access ODBC driver, + # which are not available on CI + # nocov start if (.Platform$OS.type == "unix") { # mdb-export writes dates as MM/DD/YY by default, which readr cannot parse # and turns into NA, so request ISO dates explicitly @@ -371,6 +374,7 @@ read_soi_gld_access <- function( readr::write_csv(DBI::dbReadTable(con, "GDL_Orders"), order_file) readr::write_csv(DBI::dbReadTable(con, "GDL_Data"), data_file) } + # nocov end invisible(c(data_file, order_file)) } diff --git a/tests/testthat/test-read_soi_gld.R b/tests/testthat/test-read_soi_gld.R index 622769a..0b19896 100644 --- a/tests/testthat/test-read_soi_gld.R +++ b/tests/testthat/test-read_soi_gld.R @@ -41,6 +41,13 @@ test_that("read_soi_gld() keeps retrieval coordinates and handles column selecti expect_named(gdl, c("GDL_ID", "Client")) expect_error(read_soi_gld(data_file = d), "Need") + expect_warning( + expect_error( + read_soi_gld(access_file = "missing.accdb", data_file = d), + "does not exist" + ), + "access_file" + ) # CSV exports are read with their declared column types; partial exports warn about the # columns they lack