diff --git a/DESCRIPTION b/DESCRIPTION index 79b1170..36426c1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: collar Title: Utilities for Accessing and Manipulating GPS Data -Version: 0.0.4006 +Version: 0.0.4007 Authors@R: c( person("Josh", "Nowak", email = "josh.nowak@speedgoat.io", role = c("aut", "cre")), person("Forest", "Hayes", email = "forest.hayes@umontana.edu", role = "aut"), @@ -39,7 +39,6 @@ Imports: rvest, sf, stats, - stringr, tibble, tidyr, xml2, diff --git a/NAMESPACE b/NAMESPACE index 9b89861..ee330b7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(add_lotek_status) export(ats_get) export(ats_join_trans) export(ats_login) @@ -35,7 +36,9 @@ export(get_keys) export(get_paths) export(lotek_login) export(lotek_logout) +export(lotek_num_sats) export(lotek_refresh_token) +export(lotek_status_code) export(lotek_token) export(make_gpx) export(make_map) diff --git a/R/fetch_ats.R b/R/fetch_ats.R index ac9b7ad..a9263bd 100644 --- a/R/fetch_ats.R +++ b/R/fetch_ats.R @@ -992,7 +992,7 @@ fetch_ats_config <- function() { ), dplyr::across( c(.data$Active, .data$RestEndPoint:.data$`SMS LowBatt`), - ~ dplyr::if_else(.x == "yes", TRUE, FALSE) + function(x) dplyr::if_else(x == "yes", TRUE, FALSE) ) ) diff --git a/R/fetch_lotek.R b/R/fetch_lotek.R index e4d9e4e..15a6bc6 100644 --- a/R/fetch_lotek.R +++ b/R/fetch_lotek.R @@ -165,7 +165,7 @@ fetch_lotek_devices <- function() { #' in the output. Must be in the format YYYY-MM-DD HH:MM:SS. If NULL #' Jan 1 1970 is used. #' @param end_date Only fixes at or before end_date are included, -#' analagous to start_date above. If NULL current date/time is used. +#' analogous to start_date above. If NULL current date/time is used. #' #' @return A tibble containing position data, or an empty tibble in the #' same format is no rows are returned. @@ -203,21 +203,22 @@ fetch_lotek_positions <- function(device_id = NULL, # function will exit here if login info is invalid tkn <- lotek_token() - st <- dplyr::if_else( - is.null(start_date), - "1970-01-01T00:00:00Z", - start_date) + if (is.null(start_date)) { + st <- "1970-01-01T00:00:00Z" + } else { + st <- start_date + } - end <- dplyr::if_else( - is.null(end_date), - paste0( - gsub(" ", "T", lubridate::now(tzone = "GMT")), - "Z"), - end_date) + if (is.null(end_date)) { + end <- paste0(lubridate::today(tzone = "GMT"), "T23:59:59Z") + } else { + end <- end_date + } dev <- paste0( device_id, - collapse = ",") + collapse = "," + ) # send request resp <- httr::RETRY( @@ -281,3 +282,199 @@ fetch_lotek_positions <- function(device_id = NULL, } } + +#' @title Add Lotek Status +#' +#' @description Parses coded values in RxStatus column of API response. +#' +#' @section Notes: +#' +#' See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +#' +#' @param lotek_positions A data frame of GPS data matching the format returned +#' by \code{\link{fetch_lotek_positions}}. +#' @param position_type One of 'Litetrack (Standard fix)', +#' 'Litetrack (Swift fix)', or 'Iridium Track'. Determines how the information +#' in the FixType and RxStatus columns is parsed. See API User Manual for +#' details. If you have more than one position type in the same account +#' separate them before passing into this function (see aexamples). +#' +#' @return A tibble containing the original position data with additional +#' columns containing fix status information and in some cases the number of +#' satellites. +#' +#' @seealso \code{\link{fetch_lotek_positions}} +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' +#' lotek_login("demo", "PASSWORD09") +#' +#' # download and parse all data - standard fixes +#' fixes <- fetch_lotek_positions() |> +#' add_lotek_status() +#' +#' # download and parse all data - swift fixes +#' fixes <- fetch_lotek_positions() |> +#' add_lotek_status("Litetrack (Swift fix)") +#' +#' # download and parse all data - iridium +#' fixes <- fetch_lotek_positions() |> +#' add_lotek_status("Iridium Track") +#' +#' # if data contains collars with different position types... +#' standard_dev <- c(34023, 42492) +#' swift_dev <- 32763 +#' +#' # download all, split, add status, and recombine... +#' fixes <- fetch_lotek_positions() +#' standard_fixes <- fixes |> +#' dplyr::filter(DeviceID %in% standard_dev) |> +#' add_lotek_status() +#' swift_fixes <- fixes |> +#' dplyr::filter(DeviceID %in% swift_dev) |> +#' add_lotek_status("Litetrack (Swift fix)") +#' fixes <- standard_fixes |> +#' dplyr::bind_rows(swift_fixes) +#' +#' # or download separately, add status, and combine +#' standard_fixes <- fetch_lotek_positions(device_id = standard_dev) |> +#' add_lotek_status() +#' swift_fixes <- fetch_lotek_positions(device_id = swift_dev) |> +#' add_lotek_status("Litetrack (Swift fix)") +#' fixes <- standard_fixes |> +#' dplyr::bind_rows(swift_fixes) +#' +#' } +add_lotek_status <- function(lotek_positions, + position_type = c("Litetrack (Standard fix)", + "Litetrack (Swift fix)", + "Iridium Track")) { + + pos_type <- match.arg(position_type) + + if (pos_type == "Iridium Track") { + + join_tbl <- tibble::tibble( + RxStatus = c(0L, 3L, 4L, 19L, 20L), + FixStatus = c( + "No satellites", "2D fix", "3D fix", + "2D validated fix", "3D validated fix" + ) + ) + + out <- lotek_positions %>% + dplyr::left_join(join_tbl, by = "RxStatus") + + } + + if (pos_type == "Litetrack (Standard fix)") { + + join_tbl <- tibble::tibble( + StatusCode = 0L:7L, + FixStatus = c( + "No fix", "1_SV KF Solution", "2_SV KF", "3_SV KF", + "4 or more SV KF", "2-D least-squares", "3-D least-squares", "DR" + ) + ) + + out <- lotek_positions %>% + dplyr::mutate( + StatusCode = lotek_status_code(.data$RxStatus), + NumSats = lotek_num_sats(.data$RxStatus) + ) %>% + dplyr::left_join(join_tbl, by = "StatusCode") + + } + + if (pos_type == "Litetrack (Swift fix)") { + + ft_join_tbl <- tibble::tibble( + FixType = 0L:3L, + FixTypeText = c( + "Time out", "Standard fix", "Swift fix", "Iridium position" + ) + ) + + rx_join_tbl <- tibble::tibble( + FixType = c(rep(1L, 8), rep(2L, 3)), + StatusCode = c(0L:7L, 0L:2L), + FixStatus = c( + "No fix", "1_SV KF Solution", "2_SV KF", "3_SV KF", + "4 or more SV KF", "2-D least-squares", "3-D least-squares", "DR", + "Valid fix", "Not enough satellites", "No ephemeris" + ) + ) + + out <- lotek_positions %>% + dplyr::mutate( + StatusCode = lotek_status_code(.data$RxStatus) + ) %>% + dplyr::left_join(ft_join_tbl, by = "FixType") %>% + dplyr::left_join(rx_join_tbl, by = c("FixType", "StatusCode")) + + } + + out + +} + +#' @title Parse Lotek Status Code +#' +#' @description Converts integers in RxStatus column of API response to integer +#' fix status codes. +#' +#' @section Notes: +#' +#' See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +#' +#' @param int_values Vector of integers from API response +#' +#' @return Vector of integer status codes +#' +#' @seealso \code{\link{lotek_num_sats}} +#' +#' @export +#' +#' @keywords internal +#' +lotek_status_code <- function(int_values) { + + vapply( + int_values, + function(i) sum(as.integer(intToBits(i)[1:3]) * c(1L, 2L, 4L)), + FUN.VALUE = integer(1) + ) + +} + +#' @title Parse Lotek Number of Satellites +#' +#' @description Converts integers in RxStatus column of API response to number +#' of satellites. +#' +#' @section Notes: +#' +#' See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +#' +#' @param int_values Vector of integers from API response +#' +#' @return Intger vector representing number of satellites for each fix +#' +#' @seealso \code{\link{lotek_status_code}} +#' +#' @export +#' +#' @keywords internal +#' +lotek_num_sats <- function(int_values) { + + vapply( + int_values, + function(i) sum(as.integer(intToBits(i)[4:7]) * c(1L, 2L, 4L, 8L)), + FUN.VALUE = integer(1) + ) + +} diff --git a/R/fetch_trktg.R b/R/fetch_trktg.R index ed177ac..2cc801a 100644 --- a/R/fetch_trktg.R +++ b/R/fetch_trktg.R @@ -42,11 +42,11 @@ trktg_empty_pos <- tibble::tibble( #' in the output. Must be a POSIX date or date/time object. If NULL #' Jan 1 1970 is used. #' @param end_date Only fixes at or before end_date are included, -#' analagous to start_date above. If NULL current date/time is used. +#' analogous to start_date above. If NULL current date/time is used. #' @param time_zone Time zone used to convert date values to \code{POSIX}. #' @param as_sf Boolean indicating if the result should be converted to an #' \code{sf} object. -#' @param sf_crs Coordinate referenece system for converting to \code{sf}. Only +#' @param sf_crs Coordinate reference system for converting to \code{sf}. Only #' CRS 4326 (WGS84) has been tested, coordinates may not be parsed correctly #' for other systems. #' @@ -74,7 +74,7 @@ trktg_empty_pos <- tibble::tibble( #' #' @examples #' \dontrun{ -#' trktg_login("some_user", "some_users_pw") +#' trktg_login("WyoDemo", "WYODEMO2024") #' #' # all fixes for all transmitters in 2024 #' fixes <- fetch_trktg_positions( @@ -139,19 +139,21 @@ fetch_trktg_positions <- function(device_id = NULL, } - body$From <- dplyr::if_else( - is.null(start_date), - "1/1/1970 00:00", - format(start_date, "%m/%d/%Y %R") - ) %>% - stringr::str_replace_all("0(\\d/)", "\\1") + if (is.null(start_date)) { + body$From <- "1/1/1970 00:00" + } else { + body$From <- format(start_date, "%m/%d/%Y %R") + } + + body$From <- gsub("0(\\d/)", "\\1", body$From) + + if (is.null(end_date)) { + body$To <- format(Sys.time(), "%m/%d/%Y %R") + } else { + body$To <- format(end_date, "%m/%d/%Y %R") + } - body$To <- dplyr::if_else( - is.null(end_date), - format(Sys.time(), "%m/%d/%Y %R"), - format(end_date, "%m/%d/%Y %R") - ) %>% - stringr::str_replace_all("0(\\d/)", "\\1") + body$To <- gsub("0(\\d/)", "\\1", body$To) # send request resp <- httr::RETRY( @@ -167,7 +169,7 @@ fetch_trktg_positions <- function(device_id = NULL, assertthat::assert_that( httr::status_code(resp) == 200, msg = paste( - "API call failed - device list.", + "API call failed - position report.", paste("Status:", httr::status_code(resp)), paste("Response:", httr::content(resp)), sep = "\n" @@ -219,8 +221,14 @@ fetch_trktg_positions <- function(device_id = NULL, out <- out %>% dplyr::mutate( - lng = stringr::str_extract(.data$`Lat/Lng`, "[0-9.-]+$"), - lat = stringr::str_extract(.data$`Lat/Lng`, "^[0-9.-]+") + lng = regmatches( + .data$`Lat/Lng`, + regexpr("[0-9.-]+$", .data$`Lat/Lng`) + ), + lat = regmatches( + .data$`Lat/Lng`, + regexpr("^[0-9.-]+", .data$`Lat/Lng`) + ) ) %>% sf::st_as_sf(coords = c("lng", "lat"), crs = sf_crs) @@ -250,7 +258,7 @@ fetch_trktg_positions <- function(device_id = NULL, #' #' @examples #' \dontrun{ -#' trktg_login("some_user", "some_users_pw") +#' trktg_login("WyoDemo", "WYODEMO2024") #' #' tt_devices <- fetch_trktg_devices() #' @@ -292,16 +300,29 @@ fetch_trktg_devices <- function() { httr::content() %>% xml2::xml_find_all("//label[contains(@for, 'asset')]") + api_id <- label_nodes %>% + xml2::xml_attr("for") + + api_id <- regmatches(api_id, regexpr("\\d{6}", api_id)) + + lbl_txt <- label_nodes %>% + xml2::xml_text() + + trktg_id <- regmatches( + lbl_txt, + regexpr("(?<=\\()\\d{9}(?=\\))", lbl_txt, perl = TRUE) + ) + + user_id <- regmatches( + lbl_txt, + regexpr("^.+(?= \\(\\d{9}\\))", lbl_txt, perl = TRUE) + ) + + # return tibble tibble::tibble( - api_id = label_nodes %>% - xml2::xml_attr("for") %>% - stringr::str_extract("\\d{6}"), - trktg_id = label_nodes %>% - xml2::xml_text() %>% - stringr::str_extract("(?<=\\()\\d{9}(?=\\))"), - user_id = label_nodes %>% - xml2::xml_text() %>% - stringr::str_extract("^.+(?= \\(\\d{9}\\))") + api_id = api_id, + trktg_id = trktg_id, + user_id = user_id ) } diff --git a/R/trktg_auth.R b/R/trktg_auth.R index 4d7fda9..934a6b1 100644 --- a/R/trktg_auth.R +++ b/R/trktg_auth.R @@ -68,7 +68,7 @@ trktg_token <- function() { #' #' @examples #' \dontrun{ -#' trktg_login("some_user", "some_users_pw") +#' trktg_login("WyoDemo", "WYODEMO2024") #' #' fixes <- fetch_trktg_positions() #' @@ -162,7 +162,7 @@ trktg_login <- function(usr, pwd) { #' #' @examples #' \dontrun{ -#' trktg_login("some_user", "some_users_pw") +#' trktg_login("WyoDemo", "WYODEMO2024") #' #' fixes <- fetch_trktg_positions() #' diff --git a/man/add_lotek_status.Rd b/man/add_lotek_status.Rd new file mode 100644 index 0000000..6359b29 --- /dev/null +++ b/man/add_lotek_status.Rd @@ -0,0 +1,80 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fetch_lotek.R +\name{add_lotek_status} +\alias{add_lotek_status} +\title{Add Lotek Status} +\usage{ +add_lotek_status( + lotek_positions, + position_type = c("Litetrack (Standard fix)", "Litetrack (Swift fix)", "Iridium Track") +) +} +\arguments{ +\item{lotek_positions}{A data frame of GPS data matching the format returned +by \code{\link{fetch_lotek_positions}}.} + +\item{position_type}{One of 'Litetrack (Standard fix)', +'Litetrack (Swift fix)', or 'Iridium Track'. Determines how the information +in the FixType and RxStatus columns is parsed. See API User Manual for +details. If you have more than one position type in the same account +separate them before passing into this function (see aexamples).} +} +\value{ +A tibble containing the original position data with additional + columns containing fix status information and in some cases the number of + satellites. +} +\description{ +Parses coded values in RxStatus column of API response. +} +\section{Notes}{ + + + See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +} + +\examples{ +\dontrun{ + +lotek_login("demo", "PASSWORD09") + +# download and parse all data - standard fixes +fixes <- fetch_lotek_positions() |> + add_lotek_status() + +# download and parse all data - swift fixes +fixes <- fetch_lotek_positions() |> + add_lotek_status("Litetrack (Swift fix)") + +# download and parse all data - iridium +fixes <- fetch_lotek_positions() |> + add_lotek_status("Iridium Track") + +# if data contains collars with different position types... +standard_dev <- c(34023, 42492) +swift_dev <- 32763 + +# download all, split, add status, and recombine... +fixes <- fetch_lotek_positions() +standard_fixes <- fixes |> + dplyr::filter(DeviceID \%in\% standard_dev) |> + add_lotek_status() +swift_fixes <- fixes |> + dplyr::filter(DeviceID \%in\% swift_dev) |> + add_lotek_status("Litetrack (Swift fix)") +fixes <- standard_fixes |> + dplyr::bind_rows(swift_fixes) + +# or download separately, add status, and combine +standard_fixes <- fetch_lotek_positions(device_id = standard_dev) |> + add_lotek_status() +swift_fixes <- fetch_lotek_positions(device_id = swift_dev) |> + add_lotek_status("Litetrack (Swift fix)") +fixes <- standard_fixes |> + dplyr::bind_rows(swift_fixes) + +} +} +\seealso{ +\code{\link{fetch_lotek_positions}} +} diff --git a/man/fetch_lotek_positions.Rd b/man/fetch_lotek_positions.Rd index 72ec2b8..545d06a 100644 --- a/man/fetch_lotek_positions.Rd +++ b/man/fetch_lotek_positions.Rd @@ -15,7 +15,7 @@ in the output. Must be in the format YYYY-MM-DD HH:MM:SS. If NULL Jan 1 1970 is used.} \item{end_date}{Only fixes at or before end_date are included, -analagous to start_date above. If NULL current date/time is used.} +analogous to start_date above. If NULL current date/time is used.} } \value{ A tibble containing position data, or an empty tibble in the diff --git a/man/fetch_trktg_devices.Rd b/man/fetch_trktg_devices.Rd index 20f1d69..8d3ecd1 100644 --- a/man/fetch_trktg_devices.Rd +++ b/man/fetch_trktg_devices.Rd @@ -20,7 +20,7 @@ Retrieves a list of devices (transmitters) associated } \examples{ \dontrun{ -trktg_login("some_user", "some_users_pw") +trktg_login("WyoDemo", "WYODEMO2024") tt_devices <- fetch_trktg_devices() diff --git a/man/fetch_trktg_positions.Rd b/man/fetch_trktg_positions.Rd index 7ef7eb6..a8fd4ee 100644 --- a/man/fetch_trktg_positions.Rd +++ b/man/fetch_trktg_positions.Rd @@ -24,14 +24,14 @@ in the output. Must be a POSIX date or date/time object. If NULL Jan 1 1970 is used.} \item{end_date}{Only fixes at or before end_date are included, -analagous to start_date above. If NULL current date/time is used.} +analogous to start_date above. If NULL current date/time is used.} \item{time_zone}{Time zone used to convert date values to \code{POSIX}.} \item{as_sf}{Boolean indicating if the result should be converted to an \code{sf} object.} -\item{sf_crs}{Coordinate referenece system for converting to \code{sf}. Only +\item{sf_crs}{Coordinate reference system for converting to \code{sf}. Only CRS 4326 (WGS84) has been tested, coordinates may not be parsed correctly for other systems.} } @@ -58,7 +58,7 @@ Retrieves GPS data optionally filtered by date or collar. } \examples{ \dontrun{ -trktg_login("some_user", "some_users_pw") +trktg_login("WyoDemo", "WYODEMO2024") # all fixes for all transmitters in 2024 fixes <- fetch_trktg_positions( diff --git a/man/lotek_num_sats.Rd b/man/lotek_num_sats.Rd new file mode 100644 index 0000000..4410cf0 --- /dev/null +++ b/man/lotek_num_sats.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fetch_lotek.R +\name{lotek_num_sats} +\alias{lotek_num_sats} +\title{Parse Lotek Number of Satellites} +\usage{ +lotek_num_sats(int_values) +} +\arguments{ +\item{int_values}{Vector of integers from API response} +} +\value{ +Intger vector representing number of satellites for each fix +} +\description{ +Converts integers in RxStatus column of API response to number + of satellites. +} +\section{Notes}{ + + + See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +} + +\seealso{ +\code{\link{lotek_status_code}} +} +\keyword{internal} diff --git a/man/lotek_status_code.Rd b/man/lotek_status_code.Rd new file mode 100644 index 0000000..92aea6f --- /dev/null +++ b/man/lotek_status_code.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fetch_lotek.R +\name{lotek_status_code} +\alias{lotek_status_code} +\title{Parse Lotek Status Code} +\usage{ +lotek_status_code(int_values) +} +\arguments{ +\item{int_values}{Vector of integers from API response} +} +\value{ +Vector of integer status codes +} +\description{ +Converts integers in RxStatus column of API response to integer + fix status codes. +} +\section{Notes}{ + + + See Lotek Web Service API User Manual Revision 03 (30 MAY 2024) +} + +\seealso{ +\code{\link{lotek_num_sats}} +} +\keyword{internal} diff --git a/man/trktg_login.Rd b/man/trktg_login.Rd index 5c673f2..3caf839 100644 --- a/man/trktg_login.Rd +++ b/man/trktg_login.Rd @@ -20,7 +20,7 @@ Send username and password info to Track Tag website } \examples{ \dontrun{ -trktg_login("some_user", "some_users_pw") +trktg_login("WyoDemo", "WYODEMO2024") fixes <- fetch_trktg_positions() diff --git a/man/trktg_logout.Rd b/man/trktg_logout.Rd index d55947d..1f2e7f2 100644 --- a/man/trktg_logout.Rd +++ b/man/trktg_logout.Rd @@ -15,7 +15,7 @@ Removes authentication info from memory and logs out of } \examples{ \dontrun{ -trktg_login("some_user", "some_users_pw") +trktg_login("WyoDemo", "WYODEMO2024") fixes <- fetch_trktg_positions()