From f43cbed0d5a6feac960ce877626afcd933f7fb74 Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 10:21:10 +0100 Subject: [PATCH 1/8] Bump version + mention changes in NEWS.md --- DESCRIPTION | 2 +- NEWS.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index bd58a9ed..c869f971 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: camtraptor Title: Read, Explore and Visualize Camera Trap Data Packages -Version: 0.26.0 +Version: 0.27.0 Authors@R: c( person("Damiano", "Oldoni", , "damiano.oldoni@inbo.be", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3445-7562")), diff --git a/NEWS.md b/NEWS.md index 882d8aa9..31ffb22a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# camtraptor 0.27.0 + +- `get_record_table()` returns now 4 new columns: `longitude`, `latitude` (deployment coordinates), `clock` (clock time of the observation in radians) and `solar` (sun time of the observation in radians) (#341). + # camtraptor 0.26.0 - `get_custom_effort()` returns now the effort for each deployment separately (#333). The returned data frame has two new columns: `deploymentID` and `locationName`. From 1bd7a85ea95054fde42bd66b9e35a46583895a3e Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 11:12:37 +0100 Subject: [PATCH 2/8] Add package dependencies --- DESCRIPTION | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index c869f971..2115be5c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,6 +33,7 @@ BugReports: https://github.com/inbo/camtraptor/issues Depends: R (>= 3.5.0) Imports: + activity, assertthat, dplyr (>= 1.1.0), EML, @@ -42,6 +43,7 @@ Imports: leaflet, lifecycle, lubridate, + overlap, purrr, RColorBrewer, readr, From 9ab7851c2a194ca295deac9ac941ff2badf95c06 Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 11:13:11 +0100 Subject: [PATCH 3/8] Add help function to add coords to observations --- R/zzz.R | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/R/zzz.R b/R/zzz.R index 6d07e078..08691ce9 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1009,3 +1009,26 @@ order_cols_observations <- function(df) { ) ) } + +#' Add deployment coordinates to observations +#' +#' This function adds deployment coordinates to observations based on +#' `deploymentID`. +#' +#' @param package Camera trap data package object. +#' @return Camera trap data package object with `observations` updated. +#' @noRd +add_coordinates <- function(package) { + + deployments <- package$data$deployments + observations <- package$data$observations + + # add coordinates to observations + observations <- observations %>% + dplyr::left_join(deployments %>% + dplyr::select("deploymentID", "longitude", "latitude"), + by = "deploymentID") + + package$data$observations <- observations + return(package) +} From 8f7b44c617ca9dc4cd3889dd06bbe40546f67e80 Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 11:13:32 +0100 Subject: [PATCH 4/8] Add basic logic to fix #341 --- R/get_record_table.R | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/R/get_record_table.R b/R/get_record_table.R index 0f3f6235..8c60fdb1 100644 --- a/R/get_record_table.R +++ b/R/get_record_table.R @@ -57,6 +57,10 @@ #' as defined in column `filePath` of `media`. #' - `Filename`: List, file names of the images linked to the given record, #' as defined in column `fileName` of `media`. +#' - `Latitude`: Numeric, latitude of the station, based on `deploymentID` of the observations. +#' - `Longitude`: Numeric, longitude of the station, based on `deploymentID` of the observations. +#' - `clock`: Numeric, clock time in radians. +#' - `solar`: Numeric, solar time in radians. #' @family exploration functions #' @importFrom dplyr .data %>% #' @importFrom rlang !! := @@ -172,6 +176,9 @@ get_record_table <- function(package = NULL, msg = "removeDuplicateRecords must be a logical: TRUE or FALSE." ) + # Add coordinates to observations + package <- add_coordinates(package) + # remove observations of unidentified individuals obs <- package$data$observations %>% dplyr::filter(!is.na(.data$scientificName)) @@ -276,6 +283,17 @@ get_record_table <- function(package = NULL, )) %>% dplyr::ungroup() + # Add clock time in radians + record_table <- record_table %>% + dplyr::mutate(clock = activity::gettime(.data$timestamp)) + # Add solar time in radians + matrix_coords <- matrix(c(record_table$longitude, record_table$latitude), + ncol = 2) + record_table <- record_table %>% + dplyr::mutate(solar = overlap::sunTime(.data$clock, + .data$timestamp, + matrix_coords)) + record_table <- record_table %>% dplyr::rename(Station := !!stationCol, Species = "scientificName", @@ -296,7 +314,11 @@ get_record_table <- function(package = NULL, "delta.time.hours", "delta.time.days", "Directory", - "FileName" + "FileName", + "latitude", + "longitude", + "clock", + "solar" ) # remove duplicates if needed if (isTRUE(removeDuplicateRecords)) { From a5b95aec664aa9e1f7c60005b8ed07a5978f792c Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 11:18:08 +0100 Subject: [PATCH 5/8] Update tests --- tests/testthat/test-get_record_table.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-get_record_table.R b/tests/testthat/test-get_record_table.R index 75a12015..b4d8b9ae 100644 --- a/tests/testthat/test-get_record_table.R +++ b/tests/testthat/test-get_record_table.R @@ -72,7 +72,11 @@ test_that("right columns are returned", { "delta.time.hours", "delta.time.days", "Directory", - "FileName" + "FileName", + "latitude", + "longitude", + "clock", + "solar" ) ) }) From 66172aec52b357233c1314c25bda84aab98e5aad Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 12:01:44 +0100 Subject: [PATCH 6/8] Mention function and article for `solar` column --- R/get_record_table.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/get_record_table.R b/R/get_record_table.R index 8c60fdb1..dd8500a6 100644 --- a/R/get_record_table.R +++ b/R/get_record_table.R @@ -60,7 +60,7 @@ #' - `Latitude`: Numeric, latitude of the station, based on `deploymentID` of the observations. #' - `Longitude`: Numeric, longitude of the station, based on `deploymentID` of the observations. #' - `clock`: Numeric, clock time in radians. -#' - `solar`: Numeric, solar time in radians. +#' - `solar`: Numeric, solar time in radians. Calculated using `overlap::sunTime`, which essentially uses the approach described in [Nouvellet et al. (2012)](https://doi.org/10.1111/j.1469-7998.2011.00864.x). #' @family exploration functions #' @importFrom dplyr .data %>% #' @importFrom rlang !! := @@ -330,7 +330,9 @@ get_record_table <- function(package = NULL, .data$Date, .data$Time, .data$Directory, - .data$FileName + .data$FileName, + .data$latitude, + .data$longitude ) %>% dplyr::mutate(row_number = dplyr::row_number()) %>% dplyr::filter(.data$delta.time.secs == max(.data$delta.time.secs) & From 12173fbc4f2334c410cf8759aec1637b346fdaef Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 12:03:07 +0100 Subject: [PATCH 7/8] Add warning about removing obs without timestamp A valid camtrapDP object should in theory not include such observations. But who knows how data were manipulated after initial reading. --- R/get_record_table.R | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/R/get_record_table.R b/R/get_record_table.R index dd8500a6..6d263ca5 100644 --- a/R/get_record_table.R +++ b/R/get_record_table.R @@ -187,6 +187,15 @@ get_record_table <- function(package = NULL, obs <- obs %>% dplyr::filter(!.data$scientificName %in% exclude) + + # Remove observations without timestamp and returns a warning message + # if there are any + if (any(is.na(obs$timestamp))) { + warning("Some observations have no timestamp and will be removed.") + obs <- obs %>% + dplyr::filter(!is.na(.data$timestamp)) + } + # apply filtering on deployments deployments <- apply_filter_predicate( df = package$data$deployments, From 67d24febbe60f2e9a548a1fad91e4c0b60a0e534 Mon Sep 17 00:00:00 2001 From: Damiano Oldoni Date: Wed, 20 Nov 2024 12:03:23 +0100 Subject: [PATCH 8/8] Add tests for values in clock and solar --- tests/testthat/test-get_record_table.R | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/testthat/test-get_record_table.R b/tests/testthat/test-get_record_table.R index b4d8b9ae..abdf68bb 100644 --- a/tests/testthat/test-get_record_table.R +++ b/tests/testthat/test-get_record_table.R @@ -56,6 +56,14 @@ test_that("input of get_record_table, removeDuplicateRecords, is checked properl removeDuplicateRecords = NA )) }) +test_that("warning is returned if some observations have no timestamp", { + mica_no_timestamp <- mica + mica_no_timestamp$data$observations$timestamp[3:5] <- NA + expect_warning( + get_record_table(mica_no_timestamp), + "Some observations have no timestamp and will be removed." + ) +}) test_that("right columns are returned", { expect_named( @@ -213,6 +221,20 @@ test_that(paste( ) }) +test_that("clock is always in the range [0, 2*pi]", { + clock_values <- get_record_table(mica) %>% + dplyr::pull(clock) + expect_true(all(clock_values >= 0)) + expect_true(all(clock_values <= 2 * pi)) +}) + +test_that("solar is always in the range [0, 2*pi]", { + solar_values <- get_record_table(mica) %>% + dplyr::pull(solar) + expect_true(all(solar_values >= 0)) + expect_true(all(solar_values <= 2 * pi)) +}) + test_that("filtering predicates are allowed and work well", { stations <- unique( suppressMessages(get_record_table(mica, pred_lt("longitude", 4.0)))$Station