diff --git a/BirdFlowAPI.tar.gz b/BirdFlowAPI.tar.gz deleted file mode 100644 index 0331378..0000000 Binary files a/BirdFlowAPI.tar.gz and /dev/null differ diff --git a/NAMESPACE b/NAMESPACE index 4e0e672..fc4818b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,4 +5,3 @@ export(load_models) export(range_rescale) export(save_json_palette) export(symbolize_raster_data) -import(BirdFlowR) diff --git a/R/flow.R b/R/flow.R index 2b81f7b..3a339ee 100644 --- a/R/flow.R +++ b/R/flow.R @@ -22,7 +22,6 @@ get_s3_config <- function() { #' Null coalescing operator for config values `%||%` <- function(a, b) if (!is.null(a) && !is.na(a) && nzchar(a)) a else b -#' @import BirdFlowR if(FALSE) { # Manually set function arguments for dev and debugging @@ -35,12 +34,6 @@ if(FALSE) { # the other files are correct - # Load required libraries - library(BirdFlowR) - library(jsonlite) - library(terra) - library(aws.s3) - # Load globals and helpers original_wd <- getwd() if(!grepl("api$", getwd())) @@ -99,6 +92,11 @@ save_local_path <- "config/save_local.flag" #' `type` #' @export flow <- function(loc, week, taxa, n, direction = "forward", save_local = FALSE) { + # TODO: + # utils::data("species", package = "BirdFlowAPI", envir = environment()) + + # load_models() + s3_cfg <- get_s3_config() s3_enabled <- !is.na(s3_cfg$bucket) && nzchar(s3_cfg$bucket) @@ -139,24 +137,24 @@ flow <- function(loc, week, taxa, n, direction = "forward", save_local = FALSE) # Snap lat/lon to cell center using the first model (all models use same grid) bf <- models[[ifelse(taxa == "total", species$species[1], taxa)]] - xy <- latlon_to_xy(lat_lon$lat, lat_lon$lon, bf = bf) - col <- x_to_col(xy$x, bf = bf) - row <- y_to_row(xy$y, bf = bf) - x <- col_to_x(col, bf = bf) - y <- row_to_y(row, bf = bf) - snapped_latlon <- xy_to_latlon(x, y, bf = bf) + xy <- BirdFlowR::latlon_to_xy(lat_lon$lat, lat_lon$lon, bf = bf) + col <- BirdFlowR::x_to_col(xy$x, bf = bf) + row <- BirdFlowR::y_to_row(xy$y, bf = bf) + x <- BirdFlowR::col_to_x(col, bf = bf) + y <- BirdFlowR::row_to_y(row, bf = bf) + snapped_latlon <- BirdFlowR::xy_to_latlon(x, y, bf = bf) snapped_latlon$lat <- round(snapped_latlon$lat, 2) snapped_latlon$lon <- round(snapped_latlon$lon, 2) lat_lon <- snapped_latlon # Re-compute snapped xy for later use - xy <- latlon_to_xy(lat_lon$lat, lat_lon$lon, bf = bf) + xy <- BirdFlowR::latlon_to_xy(lat_lon$lat, lat_lon$lon, bf = bf) # Form file names and S3 keys using snapped lat/lon snapped_lat <- paste(lat_lon$lat, collapse = "_") snapped_lon <- paste(lat_lon$lon, collapse = "_") cache_prefix <- paste0(direction, "/", taxa, "_", week, "_", snapped_lat, "_", snapped_lon, "/") - pred_weeks <- lookup_timestep_sequence(bf, start = week, n = n, direction = direction) + pred_weeks <- BirdFlowR::lookup_timestep_sequence(bf, start = week, n = n, direction = direction) png_files <- paste0(flow_type, "_", taxa, "_", pred_weeks, ".png") symbology_files <- paste0(flow_type, "_", taxa, "_", pred_weeks, ".json") png_bucket_paths <- paste0(s3_flow_path, cache_prefix, png_files) @@ -169,14 +167,14 @@ flow <- function(loc, week, taxa, n, direction = "forward", save_local = FALSE) cache_hit <- TRUE if (!save_local && s3_enabled) { for (i in seq_along(pred_weeks)) { - png_exists <- object_exists(object = png_bucket_paths[i], bucket = s3_cfg$bucket) - json_exists <- object_exists(object = symbology_bucket_paths[i], bucket = s3_cfg$bucket) + png_exists <- aws.s3::object_exists(object = png_bucket_paths[i], bucket = s3_cfg$bucket) + json_exists <- aws.s3::object_exists(object = symbology_bucket_paths[i], bucket = s3_cfg$bucket) if (!png_exists || !json_exists) { cache_hit <- FALSE break } } - tiff_exists <- object_exists(object = tiff_bucket_path, bucket = s3_cfg$bucket) + tiff_exists <- aws.s3::object_exists(object = tiff_bucket_path, bucket = s3_cfg$bucket) if (!tiff_exists) cache_hit <- FALSE } else { # Local cache: check if all files exist in localtmp @@ -231,23 +229,23 @@ flow <- function(loc, week, taxa, n, direction = "forward", save_local = FALSE) for (i in seq_along(target_species)) { sp <- target_species[i] bf <- models[[sp]] - valid <- is_location_valid(bf, timestep = week, x = xy$x, y = xy$y) + valid <- BirdFlowR::is_location_valid(bf, timestep = week, x = xy$x, y = xy$y) if (!all(valid)) { next } any_valid <- TRUE - start_distr <- as_distr(xy, bf) + start_distr <- BirdFlowR::as_distr(xy, bf) if (nrow(lat_lon) > 1) { start_distr <- apply(start_distr, 1, sum) start_distr <- start_distr / sum(start_distr) } log_progress(paste("Starting prediction for", sp)) pred <- predict(bf, start_distr, start = week, n = n, direction = direction) - location_i <- xy_to_i(xy, bf = bf) - initial_population_distr <- get_distr(bf, which = week) + location_i <- BirdFlowR::xy_to_i(xy, bf = bf) + initial_population_distr <- BirdFlowR::get_distr(bf, which = week) start_proportion <- sum(initial_population_distr[location_i]) / 1 - abundance <- pred * species$population[species$species == sp] / prod(res(bf) / 1000) * start_proportion - this_raster <- rasterize_distr(abundance, bf = bf, format = "terra") + abundance <- pred * species$population[species$species == sp] / prod(terra::res(bf) / 1000) * start_proportion + this_raster <- BirdFlowR::rasterize_distr(abundance, bf = bf, format = "terra") if (is.null(combined)) { combined <- this_raster } else { diff --git a/R/load_models.R b/R/load_models.R index d9df9e9..b28729f 100644 --- a/R/load_models.R +++ b/R/load_models.R @@ -15,6 +15,8 @@ #' #' @export load_models <- function() { + # utils::data("species", package = "BirdFlowAPI", envir = environment()) + # Load BirdFlow models BirdFlowR::birdflow_options(collection_url = "https://birdflow-science.s3.amazonaws.com/sparse_avian_flu/") index <- BirdFlowR::load_collection_index() @@ -23,14 +25,16 @@ load_models <- function() { stop("Expected BirdFlow models:", paste(miss, collapse = ", "), " are missing from the model collection." ) } - # This is slow so skipping if it's already done - useful when developing to - # avoid having to wait to reload. - if(!exists("models") || !is.environment(models) || !all(species$species %in% names(models))) { - models <<- new.env(parent = globalenv()) - print(paste("Loading", length(species$species), "models from https://birdflow-science.s3.amazonaws.com/avian_flu/")) + # This is slow so skipping if it's already done - useful when developing to + # avoid having to wait to reload. + if(!all(species$species %in% names(models))) { + print(paste("Loading", length(species$species), "models from https://birdflow-science.s3.amazonaws.com/sparse_avian_flu/")) for (sp in species$species) { print(paste0("Loading model for species: ", sp)) models[[sp]] <- BirdFlowR::load_model(model = sp) } } -} \ No newline at end of file + else { + print("Models already loaded.") + } +} diff --git a/R/models.R b/R/models.R new file mode 100644 index 0000000..865143f --- /dev/null +++ b/R/models.R @@ -0,0 +1 @@ +models <- new.env() diff --git a/R/zzz.R b/R/zzz.R index 2b09801..c27bd5b 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,9 +1,3 @@ -.onLoad <- function(libname, pkgname) { - utils::data("species", package = "BirdFlowAPI", envir = parent.env(environment())) - utils::data("flow_colors", package = "BirdFlowAPI", envir = parent.env(environment())) - load_models() -} - .ignore_unused_imports <- function() { plumber::plumb } \ No newline at end of file diff --git a/README.md b/README.md index f85073d..8f57d27 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,10 @@ BirdFlowAPI is an R package containing the API and backend code for the Avian In You can build and install BirdFlowAPI locally or from GitHub. -**Build locally:** -```sh -R CMD build . -``` -This will create a source tarball (e.g., `BirdFlowAPI_x.y.z.tar.gz`). -**Install from local tarball:** +**Install from local cloned repository:** ```r -install.packages("path/to/BirdFlowAPI_x.y.z.tar.gz", repos = NULL, type = "source") +devtools::install() ``` **Install from GitHub:** @@ -22,6 +17,10 @@ install.packages("path/to/BirdFlowAPI_x.y.z.tar.gz", repos = NULL, type = "sourc devtools::install_github("UMassCDS/BirdFlowAPI") ``` +## Configure API + +The `load_models.R` file defines a function `load_models` that loads the the models of all species. The `load_models` function must be explicitly called while configuring the API. This is done by running `load_models()`. + ## Testing Run the full test suite: diff --git a/inst/plumber/flow/endpoints/predict.R b/inst/plumber/flow/endpoints/predict.R index 6d59958..5ec5927 100644 --- a/inst/plumber/flow/endpoints/predict.R +++ b/inst/plumber/flow/endpoints/predict.R @@ -1,4 +1,3 @@ -library(BirdFlowR) birdflow_options(collection_url = "https://birdflow-science.s3.amazonaws.com/avian_flu/") #* Test and generate a plot from a BirdFlow model @@ -27,4 +26,4 @@ function(model_name = "ambduc") { file_exists = file_exists, file_size = file_size ) -} \ No newline at end of file +} diff --git a/man/flow.Rd b/man/flow.Rd index 6fc4cb9..07605d4 100644 --- a/man/flow.Rd +++ b/man/flow.Rd @@ -4,7 +4,7 @@ \alias{flow} \title{Implement inflow and outflow} \usage{ -flow(loc, week, taxa, n, direction = "forward", save_local = SAVE_LOCAL) +flow(loc, week, taxa, n, direction = "forward", save_local = FALSE) } \arguments{ \item{loc}{One or more locations as a scalar character each location should @@ -21,7 +21,7 @@ the initial week so will have n + 1 images)} \item{direction}{Which direction to project in "backward" for inflow or "forward" for outflow"} -\item{save_local}{True/False boolean flag - determines whether to copy files to AWS S3 bucket} +\item{save_local}{True/False boolean flag - determines whether to save results locally (default: value in config/save_local.flag, or FALSE if not present). If FALSE, results are uploaded to S3 if configured.} } \value{ A list with components: @@ -36,6 +36,7 @@ A list with components: \code{week} \code{url} \code{legend} +\code{type} } \description{ This function is the heart of the inflow and outflow api and does all diff --git a/man/get_s3_config.Rd b/man/get_s3_config.Rd new file mode 100644 index 0000000..71b2e21 --- /dev/null +++ b/man/get_s3_config.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/flow.R +\name{get_s3_config} +\alias{get_s3_config} +\title{Get S3 configuration from explicit config, environment variables, or IAM role} +\usage{ +get_s3_config() +} +\description{ +Get S3 configuration from explicit config, environment variables, or IAM role +} diff --git a/man/grapes-or-or-grapes.Rd b/man/grapes-or-or-grapes.Rd new file mode 100644 index 0000000..6c41ad5 --- /dev/null +++ b/man/grapes-or-or-grapes.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/flow.R +\name{\%||\%} +\alias{\%||\%} +\title{Null coalescing operator for config values} +\usage{ +a \%||\% b +} +\description{ +Null coalescing operator for config values +} diff --git a/man/set_s3_config.Rd b/man/set_s3_config.Rd new file mode 100644 index 0000000..c97a5e1 --- /dev/null +++ b/man/set_s3_config.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/flow.R +\name{set_s3_config} +\alias{set_s3_config} +\title{Set S3 configuration explicitly} +\usage{ +set_s3_config( + access_key = NULL, + secret_key = NULL, + region = NULL, + bucket = NULL +) +} +\description{ +Allows user to set S3 credentials and bucket at runtime. If not set, environment variables or IAM role will be used. +} diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..d6b67ae --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1 @@ +load_models() \ No newline at end of file