From ea43e5afd5521ae9d06490c9146d2af34d6ca919 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 25 Apr 2024 12:42:02 -0400 Subject: [PATCH 01/65] https://github.com/NIEHS/amadeus/issues/71 --- R/calculate_covariates_auxiliary.R | 6 +++++- tests/testthat/test-calculate_covariates.R | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index dfe8f3ed..c1aa179e 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -374,7 +374,11 @@ calc_worker <- function( sites_extracted_layer <- cbind( locs_df, data_time, - data_level, + gsub( + "level=|lev=", + "", + data_level + ), sites_extracted_layer ) colnames(sites_extracted_layer) <- c( diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index ddadaea6..13d55bb2 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -1191,7 +1191,7 @@ testthat::test_that("calc_merra2 returns as expected.", { ncp$site_id <- "3799900018810101" # expect function expect_true( - is.function(calc_geos) + is.function(calc_merra2) ) for (c in seq_along(collections)) { collection <- collections[c] From 334846d70892ebf7becedfd3989911e344508fd1 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 25 Apr 2024 13:24:26 -0400 Subject: [PATCH 02/65] https://github.com/NIEHS/amadeus/issues/70 --- R/process.R | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/R/process.R b/R/process.R index 324df624..0b43c19d 100644 --- a/R/process.R +++ b/R/process.R @@ -1170,6 +1170,12 @@ process_hms <- function( date[2], sub_hyphen = TRUE ) + #### dates of interest with hyphen for return in 0 polygon case + dates_no_polygon <- generate_date_sequence( + date[1], + date[2], + sub_hyphen = FALSE + ) #### subset file paths to only dates of interest data_paths <- unique( grep( @@ -1190,7 +1196,9 @@ process_hms <- function( "EPSG:4326" ) #### subset to density of interest - data_density <- data_date_p[data_date_p$Density == variable] + data_density <- data_date_p[ + tolower(data_date_p$Density) == tolower(variable) + ] #### absent polygons (ie. December 31, 2018) if (nrow(data_density) == 0) { cat(paste0( @@ -1245,6 +1253,11 @@ process_hms <- function( data_aggregate <- data_aggregate[ seq_len(nrow(data_aggregate)), c("Density", "Date") ] + #### apply date format + data_aggregate$Date <- as.Date( + data_aggregate$Date, + format = "%Y%m%d" + ) #### merge with other data data_return <- rbind(data_return, data_aggregate) } @@ -1265,7 +1278,8 @@ process_hms <- function( ), ". Returning vector of dates.\n" )) - return(c(variable, dates_of_interest)) + no_polygon_return <- c(variable, as.character(dates_no_polygon)) + return(no_polygon_return) } else if (nrow(data_return) > 0) { cat(paste0( "Returning daily ", From 9c2502afb3037ecb0375ec4697e699d87601e393 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Fri, 26 Apr 2024 07:00:37 -0400 Subject: [PATCH 03/65] https://github.com/NIEHS/amadeus/issues/66 --- R/download.R | 178 +++++++++++------------ tests/testthat/test-download_functions.R | 52 +++---- vignettes/download_functions.Rmd | 36 ++--- 3 files changed, 133 insertions(+), 133 deletions(-) diff --git a/R/download.R b/R/download.R index 141264cb..aa88d4e4 100644 --- a/R/download.R +++ b/R/download.R @@ -12,28 +12,28 @@ #' large and use lots of machine storage and memory. #' @param ... Arguments passed to each download function. #' @note -#' - All download function names are in \code{download_*_data} formats +#' - All download function names are in \code{download_*} formats #' @author Insang Song #' @seealso #' For details of each download function per dataset, #' Please refer to: -#' * \link{download_aqs_data}: "aqs", "AQS" -#' * \link{download_ecoregion_data}: "ecoregion" -#' * \link{download_geos_data}: "geos" -#' * \link{download_gmted_data}: "gmted", "GMTED" -#' * \link{download_koppen_geiger_data}: "koppen", "koppengeiger" -#' * \link{download_merra2_data}: "merra2", "merra", "MERRA", "MERRA2" -#' * \link{download_narr_monolevel_data}: "narr_monolevel", "monolevel" -#' * \link{download_narr_p_levels_data}: "narr_p_levels", "p_levels", "plevels" -#' * \link{download_nlcd_data}: "nlcd", "NLCD" -#' * \link{download_hms_data}: "noaa", "smoke", "hms" -#' * \link{download_sedac_groads_data}: "sedac_groads", "groads" -#' * \link{download_sedac_population_data}: "sedac_population", "population" -#' * \link{download_modis_data}: "modis", "MODIS" -#' * \link{download_tri_data}: "tri", "TRI" -#' * \link{download_nei_data}: "nei", "NEI" -#' * \link{download_gridmet_data}: "gridMET", "gridmet" -#' * \link{download_terraclimate_data}: "TerraClimate", "terraclimate" +#' * \link{download_aqs}: "aqs", "AQS" +#' * \link{download_ecoregion}: "ecoregion" +#' * \link{download_geos}: "geos" +#' * \link{download_gmted}: "gmted", "GMTED" +#' * \link{download_koppen_geiger}: "koppen", "koppengeiger" +#' * \link{download_merra2}: "merra2", "merra", "MERRA", "MERRA2" +#' * \link{download_narr_monolevel}: "narr_monolevel", "monolevel" +#' * \link{download_narr_p_levels}: "narr_p_levels", "p_levels", "plevels" +#' * \link{download_nlcd}: "nlcd", "NLCD" +#' * \link{download_hms}: "noaa", "smoke", "hms" +#' * \link{download_sedac_groads}: "sedac_groads", "groads" +#' * \link{download_sedac_population}: "sedac_population", "population" +#' * \link{download_modis}: "modis", "MODIS" +#' * \link{download_tri}: "tri", "TRI" +#' * \link{download_nei}: "nei", "NEI" +#' * \link{download_gridmet}: "gridMET", "gridmet" +#' * \link{download_terraclimate}: "TerraClimate", "terraclimate" #' @returns NULL #' @export download_data <- @@ -55,38 +55,38 @@ download_data <- # determine whether the data exist and deter proceeding? what_to_run <- switch(dataset_name, - aqs = download_aqs_data, - ecoregion = download_ecoregion_data, - geos = download_geos_data, - gmted = download_gmted_data, - koppen = download_koppen_geiger_data, - koppengeiger = download_koppen_geiger_data, - merra2 = download_merra2_data, - merra = download_merra2_data, - narr_monolevel = download_narr_monolevel_data, - monolevel = download_narr_monolevel_data, - narr_p_levels = download_narr_p_levels_data, - p_levels = download_narr_p_levels_data, - plevels = download_narr_p_levels_data, - nlcd = download_nlcd_data, - noaa = download_hms_data, - smoke = download_hms_data, - hms = download_hms_data, - sedac_groads = download_sedac_groads_data, - groads = download_sedac_groads_data, - sedac_population = download_sedac_population_data, - population = download_sedac_population_data, - modis = download_modis_data, - tri = download_tri_data, - nei = download_nei_data, - gridmet = download_gridmet_data, - terraclimate = download_terraclimate_data, - huc = download_huc_data, - cropscape = download_cropscape_data, - cdl = download_cropscape_data, - prism = download_prism_data, - olm = download_olm_data, - openlandmap = download_olm_data + aqs = download_aqs, + ecoregion = download_ecoregion, + geos = download_geos, + gmted = download_gmted, + koppen = download_koppen_geiger, + koppengeiger = download_koppen_geiger, + merra2 = download_merra2, + merra = download_merra2, + narr_monolevel = download_narr_monolevel, + monolevel = download_narr_monolevel, + narr_p_levels = download_narr_p_levels, + p_levels = download_narr_p_levels, + plevels = download_narr_p_levels, + nlcd = download_nlcd, + noaa = download_hms, + smoke = download_hms, + hms = download_hms, + sedac_groads = download_sedac_groads, + groads = download_sedac_groads, + sedac_population = download_sedac_population, + population = download_sedac_population, + modis = download_modis, + tri = download_tri, + nei = download_nei, + gridmet = download_gridmet, + terraclimate = download_terraclimate, + huc = download_huc, + cropscape = download_cropscape, + cdl = download_cropscape, + prism = download_prism, + olm = download_olm, + openlandmap = download_olm ) tryCatch( @@ -109,7 +109,7 @@ download_data <- # nolint start #' Download air quality data #' @description -#' The \code{download_aqs_data()} function accesses and downloads Air Quality System (AQS) data from the [U.S. Environmental Protection Agency's (EPA) Pre-Generated Data Files](https://aqs.epa.gov/aqsweb/airdata/download_files.html). +#' The \code{download_aqs()} function accesses and downloads Air Quality System (AQS) data from the [U.S. Environmental Protection Agency's (EPA) Pre-Generated Data Files](https://aqs.epa.gov/aqsweb/airdata/download_files.html). #' @param parameter_code integer(1). length of 5. #' EPA pollutant parameter code. For details, please refer to #' [AQS parameter codes](https://aqs.epa.gov/aqsweb/documents/codetables/parameters.html) @@ -144,7 +144,7 @@ download_data <- #' monitors and the daily representative values #' will be stored in \code{directory_to_save}. #' @export -download_aqs_data <- +download_aqs <- function( parameter_code = 88101, resolution_temporal = "daily", @@ -268,7 +268,7 @@ download_aqs_data <- # nolint start #' Download ecoregion data #' @description -#' The \code{download_ecoregion_data()} function accesses and downloads United States Ecoregions data from the [U.S. Environmental Protection Agency's (EPA) Ecorgions](https://www.epa.gov/eco-research/ecoregions). Level 3 data, where all pieces of information in the higher levels are included, are downloaded. +#' The \code{download_ecoregion()} function accesses and downloads United States Ecoregions data from the [U.S. Environmental Protection Agency's (EPA) Ecorgions](https://www.epa.gov/eco-research/ecoregions). Level 3 data, where all pieces of information in the higher levels are included, are downloaded. # nolint end #' @note #' For EPA Data Commons certificate errors, follow the steps below: @@ -303,7 +303,7 @@ download_aqs_data <- #' @returns NULL; #' @importFrom utils download.file #' @export -download_ecoregion_data <- function( +download_ecoregion <- function( epa_certificate_path = system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus"), @@ -409,7 +409,7 @@ download_ecoregion_data <- function( # nolint start #' Download atmospheric composition data #' @description -#' The \code{download_geos_data()} function accesses and downloads various +#' The \code{download_geos()} function accesses and downloads various #' atmospheric composition collections from [NASA's Global Earth Observing System (GEOS) model](https://gmao.gsfc.nasa.gov/GEOS_systems/). # nolint end #' @param collection character(1). GEOS-CF data collection file name. @@ -431,7 +431,7 @@ download_ecoregion_data <- function( #' @return NULL; Hourly netCDF (.nc4) files will be stored in #' \code{directory_to_save}. #' @export -download_geos_data <- function( +download_geos <- function( collection = c( "aqc_tavg_1hr_g1440x721_v1", "chm_tavg_1hr_g1440x721_v1", @@ -561,7 +561,7 @@ download_geos_data <- function( # nolint start #' Download elevation data #' @description -#' The \code{download_gmted_data()} function accesses and downloads Global +#' The \code{download_gmted()} function accesses and downloads Global #' Multi-resolution Terrain Elevation Data (GMTED2010) from #' [U.S. Geological Survey and National Geospatial-Intelligence Agency](https://www.usgs.gov/coastal-changes-and-impacts/gmted2010). #' @param statistic character(1). Available statistics include `"Breakline Emphasis"`, `"Systematic Subsample"`, `"Median Statistic"`, @@ -589,7 +589,7 @@ download_geos_data <- function( #' \code{directory_to_download}, and directories containing raw ASCII Grid data #'will be stored in \code{directory_to_save}. #' @export -download_gmted_data <- function( +download_gmted <- function( statistic = c( "Breakline Emphasis", "Systematic Subsample", "Median Statistic", "Minimum Statistic", @@ -709,7 +709,7 @@ download_gmted_data <- function( # nolint start #' Download meteorological and atmospheric data #' @description -#' The \code{download_merra2_data()} function accesses and downloads various +#' The \code{download_merra2()} function accesses and downloads various #' meteorological and atmospheric collections from [NASA's Modern-Era Retrospective analysis for Research and Applications, Version 2 (MERRA-2) model](https://gmao.gsfc.nasa.gov/reanalysis/MERRA-2/). #' @param collection character(1). MERRA-2 data collection file name. #' @param date_start character(1). length of 10. Start date for downloading @@ -731,7 +731,7 @@ download_gmted_data <- function( #' \code{directory_to_save}. #' @export # nolint end -download_merra2_data <- function( +download_merra2 <- function( collection = c( "inst1_2d_asm_Nx", "inst1_2d_int_Nx", "inst1_2d_lfo_Nx", "inst3_3d_asm_Np", "inst3_3d_aer_Nv", "inst3_3d_asm_Nv", @@ -1017,7 +1017,7 @@ download_merra2_data <- function( # nolint start #' Download meteorological data (monolevel) #' @description -#' The \code{download_narr_monolevel_data} function accesses and downloads monolevel meteorological data from [NOAA's North American Regional Reanalysis (NARR) model](https://psl.noaa.gov/data/gridded/data.narr.html). "Monolevel" variables contain a single value for the entire atmospheric column (ie. Variable: Convective cloud cover; Level: Entire atmosphere considered as a single layer), or represent a specific altitude associated with the variable (ie. Variable: Air temperature; Level: 2 m). +#' The \code{download_narr_monolevel} function accesses and downloads monolevel meteorological data from [NOAA's North American Regional Reanalysis (NARR) model](https://psl.noaa.gov/data/gridded/data.narr.html). "Monolevel" variables contain a single value for the entire atmospheric column (ie. Variable: Convective cloud cover; Level: Entire atmosphere considered as a single layer), or represent a specific altitude associated with the variable (ie. Variable: Air temperature; Level: 2 m). #' @param variables character. Variable(s) name acronym. See [List of Variables in NARR Files](https://ftp.cpc.ncep.noaa.gov/NARR/fixed/merged_land_AWIP32corrected.pdf) #' for variable names and acronym codes. #' @param year_start integer(1). length of 4. Start of year range for @@ -1040,7 +1040,7 @@ download_merra2_data <- function( #' folder within \code{directory_to_save}. #' @export # nolint end -download_narr_monolevel_data <- function( +download_narr_monolevel <- function( variables = NULL, year_start = 2022, year_end = 2022, @@ -1143,7 +1143,7 @@ download_narr_monolevel_data <- function( # nolint start #' Download meteorological data (pressure levels) #' @description -#' The \code{download_narr_p_levels_data} function accesses and downloads pressure levels meteorological data from [NOAA's North American Regional Reanalysis (NARR) model](https://psl.noaa.gov/data/gridded/data.narr.html). "Pressure levels" variables contain variable values at 29 atmospheric levels, ranging from 1000 hPa to 100 hPa. All pressure levels data will be downloaded for each variable. +#' The \code{download_narr_p_levels} function accesses and downloads pressure levels meteorological data from [NOAA's North American Regional Reanalysis (NARR) model](https://psl.noaa.gov/data/gridded/data.narr.html). "Pressure levels" variables contain variable values at 29 atmospheric levels, ranging from 1000 hPa to 100 hPa. All pressure levels data will be downloaded for each variable. #' @param variables character. Variable(s) name acronym. See [List of Variables in NARR Files](https://ftp.cpc.ncep.noaa.gov/NARR/fixed/merged_land_AWIP32corrected.pdf) #' for variable names and acronym codes. #' @param year_start integer(1). length of 4. Start of year range for @@ -1167,7 +1167,7 @@ download_narr_monolevel_data <- function( #' @export # nolint end # nolint start: cyclocomp -download_narr_p_levels_data <- function( +download_narr_p_levels <- function( variables = NULL, year_start = 2022, year_end = 2022, @@ -1279,7 +1279,7 @@ download_narr_p_levels_data <- function( # nolint start #' Download land cover data #' @description -#' The \code{download_nlcd_data()} function accesses and downloads +#' The \code{download_nlcd()} function accesses and downloads #' land cover data from the #' [Multi-Resolution Land Characteristics (MRLC) Consortium's National Land Cover Database (NLCD) products data base](https://www.mrlc.gov/data). # nolint end @@ -1307,7 +1307,7 @@ download_narr_p_levels_data <- function( #' @returns NULL; Zip file will be stored in \code{directory_to_download}, and #' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. #' @export -download_nlcd_data <- function( +download_nlcd <- function( collection = "Coterminous United States", year = 2021, directory_to_download = NULL, @@ -1427,7 +1427,7 @@ download_nlcd_data <- function( # nolint start #' Download roads data #' @description -#' The \code{download_sedac_groads_data()} function accesses and downloads +#' The \code{download_sedac_groads()} function accesses and downloads #' roads data from [NASA's Global Roads Open Access Data Set (gROADS), v1 (1980-2010)](https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/data-download). #' @param data_region character(1). Data can be downloaded for `"Global"`, #' `"Africa"`, `"Asia"`, `"Europe"`, `"Americas"`, `"Oceania East"`, and `"Oceania West"`. @@ -1453,7 +1453,7 @@ download_nlcd_data <- function( #' selected Shapefile (.shp) or Geodatabase (.gdb) files will be stored in #' \code{directory_to_save}. #' @export -download_sedac_groads_data <- function( +download_sedac_groads <- function( data_region = c("Americas", "Global", "Africa", "Asia", "Europe", "Oceania East", "Oceania West"), data_format = c("Shapefile", "Geodatabase"), directory_to_download = NULL, @@ -1569,7 +1569,7 @@ download_sedac_groads_data <- function( # nolint start #' Download population density data #' @description -#' The \code{download_sedac_population_data()} function accesses and downloads +#' The \code{download_sedac_population()} function accesses and downloads #' population density data from [NASA's UN WPP-Adjusted Population Density, v4.11](https://sedac.ciesin.columbia.edu/data/set/gpw-v4-population-density-adjusted-to-2015-unwpp-country-totals-rev11). #' @param data_resolution character(1). Available resolutions are 30 second #' (approx. 1 km), 2.5 minute (approx. 5 km), 15 minute (approx. 30 km), @@ -1598,7 +1598,7 @@ download_sedac_groads_data <- function( #' @returns NULL; Zip file will be stored in \code{directory_to_download}, and #' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. #' @export -download_sedac_population_data <- function( +download_sedac_population <- function( data_resolution = "60 minute", data_format = c("GeoTIFF", "ASCII", "netCDF"), year = "2020", @@ -1747,7 +1747,7 @@ download_sedac_population_data <- function( # nolint start #' Download wildfire smoke data #' @description -#' The \code{download_hms_data()} function accesses and downloads +#' The \code{download_hms()} function accesses and downloads #' wildfire smoke plume coverage data from [NOAA's Hazard Mapping System Fire and Smoke Product](https://www.ospo.noaa.gov/Products/land/hms.html#0). # nolint end #' @param data_format character(1). "Shapefile" or "KML". @@ -1783,7 +1783,7 @@ download_sedac_population_data <- function( #' \code{directory_to_save}. #' @export # nolint start: cyclocomp -download_hms_data <- function( +download_hms <- function( data_format = "Shapefile", date_start = "2023-09-01", date_end = "2023-09-01", @@ -1923,7 +1923,7 @@ download_hms_data <- function( # nolint start #' Download climate classification data #' @description -#' The \code{download_koppen_geiger_data()} function accesses and downloads +#' The \code{download_koppen_geiger()} function accesses and downloads #' climate classification data from the \emph{Present and future #' Köppen-Geiger climate classification maps at #' 1-km resolution}([link for article](https://www.nature.com/articles/sdata2018214); [link for data](https://figshare.com/articles/dataset/Present_and_future_K_ppen-Geiger_climate_classification_maps_at_1-km_resolution/6396959/2)). @@ -1953,7 +1953,7 @@ download_hms_data <- function( #' @returns NULL; Zip file will be stored in \code{directory_to_download}, and #' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. #' @export -download_koppen_geiger_data <- function( +download_koppen_geiger <- function( data_resolution = c("0.0083", "0.083", "0.5"), time_period = c("Present", "Future"), directory_to_download = NULL, @@ -2127,7 +2127,7 @@ download_koppen_geiger_data <- function( #' @return NULL; Raw HDF (.hdf) files will be stored in #' \code{directory_to_save}. #' @export -download_modis_data <- function( +download_modis <- function( product = c( "MOD09GA", "MOD11A1", "MOD06_L2", "MCD19A2", "MOD13A2", "VNP46A2" @@ -2441,7 +2441,7 @@ download_modis_data <- function( # nolint start #' Download toxic release data #' @description -#' The \code{download_tri_data()} function accesses and downloads toxic release data from the [U.S. Environmental Protection Agency's (EPA) Toxic Release Inventory (TRI) Program](https://www.epa.gov/toxics-release-inventory-tri-program/find-understand-and-use-tri). +#' The \code{download_tri()} function accesses and downloads toxic release data from the [U.S. Environmental Protection Agency's (EPA) Toxic Release Inventory (TRI) Program](https://www.epa.gov/toxics-release-inventory-tri-program/find-understand-and-use-tri). # nolint end #' @param year_start integer(1). length of 4. Start year for downloading data. #' @param year_end integer(1). length of 4. End year for downloading data. @@ -2458,7 +2458,7 @@ download_modis_data <- function( #' @returns NULL; Yearly comma-separated value (CSV) files will be stored in #' \code{directory_to_save}. #' @export -download_tri_data <- function( +download_tri <- function( year_start = 2018L, year_end = 2022L, directory_to_save = NULL, @@ -2531,7 +2531,7 @@ download_tri_data <- function( # nolint start #' Download road emissions data #' @description -#' The \code{download_nei_data()} function accesses and downloads road emissions data from the [U.S Environmental Protection Agency's (EPA) National Emissions Inventory (NEI)](https://www.epa.gov/air-emissions-inventories/national-emissions-inventory-nei). +#' The \code{download_nei()} function accesses and downloads road emissions data from the [U.S Environmental Protection Agency's (EPA) National Emissions Inventory (NEI)](https://www.epa.gov/air-emissions-inventories/national-emissions-inventory-nei). # nolint end #' @param epa_certificate_path character(1). Path to the certificate file #' for EPA DataCommons. Default is @@ -2565,7 +2565,7 @@ download_tri_data <- function( #' @returns NULL; Yearly comma-separated value (CSV) files will be stored in #' \code{directory_to_save}. #' @export -download_nei_data <- function( +download_nei <- function( epa_certificate_path = system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus"), @@ -2762,7 +2762,7 @@ download_nei_data <- function( #' @seealso [list_stac_files] #' @export # nolint end -download_olm_data <- function( +download_olm <- function( product = NULL, format = "tif", directory_to_save = NULL, @@ -2865,7 +2865,7 @@ download_olm_data <- function( #' } #' @export # @importFrom archive archive_extract -download_huc_data <- +download_huc <- function( region = c("Lower48", "Islands"), type = c("Seamless", "OceanCatchment"), @@ -3001,7 +3001,7 @@ download_huc_data <- #' \code{directory_to_save}. #' @examples #' \dontrun{ -#' download_cropscape_data( +#' download_cropscape( #' 2020, "~/data", #' acknowledgement = TRUE, #' download = TRUE, @@ -3010,7 +3010,7 @@ download_huc_data <- #' } #' @importFrom archive archive_extract #' @export -download_cropscape_data <- function( +download_cropscape <- function( year = seq(1997, 2023), source = c("USDA", "GMU"), directory_to_save = NULL, @@ -3146,7 +3146,7 @@ download_cropscape_data <- function( #' \code{directory_to_save}. #' @examples #' \dontrun{ -#' download_prism_data( +#' download_prism( #' time = "202104", #' element = "ppt", #' data_type = "ts", @@ -3162,7 +3162,7 @@ download_cropscape_data <- function( #' * [PRISM Web Service Guide](https://prism.oregonstate.edu/documents/PRISM_downloads_web_service.pdf) #' @export # nolint end -download_prism_data <- function( +download_prism <- function( time, element = c("ppt", "tmin", "tmax", "tmean", "tdmean", "vpdmin", "vpdmax", @@ -3258,7 +3258,7 @@ download_prism_data <- function( # nolint start #' Download gridMET data #' @description -#' The \code{download_gridmet_data} function accesses and downloads gridded surface meteorological data from the [University of California Merced Climatology Lab's gridMET dataset](https://www.climatologylab.org/gridmet.html). +#' The \code{download_gridmet} function accesses and downloads gridded surface meteorological data from the [University of California Merced Climatology Lab's gridMET dataset](https://www.climatologylab.org/gridmet.html). #' @param variables character(1). Variable(s) name(s). See [gridMET Generate Wget File](https://www.climatologylab.org/wget-gridmet.html) #' for variable names and acronym codes. (Note: variable "Burning Index" has code "bi" and variable #' "Energy Release Component" has code "erc"). @@ -3282,7 +3282,7 @@ download_prism_data <- function( #' folder within \code{directory_to_save}. #' @export # nolint end -download_gridmet_data <- function( +download_gridmet <- function( variables = NULL, year_start = 2022, year_end = 2022, @@ -3388,7 +3388,7 @@ download_gridmet_data <- function( # nolint start #' Download TerraClimate data #' @description -#' The \code{download_terraclimate_data} function accesses and downloads climate and water balance data from the [University of California Merced Climatology Lab's TerraClimate dataset](https://www.climatologylab.org/terraclimate.html). +#' The \code{download_terraclimate} function accesses and downloads climate and water balance data from the [University of California Merced Climatology Lab's TerraClimate dataset](https://www.climatologylab.org/terraclimate.html). #' @param variables character(1). Variable(s) name(s). See [TerraClimate Direct Downloads](https://climate.northwestknowledge.net/TERRACLIMATE/index_directDownloads.php) #' for variable names and acronym codes. #' @param year_start integer(1). length of 4. Start of year range for @@ -3411,7 +3411,7 @@ download_gridmet_data <- function( #' folder within \code{directory_to_save}. #' @export # nolint end -download_terraclimate_data <- function( +download_terraclimate <- function( variables = NULL, year_start = 2022, year_end = 2022, diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 8fbf8e43..9c7e4b59 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -39,7 +39,7 @@ testthat::test_that("Error when one parameter is NULL.", { testthat::test_that("Errors when temporal ranges invalid.", { expect_error( - download_geos_data( + download_geos( date_start = "1900-01-01", collection = "aqc_tavg_1hr_g1440x721_v1", acknowledgement = TRUE, @@ -47,7 +47,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_aqs_data( + download_aqs( year_start = 1900, acknowledgement = TRUE, directory_to_save = testthat::test_path("..", "testdata/", ""), @@ -55,7 +55,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_narr_monolevel_data( + download_narr_monolevel( year_start = 1900, variables = "air.sfc", acknowledgement = TRUE, @@ -63,7 +63,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_narr_p_levels_data( + download_narr_p_levels( year_start = 1900, variables = "omega", acknowledgement = TRUE, @@ -71,7 +71,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_merra2_data( + download_merra2( date_start = "1900-01-01", collection = "inst1_2d_asm_Nx", directory_to_save = testthat::test_path("..", "testdata/", ""), @@ -79,7 +79,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_hms_data( + download_hms( date_start = "1900-01-01", directory_to_save = testthat::test_path("..", "testdata/", ""), directory_to_download = testthat::test_path("..", "testdata/", ""), @@ -87,7 +87,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_gridmet_data( + download_gridmet( year_start = 1900, variables = "Precipitation", acknowledgement = TRUE, @@ -95,7 +95,7 @@ testthat::test_that("Errors when temporal ranges invalid.", { ) ) expect_error( - download_terraclimate_data( + download_terraclimate( year_start = 1900, variables = "Wind Speed", acknowledgement = TRUE, @@ -460,7 +460,7 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { } }) -testthat::test_that("download_hms_data error for unzip and directory.", { +testthat::test_that("download_hms error for unzip and directory.", { testthat::expect_error( download_data( dataset_name = "hms", @@ -1225,7 +1225,7 @@ testthat::test_that("check_urls returns NULL undefined size.", { ) }) -testthat::test_that("download_hms_data LIVE run.", { +testthat::test_that("download_hms LIVE run.", { # function parameters date <- "2018-01-01" directory <- testthat::test_path("..", "testdata", "hms_live") @@ -1402,16 +1402,16 @@ testthat::test_that("terraclimate error with invalid variables", { -testthat::test_that("download_cropscape_data throws an error for invalid year", { +testthat::test_that("download_cropscape throws an error for invalid year", { # Set up test data invalid_year <- 1996 - testthat::expect_error(download_cropscape_data(year = 2020, source = "CMU")) + testthat::expect_error(download_cropscape(year = 2020, source = "CMU")) # Call the function and expect an error - testthat::expect_error(download_cropscape_data(year = invalid_year, source = "GMU")) - testthat::expect_error(download_cropscape_data(year = 2000, source = "USDA")) + testthat::expect_error(download_cropscape(year = invalid_year, source = "GMU")) + testthat::expect_error(download_cropscape(year = 2000, source = "USDA")) }) -testthat::test_that("download_cropscape_data generates correct download commands (GMU)", { +testthat::test_that("download_cropscape generates correct download commands (GMU)", { withr::local_package("httr") withr::local_package("stringr") # Set up test data @@ -1420,7 +1420,7 @@ testthat::test_that("download_cropscape_data generates correct download commands # Call the function testthat::expect_no_error( - download_cropscape_data( + download_cropscape( year = year, source = "GMU", directory_to_save = directory_to_save, @@ -1457,7 +1457,7 @@ testthat::test_that("download_cropscape_data generates correct download commands }) -test_that("download_cropscape_data generates correct download commands (USDA)", { +test_that("download_cropscape generates correct download commands (USDA)", { withr::local_package("httr") withr::local_package("stringr") # Set up test data @@ -1466,7 +1466,7 @@ test_that("download_cropscape_data generates correct download commands (USDA)", # Call the function testthat::expect_no_error( - download_cropscape_data( + download_cropscape( year = year, source = "USDA", directory_to_save = directory_to_save, @@ -1503,7 +1503,7 @@ test_that("download_cropscape_data generates correct download commands (USDA)", }) -testthat::test_that("download_prism_data downloads the correct data files", { +testthat::test_that("download_prism downloads the correct data files", { # Set up test data time <- seq(201005, 201012, by = 1) element <- c("ppt", "tmin", "tmax", "tmean", "tdmean", @@ -1522,7 +1522,7 @@ testthat::test_that("download_prism_data downloads the correct data files", { remove_command <- FALSE # Call the function - download_prism_data( + download_prism( time = time, element = element, data_type = data_type, @@ -1534,7 +1534,7 @@ testthat::test_that("download_prism_data downloads the correct data files", { ) testthat::expect_message( - download_prism_data( + download_prism( time = time, element = "ppt", data_type = "normals", @@ -1583,7 +1583,7 @@ testthat::test_that("download_prism_data downloads the correct data files", { remove_command <- FALSE # Call the function and expect an error - testthat::expect_error(download_prism_data( + testthat::expect_error(download_prism( time = time, element = element, data_type = data_type, @@ -1630,7 +1630,7 @@ testthat::test_that("list_stac_files returns a character vector of file links", }) -testthat::test_that("download_huc_data works", +testthat::test_that("download_huc works", { withr::local_package("httr") @@ -1641,7 +1641,7 @@ testthat::test_that("download_huc_data works", for (region in allregions) { for (type in alltypes) { testthat::expect_no_error( - download_huc_data( + download_huc( region, type, directory_to_save, acknowledgement = TRUE, @@ -1677,7 +1677,7 @@ testthat::test_that("download_huc_data works", } testthat::expect_error( - download_huc_data( + download_huc( "Lower48", "OceanCatchment", tempdir(), acknowledgement = TRUE, @@ -1703,7 +1703,7 @@ testthat::test_that( download <- FALSE testthat::expect_no_error( - download_olm_data( + download_olm( product = product, format = format, directory_to_save = directory_to_save, diff --git a/vignettes/download_functions.Rmd b/vignettes/download_functions.Rmd index 6ee91083..a69c9dd2 100644 --- a/vignettes/download_functions.Rmd +++ b/vignettes/download_functions.Rmd @@ -34,13 +34,13 @@ The `data_download()` function was developed to improve researchers' access to p ```{r, echo = FALSE} functions <- c( - "download_aqs_data", "download_ecoregion_data", - "download_geos_cf_data", "download_gmted_data", - "download_koppen_geiger_data", "download_merra2_data", - "download_narr_monolevel_data", "download_narr_p_levels_data", - "download_nlcd_data", "download_hms_data", - "download_sedac_groads_data", "download_sedac_population_data", - "download_modis_data" + "download_aqs", "download_ecoregion", + "download_geos_cf_data", "download_gmted", + "download_koppen_geiger", "download_merra2", + "download_narr_monolevel", "download_narr_p_levels", + "download_nlcd", "download_hms", + "download_sedac_groads", "download_sedac_population", + "download_modis" ) source <- c( "US EPA Air Data Pre-Generated Data Files", @@ -102,18 +102,18 @@ kable(functions_sources, ) ``` -It is important to note that `data_download()` calls a source-specific function based on the `dataset_name =` parameter. Using the source-specific function directly will return the exact same data (**if the parameters are the same**), but `data_download()` may be beneficial if using a `for` loop to download data from various sources. For example, `download_data(dataset_name = "hms", ...)` will return the same data as `download_hms_data(...)` assuming that `...` indicates the same parameters. +It is important to note that `data_download()` calls a source-specific function based on the `dataset_name =` parameter. Using the source-specific function directly will return the exact same data (**if the parameters are the same**), but `data_download()` may be beneficial if using a `for` loop to download data from various sources. For example, `download_data(dataset_name = "hms", ...)` will return the same data as `download_hms(...)` assuming that `...` indicates the same parameters. ### Parameters User-defined parameters differ based on the data source. Required parameters for each source can be checked with `names(formals())`. ```{r} -names(formals(download_hms_data)) -names(formals(download_narr_monolevel_data)) +names(formals(download_hms)) +names(formals(download_narr_monolevel)) ``` -The two functions have different required parameters because `download_hms_data()` uses a daily temporal resolution while `download_narr_monolevel_data()` uses yearly, but they share some common, standard parameters. +The two functions have different required parameters because `download_hms()` uses a daily temporal resolution while `download_narr_monolevel()` uses yearly, but they share some common, standard parameters. #### Standard parameters @@ -156,7 +156,7 @@ Additionally, the `dataset_name =` parameter must be specified when using `data_ ### Function Structure -Although each source-specific download function is unique, they all follow the same general structure. The following chunks of code have been **adopted** from `download_hms_data()` to demonstrate the functions' structure. +Although each source-specific download function is unique, they all follow the same general structure. The following chunks of code have been **adopted** from `download_hms()` to demonstrate the functions' structure. [1. Clean Parameters] @@ -196,7 +196,7 @@ date_sequence #### 2. Generate download URLs -The URL base and pattern are identified by manually inspecting the download link on the source-specific web page. `download_hms_data()` utilizes the year, month, date, and data format to generate the download url. +The URL base and pattern are identified by manually inspecting the download link on the source-specific web page. `download_hms()` utilizes the year, month, date, and data format to generate the download url. ```{r} # user defined parameters @@ -659,7 +659,7 @@ testthat::test_that( ``` -This test utilizes `expect_error()` because the `data_download()` wrapper function returns an error message if the underlying source-specific download function returns an error. If we directly used the `download_hms_data` function, we would expect and receive an error. +This test utilizes `expect_error()` because the `data_download()` wrapper function returns an error message if the underlying source-specific download function returns an error. If we directly used the `download_hms` function, we would expect and receive an error. ```{r} testthat::test_that( @@ -671,7 +671,7 @@ testthat::test_that( test_directory <- "../inst/extdata/" # test for error testthat::expect_error( - download_hms_data( + download_hms( date_start = test_start, date_end = test_end, data_format = "Shapefile", @@ -698,7 +698,7 @@ These unit tests are just two of many implemented on `download_data()` and the a With the function structure outlined and the unit tests in place, we can now perform a data download. To begin, check the parameters required by the source-specific data download function. ```{r} -names(formals(download_hms_data)) +names(formals(download_hms)) ``` Define the parameters. @@ -1055,8 +1055,8 @@ sedac_files ## Code Example -The following is the entire R code used to create `download_hms_data()`. +The following is the entire R code used to create `download_hms()`. ```{r} -download_hms_data +download_hms ``` From 867628e57ebc5d5b2374191709edc9eac4b28b97 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Fri, 26 Apr 2024 10:22:41 -0400 Subject: [PATCH 04/65] https://github.com/NIEHS/amadeus/issues/64 --- R/download.R | 475 +++++++++++------------ tests/testthat/test-download_functions.R | 114 +++--- 2 files changed, 291 insertions(+), 298 deletions(-) diff --git a/R/download.R b/R/download.R index aa88d4e4..a24bc2ea 100644 --- a/R/download.R +++ b/R/download.R @@ -428,9 +428,10 @@ download_ecoregion <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Hourly netCDF (.nc4) files will be stored in -#' \code{directory_to_save}. +#' @return NULL; Hourly netCDF (.nc4) files will be stored in a +#' collection-specific folder within \code{directory_to_save}. #' @export +# nolint start: cyclocomp download_geos <- function( collection = c( @@ -452,22 +453,19 @@ download_geos <- function( download_setup_dir(directory_to_save) directory_to_save <- download_sanitize_path(directory_to_save) #### 4. match collection - collection <- match.arg(collection) + collection <- match.arg(collection, several.ok = TRUE) #### 5. define date sequence date_sequence <- generate_date_sequence( date_start, date_end, sub_hyphen = TRUE ) - #### 6. define time sequence - time_sequence <- generate_time_sequence(collection) #### 7. define URL base base <- "https://portal.nccs.nasa.gov/datashare/gmao/geos-cf/v1/ana/" #### 8. initiate "..._wget_commands.txt" file commands_txt <- paste0( directory_to_save, - collection, - "_", + "geos_", date_start, "_", date_end, @@ -475,67 +473,71 @@ download_geos <- function( ) download_sink(commands_txt) #### 9. concatenate and print download commands to "..._wget_commands.txt" - for (d in seq_along(date_sequence)) { - date <- date_sequence[d] - year <- substr(date, 1, 4) - month <- substr(date, 5, 6) - day <- substr(date, 7, 8) - for (t in seq_along(time_sequence)) { - download_url_base <- paste0( - base, - "Y", - year, - "/M", - month, - "/D", - day, - "/" - ) - download_name <- paste0( - "GEOS-CF.v01.rpl.", - collection, - ".", - date, - "_", - time_sequence[t], - "z.nc4" - ) - download_url <- paste0( - download_url_base, - download_name - ) - if (t == 1) { - if (!(check_url_status(download_url))) { - sink() - file.remove(commands_txt) - stop(paste0( - "Invalid date returns HTTP code 404. ", - "Check `date_start` parameter.\n" - )) + for (c in seq_along(collection)) { + collection_loop <- collection[c] + download_folder <- paste0( + directory_to_save, + collection_loop, + "/" + ) + if (!file.exists(download_folder)) { + dir.create(download_folder) + } + for (d in seq_along(date_sequence)) { + date <- date_sequence[d] + year <- substr(date, 1, 4) + month <- substr(date, 5, 6) + day <- substr(date, 7, 8) + time_sequence <- generate_time_sequence(collection_loop) + for (t in seq_along(time_sequence)) { + download_url_base <- paste0( + base, + "Y", + year, + "/M", + month, + "/D", + day, + "/" + ) + download_name <- paste0( + "GEOS-CF.v01.rpl.", + collection_loop, + ".", + date, + "_", + time_sequence[t], + "z.nc4" + ) + download_url <- paste0( + download_url_base, + download_name + ) + if (t == 1) { + if (!(check_url_status(download_url))) { + sink() + file.remove(commands_txt) + stop(paste0( + "Invalid date returns HTTP code 404. ", + "Check `date_start` parameter.\n" + )) + } + } + download_folder_name <- paste0( + download_folder, + download_name + ) + download_command <- paste0( + "curl ", + download_url, + " -o ", + download_folder_name, + "\n" + ) + if (!file.exists(download_folder_name)) { + #### cat command only if file does not already exist + cat(download_command) } - } - download_folder <- paste0( - directory_to_save, - collection, - "/" - ) - download_folder_name <- paste0( - download_folder, - download_name - ) - if (!file.exists(download_folder)) { - dir.create(download_folder) - } - download_command <- paste0( - "curl ", - download_url, - " -o ", - download_folder_name, - "\n" - ) - if (!file.exists(download_folder_name)) { - #### cat command only if file does not already exist - cat(download_command) } } } @@ -557,6 +559,7 @@ download_geos <- function( remove = remove_command ) } +# nolint end: cyclocomp # nolint start #' Download elevation data @@ -727,10 +730,11 @@ download_gmted <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Daily netCDF (.nc4) files will be stored in -#' \code{directory_to_save}. +#' @return NULL; Daily netCDF (.nc4) files will be stored in a +#' collection-specific folder within \code{directory_to_save}. #' @export # nolint end +# nolint start: cyclocomp download_merra2 <- function( collection = c( "inst1_2d_asm_Nx", "inst1_2d_int_Nx", "inst1_2d_lfo_Nx", @@ -753,14 +757,14 @@ download_merra2 <- function( acknowledgement = FALSE, download = FALSE, remove_command = FALSE) { - #### 1. check for data download acknowledgement + #### check for data download acknowledgement download_permit(acknowledgement = acknowledgement) - #### 2. directory setup + #### directory setup download_setup_dir(directory_to_save) directory_to_save <- download_sanitize_path(directory_to_save) - #### 3. check for null parameters + #### check for null parameters check_for_null_parameters(mget(ls())) - #### 4. check if collection is recognized + #### check if collection is recognized identifiers <- c( "inst1_2d_asm_Nx M2I1NXASM 10.5067/3Z173KIE2TPD", "inst1_2d_int_Nx M2I1NXINT 10.5067/G0U6NGQ3BLE0", @@ -807,212 +811,207 @@ download_merra2 <- function( identifiers <- do.call(rbind, identifiers) identifiers_df <- as.data.frame(identifiers) colnames(identifiers_df) <- c("collection_id", "estd_name", "DOI") - if (!(collection %in% identifiers_df$collection_id)) { + if (!all(collection %in% identifiers_df$collection_id)) { print(identifiers_df) stop(paste0("Requested collection is not recognized.\n Please refer to the table above to find a proper collection.\n")) } - #### 5. define date sequence + #### define date sequence date_sequence <- generate_date_sequence( date_start, date_end, sub_hyphen = TRUE ) - #### 6. define year + month sequence + #### define year + month sequence yearmonth_sequence <- unique(substr(date_sequence, 1, 6)) - #### 7. define ESDT name and DOI - identifiers_df_requested <- subset(identifiers_df, - subset = - identifiers_df$collection_id == - collection - ) - esdt_name <- identifiers_df_requested[, 2] - cat(paste0( - "Collection: ", - collection, - " | ESDT Name: ", - esdt_name, - " | DOI: ", - identifiers_df_requested[, 3], - "\n" - )) - #### 8. define URL base - #### NOTE: sorted and defined manually according to - #### https://goldsmr4.gesdisc.eosdis.nasa.gov/data/MERRA2/ \& - #### https://goldsmr5.gesdisc.eosdis.nasa.gov/data/MERRA2/ - esdt_name_4 <- c( - "M2I1NXASM", "M2I1NXINT", "M2I1NXLFO", "M2I3NXGAS", - "M2SDNXSLV", "M2T1NXADG", "M2T1NXAER", "M2T1NXCHM", - "M2T1NXCSP", "M2T1NXFLX", "M2T1NXINT", "M2T1NXLFO", - "M2T1NXLND", "M2T1NXOCN", "M2T1NXRAD", "M2T1NXSLV", - "M2T3NXGLC" - ) - esdt_name_5 <- c( - "M2I3NPASM", "M2I3NVAER", "M2I3NVASM", "M2I3NVCHM", - "M2I3NVGAS", "M2I6NPANA", "M2I6NVANA", "M2T3NEMST", - "M2T3NENAV", "M2T3NETRB", "M2T3NPCLD", "M2T3NPMST", - "M2T3NPODT", "M2T3NPQDT", "M2T3NPRAD", "M2T3NPTDT", - "M2T3NPTRB", "M2T3NPUDT", "M2T3NVASM", "M2T3NVCLD", - "M2T3NVMST", "M2T3NVRAD" - ) - if (esdt_name %in% esdt_name_4) { - base <- "https://goldsmr4.gesdisc.eosdis.nasa.gov/data/MERRA2/" - } else if (esdt_name %in% esdt_name_5) { - base <- "https://goldsmr5.gesdisc.eosdis.nasa.gov/data/MERRA2/" - } - #### 9. identify download URLs - list_urls <- NULL - for (y in seq_along(yearmonth_sequence)) { - year <- substr(yearmonth_sequence[y], 1, 4) - month <- substr(yearmonth_sequence[y], 5, 6) - if (y == 1) { - base_url <- paste0( + #### initiate "..._wget_commands.txt" file + commands_txt <- paste0( + directory_to_save, + "merra2_", + date_start, + "_", + date_end, + "_wget_commands.txt" + ) + download_sink(commands_txt) + for (c in seq_along(collection)) { + collection_loop <- collection[c] + #### define ESDT name and DOI + identifiers_df_requested <- subset( + identifiers_df, + subset = identifiers_df$collection_id == collection_loop + ) + esdt_name <- identifiers_df_requested[, 2] + #### define URL base + #### NOTE: sorted and defined manually according to + #### https://goldsmr4.gesdisc.eosdis.nasa.gov/data/MERRA2/ \& + #### https://goldsmr5.gesdisc.eosdis.nasa.gov/data/MERRA2/ + esdt_name_4 <- c( + "M2I1NXASM", "M2I1NXINT", "M2I1NXLFO", "M2I3NXGAS", + "M2SDNXSLV", "M2T1NXADG", "M2T1NXAER", "M2T1NXCHM", + "M2T1NXCSP", "M2T1NXFLX", "M2T1NXINT", "M2T1NXLFO", + "M2T1NXLND", "M2T1NXOCN", "M2T1NXRAD", "M2T1NXSLV", + "M2T3NXGLC" + ) + esdt_name_5 <- c( + "M2I3NPASM", "M2I3NVAER", "M2I3NVASM", "M2I3NVCHM", + "M2I3NVGAS", "M2I6NPANA", "M2I6NVANA", "M2T3NEMST", + "M2T3NENAV", "M2T3NETRB", "M2T3NPCLD", "M2T3NPMST", + "M2T3NPODT", "M2T3NPQDT", "M2T3NPRAD", "M2T3NPTDT", + "M2T3NPTRB", "M2T3NPUDT", "M2T3NVASM", "M2T3NVCLD", + "M2T3NVMST", "M2T3NVRAD" + ) + if (esdt_name %in% esdt_name_4) { + base <- "https://goldsmr4.gesdisc.eosdis.nasa.gov/data/MERRA2/" + } else if (esdt_name %in% esdt_name_5) { + base <- "https://goldsmr5.gesdisc.eosdis.nasa.gov/data/MERRA2/" + } + #### identify download URLs + list_urls <- NULL + for (y in seq_along(yearmonth_sequence)) { + year <- substr(yearmonth_sequence[y], 1, 4) + month <- substr(yearmonth_sequence[y], 5, 6) + if (y == 1) { + base_url <- paste0( + base, + esdt_name, + ".5.12.4/", + year, + "/", + month, + "/" + ) + if (!(check_url_status(base_url))) { + stop(paste0( + "Invalid date returns HTTP code 404. ", + "Check `date_start` parameter.\n" + )) + } + } + list_urls_month <- system( + paste0( + "wget -q -nH -nd ", + "\"", + base, + esdt_name, + ".5.12.4/", + year, + "/", + month, + "/\"", + " -O - | grep .nc4 | awk -F'\"' ", + "'{print $4}'" + ), + intern = TRUE + ) + list_urls <- c(list_urls, list_urls_month) + } + #### match list_urls to date sequence + list_urls_date_sequence <- list_urls[substr(list_urls, 28, 35) %in% + date_sequence] + #### separate data and metadata + list_urls_data <- list_urls_date_sequence[grep( + "*.xml", + list_urls_date_sequence, + invert = TRUE + )] + list_urls_metadata <- list_urls_date_sequence[grep( + "*.xml", + list_urls_date_sequence, + invert = FALSE + )] + #### concatenate and print download commands to "..._wget_commands.txt" + for (l in seq_along(date_sequence)) { + year <- as.character(substr(date_sequence[l], 1, 4)) + month <- as.character(substr(date_sequence[l], 5, 6)) + download_url <- paste0( base, esdt_name, ".5.12.4/", year, "/", month, - "/" + "/", + list_urls_data[l] ) - if (!(check_url_status(base_url))) { - stop(paste0( - "Invalid date returns HTTP code 404. ", - "Check `date_start` parameter.\n" - )) + download_folder <- paste0( + directory_to_save, + collection_loop + ) + if (!file.exists(download_folder)) { + dir.create(download_folder) } - } - list_urls_month <- system( - paste0( - "wget -q -nH -nd ", - "\"", + download_name <- paste0( + download_folder, + "/", + list_urls_data[l] + ) + download_command <- paste0( + "wget ", + download_url, + " -O ", + download_name, + "\n" + ) + if (!file.exists(download_name)) { + #### cat command only if file does not already exist + cat(download_command) + } + download_url_metadata <- paste0( base, esdt_name, ".5.12.4/", year, "/", month, - "/\"", - " -O - | grep .nc4 | awk -F'\"' ", - "'{print $4}'" - ), - intern = TRUE - ) - list_urls <- c(list_urls, list_urls_month) - } - #### 10. match list_urls to date sequence - list_urls_date_sequence <- list_urls[substr(list_urls, 28, 35) %in% - date_sequence] - #### 11. separate data and metadata - list_urls_data <- list_urls_date_sequence[grep("*.xml", - list_urls_date_sequence, - invert = TRUE - )] - list_urls_metadata <- list_urls_date_sequence[grep("*.xml", - list_urls_date_sequence, - invert = FALSE - )] - #### 12. initiate "..._wget_commands.txt" file - commands_txt <- paste0( - directory_to_save, - collection, - "_", - date_start, - "_", - date_end, - "_wget_commands.txt" - ) - download_sink(commands_txt) - #### 13. concatenate and print download commands to "..._wget_commands.txt" - for (l in seq_along(date_sequence)) { - year <- as.character(substr(date_sequence[l], 1, 4)) - month <- as.character(substr(date_sequence[l], 5, 6)) - download_url <- paste0( - base, - esdt_name, - ".5.12.4/", - year, - "/", - month, - "/", - list_urls_data[l] - ) - download_folder <- paste0( - directory_to_save, - collection - ) - if (!file.exists(download_folder)) { - dir.create(download_folder) - } - download_name <- paste0( - download_folder, - "/", - list_urls_data[l] - ) - download_command <- paste0( - "wget ", - download_url, - " -O ", - download_name, - "\n" - ) - if (!file.exists(download_name)) { - #### cat command only if file does not already exist - cat(download_command) - } - download_url_metadata <- paste0( - base, - esdt_name, - ".5.12.4/", - year, - "/", - month, - "/", - list_urls_metadata[l] - ) - download_folder_metadata <- paste0( - directory_to_save, - collection, - "/metadata/" - ) - if (!file.exists(download_folder_metadata)) { - dir.create(download_folder_metadata) - } - download_name_metadata <- paste0( - download_folder_metadata, - list_urls_metadata[l] - ) - download_command_metadata <- paste0( - "wget ", - download_url_metadata, - " -O ", - download_name_metadata, - "\n" - ) - if (!file.exists(download_name)) { - #### cat command only if file does not already exist - cat(download_command_metadata) + "/", + list_urls_metadata[l] + ) + download_folder_metadata <- paste0( + directory_to_save, + collection_loop, + "/metadata/" + ) + if (!file.exists(download_folder_metadata)) { + dir.create(download_folder_metadata) + } + download_name_metadata <- paste0( + download_folder_metadata, + list_urls_metadata[l] + ) + download_command_metadata <- paste0( + "wget ", + download_url_metadata, + " -O ", + download_name_metadata, + "\n" + ) + if (!file.exists(download_name)) { + #### cat command only if file does not already exist + cat(download_command_metadata) + } } } - #### 14. finish "..._wget_commands.txt" + #### finish "..._wget_commands.txt" sink() - #### 15. build system command + #### build system command system_command <- paste0( ". ", commands_txt, "\n" ) - #### 16. download data + #### download data download_run( download = download, system_command = system_command ) - #### 17. Remove command file + #### Remove command file download_remove_command( commands_txt = commands_txt, remove = remove_command ) } +# nolint end: cyclocomp # nolint start #' Download meteorological data (monolevel) diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 9c7e4b59..87faa73d 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -201,36 +201,33 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { collections <- c("aqc_tavg_1hr_g1440x721_v1", "chm_inst_1hr_g1440x721_p23") directory_to_save <- testthat::test_path("..", "testdata/", "") - for (c in seq_along(collections)) { - # run download function - download_data(dataset_name = "geos", - date_start = date_start, - date_end = date_end, - collection = collections[c], - directory_to_save = directory_to_save, - acknowledgement = TRUE, - download = FALSE) - # define file path with commands - commands_path <- paste0(directory_to_save, - collections[c], - "_", - date_start, - "_", - date_end, - "_wget_commands.txt") - # import commands - commands <- read_commands(commands_path = commands_path) - # extract urls - urls <- extract_urls(commands = commands, position = 2) - # check HTTP URL status - url_status <- check_urls(urls = urls, size = 20L, method = "HEAD") - # implement unit tests - test_download_functions(directory_to_save = directory_to_save, - commands_path = commands_path, - url_status = url_status) - # remove file with commands after test - file.remove(commands_path) - } + # run download function + download_data(dataset_name = "geos", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save, + acknowledgement = TRUE, + download = FALSE) + # define file path with commands + commands_path <- paste0(directory_to_save, + "geos_", + date_start, + "_", + date_end, + "_wget_commands.txt") + # import commands + commands <- read_commands(commands_path = commands_path) + # extract urls + urls <- extract_urls(commands = commands, position = 2) + # check HTTP URL status + url_status <- check_urls(urls = urls, size = 20L, method = "HEAD") + # implement unit tests + test_download_functions(directory_to_save = directory_to_save, + commands_path = commands_path, + url_status = url_status) + # remove file with commands after test + file.remove(commands_path) }) testthat::test_that("GMTED download URLs have HTTP status 200.", { @@ -287,36 +284,33 @@ testthat::test_that("MERRA2 download URLs have HTTP status 200.", { date_end <- "2022-03-08" collections <- c("inst1_2d_asm_Nx", "inst3_3d_asm_Np") directory_to_save <- testthat::test_path("..", "testdata/", "") - for (c in seq_along(collections)) { - # run download function - download_data(dataset_name = "merra2", - date_start = date_start, - date_end = date_end, - collection = collections[c], - directory_to_save = directory_to_save, - acknowledgement = TRUE, - download = FALSE) - # define path with commands - commands_path <- paste0(directory_to_save, - collections[c], - "_", - date_start, - "_", - date_end, - "_wget_commands.txt") - # import commands - commands <- read_commands(commands_path = commands_path) - # extract urls - urls <- extract_urls(commands = commands, position = 2) - # check HTTP URL status - url_status <- check_urls(urls = urls, size = 3L, method = "HEAD") - # implement unit tests - test_download_functions(directory_to_save = directory_to_save, - commands_path = commands_path, - url_status = url_status) - # remove file with commands after test - file.remove(commands_path) - } + # run download function + download_data(dataset_name = "merra2", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save, + acknowledgement = TRUE, + download = FALSE) + # define path with commands + commands_path <- paste0(directory_to_save, + "merra2_", + date_start, + "_", + date_end, + "_wget_commands.txt") + # import commands + commands <- read_commands(commands_path = commands_path) + # extract urls + urls <- extract_urls(commands = commands, position = 2) + # check HTTP URL status + url_status <- check_urls(urls = urls, size = 3L, method = "HEAD") + # implement unit tests + test_download_functions(directory_to_save = directory_to_save, + commands_path = commands_path, + url_status = url_status) + # remove file with commands after test + file.remove(commands_path) }) testthat::test_that("MERRA2 returns message with unrecognized collection.", { From a9dcdf7f4c6f6cfea498ffc8e8824d28b0696ef8 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Fri, 26 Apr 2024 15:54:04 -0400 Subject: [PATCH 05/65] deprecate directory_to_download (download_hms) only --- R/download.R | 30 +++++++++++++++++------- R/download_auxiliary.R | 20 +++++++++++++++- tests/testthat/test-download_functions.R | 8 +++---- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/R/download.R b/R/download.R index a24bc2ea..434509e0 100644 --- a/R/download.R +++ b/R/download.R @@ -1757,8 +1757,11 @@ download_sedac_population <- function( #' @param directory_to_download character(1). Directory to download zip files #' from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if #' \code{data_format = "KML"}.) -#' @param directory_to_save character(1). Directory to save unzipped shapefiles -#' and KML files. +#' @param directory_to_save character(1). Directory to save data. If +#' `data_format = "Shapefile"`, two sub-directories will be created for the +#' downloaded zip files ("/zip_files") and the unzipped shapefiles +#' ("/data_files"). If `data_format = "KML"`, a single sub-directory +#' ("/data_files") will be created. #' @param acknowledgement logical(1). #' By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very @@ -1786,7 +1789,6 @@ download_hms <- function( data_format = "Shapefile", date_start = "2023-09-01", date_end = "2023-09-01", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -1798,10 +1800,21 @@ download_hms <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- directory_to_save + download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- download_sanitize_path( + paste0( + download_sanitize_path(directory_to_save), + "zip_files" + ) + ) + directory_to_save <- download_sanitize_path( + paste0( + download_sanitize_path(directory_to_save), + "data_files" + ) + ) + cat(c(directory_to_download, directory_to_save)) #### 4. check for unzip == FALSE && remove_zip == TRUE if (unzip == FALSE && remove_zip == TRUE) { stop(paste0( @@ -1819,7 +1832,7 @@ download_hms <- function( base <- "https://satepsanone.nesdis.noaa.gov/pub/FIRE/web/HMS/Smoke_Polygons/" #### 7. initiate "..._curl_commands.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "hms_smoke_", utils::head(date_sequence, n = 1), "_", @@ -1901,6 +1914,7 @@ download_hms <- function( ) #### 13. end if data_format == "KML" if (data_format == "KML") { + unlink(directory_to_download, recursive = TRUE) return(cat(paste0("KML files cannot be unzipped.\n"))) } #### 14. unzip downloaded zip files diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index 10c5cc41..6f4a487b 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -4,16 +4,34 @@ #' @description #' Create \code{directory} if it does not already exist. #' @param directory character(1) directory path +#' @param zip logical(1). Should sub-directories be created for zip files and +#' data files? #' @description If directory does not exist, the directory #' will be created. #' @returns NULL #' @keywords internal #' @export download_setup_dir <- - function(directory) { + function(directory, zip) { if (!dir.exists(directory)) { dir.create(directory, recursive = TRUE) } + if (zip) { + directory_zip <- paste0( + directory, + "zip_files/" + ) + if (!dir.exists(directory_zip)) { + dir.create(directory_zip, recursive = TRUE) + } + directory_data <- paste0( + directory, + "data_files/" + ) + if (!dir.exists(directory_data)) { + dir.create(directory_data, recursive = TRUE) + } + } } diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 87faa73d..b02079a7 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -416,8 +416,7 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { # function parameters date_start <- "2022-08-12" date_end <- "2022-09-21" - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "hms_temp") data_formats <- c("Shapefile", "KML") for (d in seq_along(data_formats)) { # run download function @@ -425,7 +424,6 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { date_start = date_start, date_end = date_end, data_format = data_formats[d], - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, @@ -433,7 +431,7 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { unzip = FALSE, remove_zip = FALSE) # define file path with commands - commands_path <- paste0(directory_to_download, + commands_path <- paste0(directory_to_save, "hms_smoke_", gsub("-", "", date_start), "_", @@ -451,6 +449,8 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary hms + unlink(directory_to_save, recursive = TRUE) } }) From 480d3ac934b33fb7a28a8f6c1a33958284a2b19a Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Mon, 29 Apr 2024 13:46:05 -0400 Subject: [PATCH 06/65] deprecate directory_to_download 2 --- R/download.R | 276 ++++++++++++----------- R/download_auxiliary.R | 24 +- tests/testthat/test-download_functions.R | 176 ++++++++++----- 3 files changed, 269 insertions(+), 207 deletions(-) diff --git a/R/download.R b/R/download.R index 434509e0..91ca62da 100644 --- a/R/download.R +++ b/R/download.R @@ -123,10 +123,9 @@ download_data <- #' Start year for downloading data. #' @param year_end integer(1). length of 4. #' End year for downloading data. -#' @param directory_to_download character(1). -#' Directory to download zip files from AQS data mart. -#' @param directory_to_save character(1). -#' Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped data files ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -151,7 +150,6 @@ download_aqs <- year_start = 2018, year_end = 2022, url_aqs_download = "https://aqs.epa.gov/aqsweb/airdata/", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -167,10 +165,10 @@ download_aqs <- #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. define year sequence year_sequence <- seq(year_start, year_end, 1) #### 5. build URLs @@ -219,7 +217,7 @@ download_aqs <- ] #### 7. initiate "..._curl_commands.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "aqs_", parameter_code, "_", @@ -256,6 +254,9 @@ download_aqs <- download_name = download_names[n] ) } + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } #### 13. remove command file download_remove_command( commands_txt = commands_txt, @@ -284,9 +285,9 @@ download_aqs <- #' 'extdata/cacert_gaftp_epa.pem' under the package installation path. #' @param certificate_url character(1). URL to certificate file. See notes for #' details. -#' @param directory_to_download character(1). Directory to download zip file -#' of Ecoregion level 3 shapefiles -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped data files ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -309,7 +310,6 @@ download_ecoregion <- function( package = "amadeus"), certificate_url = "http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -322,10 +322,10 @@ download_ecoregion <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_save) - download_setup_dir(directory_to_download) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. Check the presence of file ## This part is hard-coded as the original file appears to ## be a misnomer. May need to be modified accordingly in the future. @@ -365,7 +365,7 @@ download_ecoregion <- function( ) #### 8. initiate "..._curl_commands.txt" file commands_txt <- paste0( - directory_to_download, + directory_original, "us_eco_l3_state_boundaries_", Sys.Date(), "_wget_command.txt" @@ -404,6 +404,9 @@ download_ecoregion <- function( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } # nolint start @@ -571,9 +574,9 @@ download_geos <- function( #' `"Minimum Statistic"`, `"Mean Statistic"`, `"Maximum Statistic"`, and #' `"Standard Deviation Statistic"`. #' @param resolution character(1). Available resolutions include `"7.5 arc-seconds"`, `"15 arc-seconds"`, and `"30 arc-seconds"`. -#' @param directory_to_download character(1). Directory to download zip files -#' from Global Multi-resolution Terrain Elevation Data (GMTED2010). -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped data files ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -600,7 +603,6 @@ download_gmted <- function( "Standard Deviation Statistic" ), resolution = c("7.5 arc-seconds", "15 arc-seconds", "30 arc-seconds"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -613,10 +615,10 @@ download_gmted <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. check for valid statistic statistic <- match.arg(statistic) #### 5. check for valid resolution @@ -663,7 +665,7 @@ download_gmted <- function( ) #### 12. initiate "..._curl_commands.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "gmted_", gsub(" ", "", statistic), "_", @@ -707,6 +709,9 @@ download_gmted <- function( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } # nolint start @@ -1287,9 +1292,9 @@ download_narr_p_levels <- function( #' include `2001`, `2004`, `2006`, `2008`, `2011`, `2013`, `2016`, #' `2019`, and `2021`. #' Available years for Alaska include `2001`, `2011`, and `2016`. -#' @param directory_to_download character(1). Directory to download zip files -#' from National Land Cover Database Science Research Products. -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped shapefiles ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -1303,13 +1308,13 @@ download_narr_p_levels <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @returns NULL; Zip file will be stored in \code{directory_to_download}, and -#' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +#' @return NULL; Zip files and unzipped data files will be stored in +#' "/zip_files" and "/data_files" sub-directories, respectively, within the +#' \code{directory_to_save}. #' @export download_nlcd <- function( collection = "Coterminous United States", year = 2021, - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -1322,10 +1327,10 @@ download_nlcd <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. check for valid years valid_years <- c(2001, 2004, 2006, 2008, 2011, 2013, 2016, 2019, 2021) if (!(year %in% valid_years)) { @@ -1381,7 +1386,7 @@ download_nlcd <- function( ) #### 11. initiate "..._curl_command.txt" commands_txt <- paste0( - directory_to_download, + directory_original, tolower(collection_code), Sys.Date(), "_curl_command.txt" @@ -1416,6 +1421,9 @@ download_nlcd <- function( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } #### 18. remove command text download_remove_command( commands_txt = commands_txt, @@ -1432,9 +1440,9 @@ download_nlcd <- function( #' `"Africa"`, `"Asia"`, `"Europe"`, `"Americas"`, `"Oceania East"`, and `"Oceania West"`. #' @param data_format character(1). Data can be downloaded as `"Shapefile"` or #' `"Geodatabase"`. (Only `"Geodatabase"` available for `"Global"` region). -#' @param directory_to_download character(1). Directory to download zip files -#' from NASA Global Roads Open Access Data Set. -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped shapefiles ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -1448,14 +1456,13 @@ download_nlcd <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @returns NULL; Zip file will be stored in \code{directory_to_download}, and -#' selected Shapefile (.shp) or Geodatabase (.gdb) files will be stored in +#' @return NULL; Zip files and unzipped data files will be stored in +#' "/zip_files" and "/data_files" sub-directories, respectively, within the #' \code{directory_to_save}. #' @export download_sedac_groads <- function( data_region = c("Americas", "Global", "Africa", "Asia", "Europe", "Oceania East", "Oceania West"), data_format = c("Shapefile", "Geodatabase"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -1469,10 +1476,10 @@ download_sedac_groads <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. check if region is valid data_format <- match.arg(data_format) data_region <- match.arg(data_region) @@ -1521,7 +1528,7 @@ download_sedac_groads <- function( ) #### 11. initiate "..._curl_commands.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "sedac_groads_", gsub(" ", "_", region), "_", @@ -1563,6 +1570,9 @@ download_sedac_groads <- function( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } # nolint start @@ -1577,9 +1587,9 @@ download_sedac_groads <- function( #' `"ASCII"` or `"GeoTIFF"`. "all" years is downloaded as `"netCDF"`. #' @param year character(1). Available years are `2000`, `2005`, `2010`, `2015`, and #' `2020`, or `"all"` for all years. -#' @param directory_to_download character(1). Directory to download zip files -#' from NASA UN WPP-Adjusted Population Density, v4.11. -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped shapefiles ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -1594,14 +1604,14 @@ download_sedac_groads <- function( #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song # nolint end -#' @returns NULL; Zip file will be stored in \code{directory_to_download}, and -#' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +#' @return NULL; Zip files and unzipped data files will be stored in +#' "/zip_files" and "/data_files" sub-directories, respectively, within the +#' \code{directory_to_save}. #' @export download_sedac_population <- function( data_resolution = "60 minute", data_format = c("GeoTIFF", "ASCII", "netCDF"), year = "2020", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -1614,10 +1624,10 @@ download_sedac_population <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. define URL base base <- paste0("https://sedac.ciesin.columbia.edu/downloads/data/gpw-v4/") #### 5. define year @@ -1697,7 +1707,7 @@ download_sedac_population <- function( ) #### 12. initiate "..._curl_command.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "sedac_population_", year, "_", @@ -1741,6 +1751,9 @@ download_sedac_population <- function( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } # nolint start @@ -1780,8 +1793,8 @@ download_sedac_population <- function( #' @importFrom utils head #' @importFrom utils tail #' @author Mitchell Manware, Insang Song -#' @returns NULL; Zip file will be stored in \code{directory_to_download}, and -#' Shapefiles (.shp) or KML files (.kml) will be stored in +#' @return NULL; Zip files and unzipped data files will be stored in +#' "/zip_files" and "/data_files" sub-directories, respectively, within the #' \code{directory_to_save}. #' @export # nolint start: cyclocomp @@ -1800,21 +1813,10 @@ download_hms <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - directory_original <- directory_to_save - download_setup_dir(directory_original, zip = TRUE) - directory_to_download <- download_sanitize_path( - paste0( - download_sanitize_path(directory_to_save), - "zip_files" - ) - ) - directory_to_save <- download_sanitize_path( - paste0( - download_sanitize_path(directory_to_save), - "data_files" - ) - ) - cat(c(directory_to_download, directory_to_save)) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. check for unzip == FALSE && remove_zip == TRUE if (unzip == FALSE && remove_zip == TRUE) { stop(paste0( @@ -1845,10 +1847,12 @@ download_hms <- function( for (f in seq_along(date_sequence)) { year <- substr(date_sequence[f], 1, 4) month <- substr(date_sequence[f], 5, 6) - if (data_format == "Shapefile") { + if (tolower(data_format) == "shapefile") { + data_format <- "Shapefile" suffix <- ".zip" directory_to_cat <- directory_to_download - } else if (data_format == "KML") { + } else if (tolower(data_format) == "kml") { + data_format <- "KML" suffix <- ".kml" directory_to_cat <- directory_to_save } @@ -1930,6 +1934,9 @@ download_hms <- function( remove = remove_zip, download_name = download_names ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } # nolint end: cyclocomp @@ -1946,10 +1953,9 @@ download_hms <- function( #' @param time_period character(1). Available times are `"Present"` (1980-2016) #' and `"Future"` (2071-2100). ("Future" classifications are based on scenario #' RCP8.5). -#' @param directory_to_download character(1). Directory to download zip files -#' from Present and future Köppen-Geiger climate classification maps at 1-km -#' resolution. -#' @param directory_to_save character(1). Directory to decompress zip files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped shapefiles ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -1963,13 +1969,13 @@ download_hms <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @returns NULL; Zip file will be stored in \code{directory_to_download}, and -#' selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +#' @return NULL; Zip files and unzipped data files will be stored in +#' "/zip_files" and "/data_files" sub-directories, respectively, within the +#' \code{directory_to_save}. #' @export download_koppen_geiger <- function( data_resolution = c("0.0083", "0.083", "0.5"), time_period = c("Present", "Future"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -1981,10 +1987,10 @@ download_koppen_geiger <- function( #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup - download_setup_dir(directory_to_download) - download_setup_dir(directory_to_save) - directory_to_download <- download_sanitize_path(directory_to_download) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 4. check for data resolution data_resolution <- match.arg(data_resolution) #### 5. check for valid time period @@ -2014,7 +2020,7 @@ download_koppen_geiger <- function( ) #### 11. initiate "..._wget_commands.txt" commands_txt <- paste0( - directory_to_download, + directory_original, "koppen_geiger_", time_period, "_", @@ -2041,44 +2047,6 @@ download_koppen_geiger <- function( download = download, system_command = system_command ) - - if (unzip) { - #### 16. remove unwanted files - wanted_names <- list.files( - path = directory_to_save, - pattern = - sprintf("(Beck_KG_*.*_%s_*.*%s*.*tif$|legend.txt)", - time_period, data_resolution), - full.names = TRUE - ) - all_names <- list.files( - path = directory_to_save, - full.names = TRUE - ) - unwanted_names <- all_names[!all_names %in% wanted_names] - # unwanted_names <- as.vector(c( - # unwanted_names, - # paste0( - # directory_to_save, - # "KoppenGeiger.m" - # ) - # )) - # tif <- paste0( - # directory_to_save, - # "/Beck_KG_V1_", - # period, - # "_", - # data_resolution, - # ".tif" - # ) - # unwanted_names <- unwanted_names[grep( - # pattern = tif, - # unwanted_names, - # invert = TRUE - # )] - file.remove(unwanted_names) - } - #### 17. Remove command file download_remove_command( commands_txt = commands_txt, @@ -2090,11 +2058,37 @@ download_koppen_geiger <- function( directory_to_unzip = directory_to_save, unzip = unzip ) + if (unzip) { + #### 16. remove unwanted files + wanted_names <- grep( + "conf", + list.files( + path = directory_to_save, + pattern = sprintf( + "Beck_KG_.*_%s_.*%s.*\\.tif$|legend\\.txt", + period, + data_resolution + ), + full.names = TRUE + ), + invert = TRUE, + value = TRUE + ) + all_names <- list.files( + path = directory_to_save, + full.names = TRUE + ) + unwanted_names <- all_names[!all_names %in% wanted_names] + file.remove(unwanted_names) + } #### 19. remove zip files download_remove_zips( remove = remove_zip, download_name = download_name ) + if (remove_zip) { + unlink(directory_to_download, recursive = TRUE) + } } @@ -2484,7 +2478,8 @@ download_tri <- function( #### 2. directory setup download_setup_dir(directory_to_save) directory_to_save <- download_sanitize_path(directory_to_save) - + + #### 3. define measurement data paths url_download <- "https://data.epa.gov/efservice/downloads/tri/mv_tri_basic_download/" @@ -2553,7 +2548,9 @@ download_tri <- function( #' details. #' @param year_target Available years of NEI data. #' Default is \code{c(2017L, 2020L)}. -#' @param directory_to_save character(1). Directory to download files. +#' @param directory_to_save character(1). Directory to save data. Two +#' sub-directories will be created for the downloaded zip files ("/zip_files") +#' and the unzipped data files ("/data_files"). #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -2594,8 +2591,10 @@ download_nei <- function( #### 1. check for data download acknowledgement download_permit(acknowledgement = acknowledgement) #### 2. directory setup - download_setup_dir(directory_to_save) - directory_to_save <- download_sanitize_path(directory_to_save) + directory_original <- download_sanitize_path(directory_to_save) + directories <- download_setup_dir(directory_original, zip = TRUE) + directory_to_download <- directories[1] + directory_to_save <- directories[2] #### 5. define download URL download_epa_certificate( @@ -2616,7 +2615,7 @@ download_nei <- function( download_names_file <- c("2017neiApr_onroad_byregions.zip", "2020nei_onroad_byregion.zip") - download_names <- paste0(directory_to_save, download_names_file) + download_names <- paste0(directory_to_download, download_names_file) #### filter commands to non-existing files download_urls <- download_urls[ which( @@ -2635,7 +2634,7 @@ download_nei <- function( #### 5. initiate "..._curl_commands.txt" commands_txt <- paste0( - directory_to_save, + directory_original, "NEI_AADT_", paste(year_target, collapse = "-"), "_", @@ -2662,7 +2661,10 @@ download_nei <- function( # as duplicate file names are across multiple zip files if (download) { if (unzip) { - dir_unzip <- sub(".zip", "", download_names) + dir_unzip <- paste0( + directory_to_save, + sub(".zip", "", download_names_file) + ) for (fn in seq_along(dir_unzip)) { utils::unzip(zipfile = download_names[fn], exdir = dir_unzip[fn]) } diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index 6f4a487b..ba425465 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -5,32 +5,38 @@ #' Create \code{directory} if it does not already exist. #' @param directory character(1) directory path #' @param zip logical(1). Should sub-directories be created for zip files and -#' data files? +#' data files? If `TRUE`, a vector of sub-directoy names will be returned. #' @description If directory does not exist, the directory #' will be created. -#' @returns NULL +#' @returns NULL; if `zip = TRUE` a vector of directories for zip files and +#' data files #' @keywords internal #' @export download_setup_dir <- - function(directory, zip) { + function(directory, zip = FALSE) { if (!dir.exists(directory)) { dir.create(directory, recursive = TRUE) } if (zip) { - directory_zip <- paste0( - directory, - "zip_files/" + directory_zip <- download_sanitize_path( + paste0( + download_sanitize_path(directory), + "zip_files" + ) ) if (!dir.exists(directory_zip)) { dir.create(directory_zip, recursive = TRUE) } - directory_data <- paste0( - directory, - "data_files/" + directory_data <- download_sanitize_path( + paste0( + download_sanitize_path(directory), + "data_files" + ) ) if (!dir.exists(directory_data)) { dir.create(directory_data, recursive = TRUE) } + return(c(directory_zip, directory_data)) } } diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index b02079a7..76097c15 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -112,23 +112,29 @@ testthat::test_that("EPA AQS download URLs have HTTP status 200.", { year_end <- 2022 resolution_temporal <- "daily" parameter_code <- 88101 - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "aqs_temp") # run download function download_data(dataset_name = "aqs", year_start = year_start, year_end = year_end, directory_to_save = directory_to_save, - directory_to_download = directory_to_download, acknowledgement = TRUE, unzip = FALSE, remove_zip = FALSE, download = FALSE, remove_command = FALSE) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands commands_path <- paste0( - directory_to_download, + download_sanitize_path(directory_to_save), "aqs_", parameter_code, "_", @@ -148,6 +154,8 @@ testthat::test_that("EPA AQS download URLs have HTTP status 200.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary aqs + unlink(directory_to_save, recursive = TRUE) }) @@ -155,23 +163,29 @@ testthat::test_that("Ecoregion download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") # function parameters - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "eco_temp") certificate <- system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus") # run download function download_data(dataset_name = "ecoregion", directory_to_save = directory_to_save, - directory_to_download = directory_to_download, acknowledgement = TRUE, unzip = FALSE, remove_zip = FALSE, download = FALSE, remove_command = FALSE, epa_certificate_path = certificate) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands commands_path <- paste0( - directory_to_download, + download_sanitize_path(directory_to_save), "us_eco_l3_state_boundaries_", Sys.Date(), "_wget_command.txt" @@ -190,6 +204,8 @@ testthat::test_that("Ecoregion download URLs have HTTP status 200.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary eco + unlink(directory_to_save, recursive = TRUE) }) testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { @@ -234,26 +250,29 @@ testthat::test_that("GMTED download URLs have HTTP status 200.", { withr::local_package("httr") # function parameters statistics <- c("Breakline Emphasis", - # "Systematic Subsample", - # "Median Statistic", "Minimum Statistic", - # "Mean Statistic", "Maximum Statistic", "Standard Deviation Statistic") resolution <- "7.5 arc-seconds" - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "gmted_temp") for (s in seq_along(statistics)) { # run download function download_data(dataset_name = "gmted", statistic = statistics[s], resolution = resolution, - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, unzip = FALSE, remove_zip = FALSE, download = FALSE) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands - commands_path <- paste0(directory_to_download, + commands_path <- paste0(download_sanitize_path(directory_to_save), "gmted_", gsub(" ", "", statistics[s]), "_", @@ -273,6 +292,8 @@ testthat::test_that("GMTED download URLs have HTTP status 200.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary gmted + unlink(directory_to_save, recursive = TRUE) } }) @@ -431,12 +452,20 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { unzip = FALSE, remove_zip = FALSE) # define file path with commands - commands_path <- paste0(directory_to_save, + commands_path <- paste0(download_sanitize_path(directory_to_save), "hms_smoke_", gsub("-", "", date_start), "_", gsub("-", "", date_end), "_curl_commands.txt") + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # import commands commands <- read_commands(commands_path = commands_path) # extract urls @@ -460,7 +489,6 @@ testthat::test_that("download_hms error for unzip and directory.", { dataset_name = "hms", acknowledgement = TRUE, directory_to_save = testthat::test_path("..", "testdata/", ""), - directory_to_download = testthat::test_path("..", "testdata/", ""), unzip = FALSE, remove_zip = TRUE ) @@ -474,14 +502,12 @@ testthat::test_that("NLCD download URLs have HTTP status 200.", { years <- c(2021, 2019, 2016) collections <- c(rep("Coterminous United States", 2), "Alaska") collection_codes <- c(rep("l48", 2), "ak") - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "nlcd_temp") # run download function for (y in seq_along(years)) { download_data(dataset_name = "nlcd", year = years[y], collection = collections[y], - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, @@ -489,7 +515,7 @@ testthat::test_that("NLCD download URLs have HTTP status 200.", { unzip = FALSE, remove_zip = FALSE) # define file path with commands - commands_path <- paste0(directory_to_download, + commands_path <- paste0(download_sanitize_path(directory_to_save), "nlcd_", years[y], "_land_cover_", @@ -497,6 +523,14 @@ testthat::test_that("NLCD download URLs have HTTP status 200.", { "_", Sys.Date(), "_curl_command.txt") + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # import commands commands <- read_commands(commands_path = commands_path) # extract urls @@ -504,18 +538,18 @@ testthat::test_that("NLCD download URLs have HTTP status 200.", { # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "HEAD") # implement unit tests - test_download_functions(directory_to_download = directory_to_download, - directory_to_save = directory_to_save, + test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary nlcd + unlink(directory_to_save, recursive = TRUE) } testthat::expect_error( download_data(dataset_name = "nlcd", year = 2000, collection = "Coterminous United States", - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, @@ -523,7 +557,6 @@ testthat::test_that("NLCD download URLs have HTTP status 200.", { unzip = FALSE, remove_zip = FALSE) ) - }) testthat::test_that("SEDAC groads download URLs have HTTP status 200.", { @@ -532,8 +565,7 @@ testthat::test_that("SEDAC groads download URLs have HTTP status 200.", { # function parameters data_regions <- c("Americas", "Global") data_formats <- c("Geodatabase", "Shapefile") - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "groad_temp") # run download function for (r in seq_along(data_regions)) { data_region <- data_regions[r] @@ -543,13 +575,20 @@ testthat::test_that("SEDAC groads download URLs have HTTP status 200.", { acknowledgement = TRUE, data_format = data_formats[f], data_region = data_region, - directory_to_download = directory_to_download, download = FALSE, unzip = FALSE, remove_zip = FALSE, remove_command = FALSE) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands - commands_path <- paste0(directory_to_download, + commands_path <- paste0(download_sanitize_path(directory_to_save), "sedac_groads_", gsub(" ", "_", tolower(data_region)), "_", @@ -562,12 +601,13 @@ testthat::test_that("SEDAC groads download URLs have HTTP status 200.", { # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "GET") # implement unit tests - test_download_functions(directory_to_download = directory_to_download, - directory_to_save = directory_to_save, + test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary groads + unlink(directory_to_save, recursive = TRUE) } } @@ -575,7 +615,6 @@ testthat::test_that("SEDAC groads download URLs have HTTP status 200.", { download_data(dataset_name = "sedac_groads", data_format = "Shapefile", data_region = "Global", - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, @@ -593,8 +632,7 @@ testthat::test_that("SEDAC population download URLs have HTTP status 200.", { data_formats <- c("GeoTIFF") data_resolutions <- cbind(c("30 second"), c("30_sec")) - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "pop_temp") for (f in seq_along(data_formats)) { data_format <- data_formats[f] for (y in seq_along(years)) { @@ -605,13 +643,20 @@ testthat::test_that("SEDAC population download URLs have HTTP status 200.", { year = year, data_format = data_format, data_resolution = data_resolutions[r, 1], - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, unzip = FALSE, remove_zip = FALSE, remove_command = FALSE) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands if (year == "all") { year <- "totpop" @@ -623,7 +668,7 @@ testthat::test_that("SEDAC population download URLs have HTTP status 200.", { } else { resolution <- data_resolutions[r, 2] } - commands_path <- paste0(directory_to_download, + commands_path <- paste0(download_sanitize_path(directory_to_save), "sedac_population_", year, "_", @@ -638,12 +683,13 @@ testthat::test_that("SEDAC population download URLs have HTTP status 200.", { # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "GET") # implement unit tests - test_download_functions(directory_to_download = directory_to_download, - directory_to_save = directory_to_save, + test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary groads + unlink(directory_to_save, recursive = TRUE) } } } @@ -691,6 +737,8 @@ testthat::test_that("SEDAC population data types are coerced.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove file with commands after test + unlink(directory_to_save, recursive = TRUE) } }) @@ -700,8 +748,7 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { # function parameters time_periods <- c("Present", "Future") data_resolutions <- c("0.0083") - directory_to_download <- testthat::test_path("..", "testdata/", "") - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "kop_temp") # run download function for (p in seq_along(time_periods)) { time_period <- time_periods[p] @@ -709,7 +756,6 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { download_data(dataset_name = "koppen", time_period = time_period, data_resolution = data_resolutions[d], - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, unzip = FALSE, @@ -717,7 +763,7 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { download = FALSE, remove_command = FALSE) # define file path with commands - commands_path <- paste0(directory_to_download, + commands_path <- paste0(download_sanitize_path(directory_to_save), "koppen_geiger_", time_period, "_", @@ -727,6 +773,14 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { "_", Sys.Date(), "_wget_command.txt") + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # import commands commands <- read_commands(commands_path = commands_path) # extract urls @@ -734,12 +788,13 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "GET") # implement unit tests - test_download_functions(directory_to_download = directory_to_download, - directory_to_save = directory_to_save, + test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary kop + unlink(directory_to_save, recursive = TRUE) } } }) @@ -1058,7 +1113,7 @@ testthat::test_that("EPA NEI (AADT) download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") # function parameters - directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save <- testthat::test_path("..", "testdata", "nei_temp") certificate <- system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus") # run download function @@ -1071,9 +1126,17 @@ testthat::test_that("EPA NEI (AADT) download URLs have HTTP status 200.", { remove_command = FALSE, epa_certificate_path = certificate ) + # expect sub-directories to be created + testthat::expect_true( + length( + list.files( + directory_to_save, include.dirs = TRUE + ) + ) == 3 + ) # define file path with commands commands_path <- paste0( - directory_to_save, + download_sanitize_path(directory_to_save), "NEI_AADT_", paste(year_target, collapse = "-"), "_", @@ -1095,6 +1158,8 @@ testthat::test_that("EPA NEI (AADT) download URLs have HTTP status 200.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) + # remove temporary nei + unlink(directory_to_save, recursive = TRUE) }) testthat::test_that("Test error cases in EPA gaftp sources 1", { @@ -1223,39 +1288,28 @@ testthat::test_that("download_hms LIVE run.", { # function parameters date <- "2018-01-01" directory <- testthat::test_path("..", "testdata", "hms_live") - # create file to be deleted - dir.create(directory) - file.create( - paste0( - directory, - "/hms_smoke_20180101_20180101_curl_commands.txt" - ) - ) # run download function download_data( dataset_name = "hms", date_start = date, date_end = date, directory_to_save = directory, - directory_to_download = directory, acknowledgement = TRUE, download = TRUE, unzip = TRUE, - remove_zip = TRUE, + remove_zip = FALSE, remove_command = FALSE ) + Sys.sleep(1.5) testthat::expect_true( - length(list.files(directory)) == 5 + length(list.files(directory, recursive = TRUE, include.dirs = TRUE)) == 8 ) commands <- list.files(directory, pattern = ".txt", full.names = TRUE) testthat::expect_true( file.exists(commands) ) - Sys.sleep(1.5) # remove directory - files <- list.files(directory, full.names = TRUE) - sapply(files, file.remove) - file.remove(directory) + unlink(directory, recursive = TRUE) }) testthat::test_that("gridmet download URLs have HTTP status 200.", { From 8b87dcc7761d754d9bcc734b943da2bf6833ee4f Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 29 Apr 2024 22:23:30 -0400 Subject: [PATCH 07/65] 0.1.8 dev - calc_nlcd: locs can be sf (actually, anything being able to be converted to vect) - process_gmted: added documentation - download_data: added "ecoregions" --- DESCRIPTION | 2 +- R/calculate_covariates.R | 7 +++- R/download.R | 37 +++++++++--------- R/process.R | 13 +++++-- man/calc_covariates.Rd | 2 +- man/download_data.Rd | 44 +++++++++++----------- man/process_covariates.Rd | 4 +- man/process_gmted.Rd | 8 +++- man/process_nei.Rd | 2 +- tests/testthat/test-calculate_covariates.R | 9 ++++- 10 files changed, 74 insertions(+), 54 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a417bb25..636da352 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: amadeus Title: AMADEUS: A Machine for Data, Environments, and User Setup for common environmental and climate health datasets -Version: 0.1.7 +Version: 0.1.8 Authors@R: c( person("Kyle", "Messier", , "kyle.messier@nih.gov", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-9508-9623")), person("Mitchell", "Manware", role = c("aut", "ctb"), comment = c(ORCID = "0009-0003-6440-6106")), diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 93ad9bb9..908b2eb3 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -19,7 +19,7 @@ #' @note `covariate` argument value is converted to lowercase. #' @seealso #' - [`calc_modis_par`]: `"modis"`, `"MODIS"` -#' - [`calc_koppen_geiger`]: `"koppen-geiger"`, `"koeppen-geiger"`, `"koppen"`, +#' - [`calc_koppen_geiger`]: `"koppen-geiger"`, `"koeppen-geiger"`, `"koppen"` #' - [`calc_ecoregion`]: `"ecoregion"`, `"ecoregions"` #' - [`calc_temporal_dummies`]: `"dummies"` #' - [`calc_hms`]: `"hms"`, `"noaa"`, `"smoke"` @@ -255,7 +255,10 @@ calc_nlcd <- function(from, stop("radius has not a likely value.") } if (!methods::is(locs, "SpatVector")) { - stop("locs is not a terra::SpatVector.") + message("locs is not a terra::SpatVector.") + locs <- tryCatch(terra::vect(locs), error = function(e) { + stop("Failed to locs to a terra::SpatVector.") + }) } if (!methods::is(from, "SpatRaster")) { stop("from is not a SpatRaster.") diff --git a/R/download.R b/R/download.R index 141264cb..1a541a5c 100644 --- a/R/download.R +++ b/R/download.R @@ -17,28 +17,28 @@ #' @seealso #' For details of each download function per dataset, #' Please refer to: -#' * \link{download_aqs_data}: "aqs", "AQS" -#' * \link{download_ecoregion_data}: "ecoregion" -#' * \link{download_geos_data}: "geos" -#' * \link{download_gmted_data}: "gmted", "GMTED" -#' * \link{download_koppen_geiger_data}: "koppen", "koppengeiger" -#' * \link{download_merra2_data}: "merra2", "merra", "MERRA", "MERRA2" -#' * \link{download_narr_monolevel_data}: "narr_monolevel", "monolevel" -#' * \link{download_narr_p_levels_data}: "narr_p_levels", "p_levels", "plevels" -#' * \link{download_nlcd_data}: "nlcd", "NLCD" -#' * \link{download_hms_data}: "noaa", "smoke", "hms" -#' * \link{download_sedac_groads_data}: "sedac_groads", "groads" -#' * \link{download_sedac_population_data}: "sedac_population", "population" -#' * \link{download_modis_data}: "modis", "MODIS" -#' * \link{download_tri_data}: "tri", "TRI" -#' * \link{download_nei_data}: "nei", "NEI" -#' * \link{download_gridmet_data}: "gridMET", "gridmet" -#' * \link{download_terraclimate_data}: "TerraClimate", "terraclimate" +#' * \link{`download_aqs_data`}: `"aqs"`, `"AQS"` +#' * \link{`download_ecoregion_data`}: `"ecoregions"`, `"ecoregion"` +#' * \link{`download_geos_data`}: `"geos"` +#' * \link{`download_gmted_data`}: `"gmted"`, `"GMTED"` +#' * \link{`download_koppen_geiger_data`}: `"koppen"`, `"koppengeiger"` +#' * \link{`download_merra2_data`}: "merra2", `"merra"`, `"MERRA"`, `"MERRA2"` +#' * \link{`download_narr_monolevel_data`}: `"narr_monolevel"`, `"monolevel"` +#' * \link{`download_narr_p_levels_data`}: `"narr_p_levels"`, `"p_levels"`, `"plevels"` +#' * \link{`download_nlcd_data`}: `"nlcd"`, `"NLCD"` +#' * \link{`download_hms_data`}: `"noaa"`, `"smoke"`, `"hms"` +#' * \link{`download_sedac_groads_data`}: `"sedac_groads"`, `"groads"` +#' * \link{`download_sedac_population_data`}: `"sedac_population"`, `"population"` +#' * \link{`download_modis_data`}: `"modis"`, `"MODIS"` +#' * \link{`download_tri_data`}: `"tri"`, `"TRI"` +#' * \link{`download_nei_data`}: `"nei"`, `"NEI"` +#' * \link{`download_gridmet_data`}: `"gridMET"`, `"gridmet"` +#' * \link{`download_terraclimate_data`}: `"TerraClimate"`, `"terraclimate"` #' @returns NULL #' @export download_data <- function( - dataset_name = c("aqs", "ecoregion", "geos", "gmted", "koppen", + dataset_name = c("aqs", "ecoregion", "ecoregions", "geos", "gmted", "koppen", "koppengeiger", "merra2", "merra", "narr_monolevel", "modis", "narr_p_levels", "nlcd", "noaa", "sedac_groads", "sedac_population", "groads", "population", "plevels", @@ -57,6 +57,7 @@ download_data <- what_to_run <- switch(dataset_name, aqs = download_aqs_data, ecoregion = download_ecoregion_data, + ecoregions = download_ecoregion_data, geos = download_geos_data, gmted = download_gmted_data, koppen = download_koppen_geiger_data, diff --git a/R/process.R b/R/process.R index 324df624..29824141 100644 --- a/R/process.R +++ b/R/process.R @@ -14,7 +14,7 @@ #' - [`process_modis_swath`]: `"modis_swath"` #' - [`process_modis_merge`]: `"modis_merge"` #' - [`process_bluemarble`]: `"bluemarble"` -#' - [`process_koppen_geiger`]: `"koppen-geiger"`, `"koeppen-geiger"`, `"koppen"`, +#' - [`process_koppen_geiger`]: `"koppen-geiger"`, `"koeppen-geiger"`, `"koppen"` #' - [`process_ecoregion`]: `"ecoregion"`, `"ecoregions"` #' - [`process_nlcd`]: `"nlcd"` #' - [`process_tri`]: `"tri"` @@ -32,7 +32,7 @@ #' - [`process_huc`]: `"huc"` #' - [`process_cropscape`]: `"cropscape"`, `"cdl"` #' - [`process_prism`]: `"prism"` -#' - [`process_olm`]: `"olm"`, `"openlandmap` +#' - [`process_olm`]: `"olm"`, `"openlandmap"` #' @returns `SpatVector`, `SpatRaster`, `sf`, or `character` depending on #' covariate type and selections. #' @author Insang Song @@ -807,7 +807,7 @@ process_tri <- function( # nolint start #' Process road emissions data #' @description -#' The \code{process_tri()} function imports and cleans raw road emissions data, +#' The \code{process_nei()} function imports and cleans raw road emissions data, #' returning a single `SpatVector` object. #' @param path character(1). Directory with NEI csv files. #' @param county `SpatVector`/`sf`. County boundaries. @@ -855,7 +855,8 @@ process_nei <- function( stop("year should be one of 2017 or 2020.\n") } # Concatenate NEI csv files - csvs_nei <- list.files(path = path, pattern = "*.csv$", full.names = TRUE) + csvs_nei <- list.files(path = path, pattern = "*.csv$", recursive = TRUE, full.names = TRUE) + csvs_nei <- grep(year, csvs_nei, value = TRUE) csvs_nei <- lapply(csvs_nei, data.table::fread) csvs_nei <- data.table::rbindlist(csvs_nei) @@ -1294,6 +1295,10 @@ process_hms <- function( #' @param variable vector(1). Vector containing the GMTED statistic first and #' the resolution second. (Example: variable = c("Breakline Emphasis", #' "7.5 arc-seconds")). +#' * Statistic options: "Breakline Emphasis", "Systematic Subsample", +#' "Median Statistic", "Minimum Statistic", "Mean Statistic", +#' "Maximum Statistic", "Standard Deviation Statistic" +#' * Resolution options: "30 arc-seconds", "15 arc-seconds", "7.5 arc-seconds" #' @param path character(1). Directory with downloaded GMTED "*_grd" #' folder containing .adf files. #' @param ... Placeholders. diff --git a/man/calc_covariates.Rd b/man/calc_covariates.Rd index 622763c6..189b8251 100644 --- a/man/calc_covariates.Rd +++ b/man/calc_covariates.Rd @@ -47,7 +47,7 @@ SpatRaster or SpatVector objects before passing to \seealso{ \itemize{ \item \code{\link{calc_modis_par}}: \code{"modis"}, \code{"MODIS"} -\item \code{\link{calc_koppen_geiger}}: \code{"koppen-geiger"}, \code{"koeppen-geiger"}, \code{"koppen"}, +\item \code{\link{calc_koppen_geiger}}: \code{"koppen-geiger"}, \code{"koeppen-geiger"}, \code{"koppen"} \item \code{\link{calc_ecoregion}}: \code{"ecoregion"}, \code{"ecoregions"} \item \item \code{\link{calc_hms}}: \code{"hms"}, \code{"noaa"}, \code{"smoke"} diff --git a/man/download_data.Rd b/man/download_data.Rd index 021c1e58..8f355b1c 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -5,11 +5,11 @@ \title{Download raw data wrapper function} \usage{ download_data( - dataset_name = c("aqs", "ecoregion", "geos", "gmted", "koppen", "koppengeiger", - "merra2", "merra", "narr_monolevel", "modis", "narr_p_levels", "nlcd", "noaa", - "sedac_groads", "sedac_population", "groads", "population", "plevels", "p_levels", - "monolevel", "hms", "smoke", "tri", "nei", "gridmet", "terraclimate", "huc", - "cropscape", "cdl", "prism", "olm", "openlandmap"), + dataset_name = c("aqs", "ecoregion", "ecoregions", "geos", "gmted", "koppen", + "koppengeiger", "merra2", "merra", "narr_monolevel", "modis", "narr_p_levels", + "nlcd", "noaa", "sedac_groads", "sedac_population", "groads", "population", + "plevels", "p_levels", "monolevel", "hms", "smoke", "tri", "nei", "gridmet", + "terraclimate", "huc", "cropscape", "cdl", "prism", "olm", "openlandmap"), directory_to_save = NULL, acknowledgement = FALSE, ... @@ -39,23 +39,23 @@ The \code{download_data()} function accesses and downloads atmospheric, meteorol For details of each download function per dataset, Please refer to: \itemize{ -\item \link{download_aqs_data}: "aqs", "AQS" -\item \link{download_ecoregion_data}: "ecoregion" -\item \link{download_geos_data}: "geos" -\item \link{download_gmted_data}: "gmted", "GMTED" -\item \link{download_koppen_geiger_data}: "koppen", "koppengeiger" -\item \link{download_merra2_data}: "merra2", "merra", "MERRA", "MERRA2" -\item \link{download_narr_monolevel_data}: "narr_monolevel", "monolevel" -\item \link{download_narr_p_levels_data}: "narr_p_levels", "p_levels", "plevels" -\item \link{download_nlcd_data}: "nlcd", "NLCD" -\item \link{download_hms_data}: "noaa", "smoke", "hms" -\item \link{download_sedac_groads_data}: "sedac_groads", "groads" -\item \link{download_sedac_population_data}: "sedac_population", "population" -\item \link{download_modis_data}: "modis", "MODIS" -\item \link{download_tri_data}: "tri", "TRI" -\item \link{download_nei_data}: "nei", "NEI" -\item \link{download_gridmet_data}: "gridMET", "gridmet" -\item \link{download_terraclimate_data}: "TerraClimate", "terraclimate" +\item \link{`download_aqs_data`}: \code{"aqs"}, \code{"AQS"} +\item \link{`download_ecoregion_data`}: \code{"ecoregions"}, \code{"ecoregion"} +\item \link{`download_geos_data`}: \code{"geos"} +\item \link{`download_gmted_data`}: \code{"gmted"}, \code{"GMTED"} +\item \link{`download_koppen_geiger_data`}: \code{"koppen"}, \code{"koppengeiger"} +\item \link{`download_merra2_data`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} +\item \link{`download_narr_monolevel_data`}: \code{"narr_monolevel"}, \code{"monolevel"} +\item \link{`download_narr_p_levels_data`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} +\item \link{`download_nlcd_data`}: \code{"nlcd"}, \code{"NLCD"} +\item \link{`download_hms_data`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} +\item \link{`download_sedac_groads_data`}: \code{"sedac_groads"}, \code{"groads"} +\item \link{`download_sedac_population_data`}: \code{"sedac_population"}, \code{"population"} +\item \link{`download_modis_data`}: \code{"modis"}, \code{"MODIS"} +\item \link{`download_tri_data`}: \code{"tri"}, \code{"TRI"} +\item \link{`download_nei_data`}: \code{"nei"}, \code{"NEI"} +\item \link{`download_gridmet_data`}: \code{"gridMET"}, \code{"gridmet"} +\item \link{`download_terraclimate_data`}: \code{"TerraClimate"}, \code{"terraclimate"} } } \author{ diff --git a/man/process_covariates.Rd b/man/process_covariates.Rd index e475eefd..20e34bb1 100644 --- a/man/process_covariates.Rd +++ b/man/process_covariates.Rd @@ -38,7 +38,7 @@ data files before passing to \code{process_covariates}}. \item \item \item -\item \code{\link{process_koppen_geiger}}: \code{"koppen-geiger"}, \code{"koeppen-geiger"}, \code{"koppen"}, +\item \code{\link{process_koppen_geiger}}: \code{"koppen-geiger"}, \code{"koeppen-geiger"}, \code{"koppen"} \item \code{\link{process_ecoregion}}: \code{"ecoregion"}, \code{"ecoregions"} \item \item @@ -56,7 +56,7 @@ data files before passing to \code{process_covariates}}. \item \item \code{\link{process_cropscape}}: \code{"cropscape"}, \code{"cdl"} \item -\item \code{\link{process_olm}}: \code{"olm"}, \verb{"openlandmap} +\item \code{\link{process_olm}}: \code{"olm"}, \code{"openlandmap"} } } \author{ diff --git a/man/process_gmted.Rd b/man/process_gmted.Rd index 4069bdd0..c50612da 100644 --- a/man/process_gmted.Rd +++ b/man/process_gmted.Rd @@ -9,7 +9,13 @@ process_gmted(variable = NULL, path = NULL, ...) \arguments{ \item{variable}{vector(1). Vector containing the GMTED statistic first and the resolution second. (Example: variable = c("Breakline Emphasis", -"7.5 arc-seconds")).} +"7.5 arc-seconds")). +\itemize{ +\item Statistic options: "Breakline Emphasis", "Systematic Subsample", +"Median Statistic", "Minimum Statistic", "Mean Statistic", +"Maximum Statistic", "Standard Deviation Statistic" +\item Resolution options: "30 arc-seconds", "15 arc-seconds", "7.5 arc-seconds" +}} \item{path}{character(1). Directory with downloaded GMTED "*_grd" folder containing .adf files.} diff --git a/man/process_nei.Rd b/man/process_nei.Rd index 0feb1936..cfc6a522 100644 --- a/man/process_nei.Rd +++ b/man/process_nei.Rd @@ -20,7 +20,7 @@ is accepted.} a \code{SpatVector} object } \description{ -The \code{process_tri()} function imports and cleans raw road emissions data, +The \code{process_nei()} function imports and cleans raw road emissions data, returning a single \code{SpatVector} object. NEI data comprises multiple csv files where emissions of diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index ddadaea6..bd5eead9 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -412,6 +412,8 @@ testthat::test_that("calc_modis works well.", { testthat::test_that("Check calc_nlcd works", { withr::local_package("terra") withr::local_package("exactextractr") + withr::local_package("sf") + withr::local_options(list(sf_use_s2 = FALSE)) point_us1 <- cbind(lon = -114.7, lat = 38.9, dem = 40) point_us2 <- cbind(lon = -114, lat = 39, dem = 15) @@ -463,10 +465,13 @@ testthat::test_that("Check calc_nlcd works", { "NLCD data not available for this year." ) # -- data_vect is a SpatVector + testthat::expect_message( + calc_nlcd(locs = sf::st_as_sf(eg_data), + from = nlcdras) + ) testthat::expect_error( calc_nlcd(locs = 12, - from = nlcdras), - "locs is not a terra::SpatVector." + from = nlcdras) ) testthat::expect_error( calc_nlcd(locs = eg_data, From 062f448623296627bf3c6b0b4b96f015ac83c8ea Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Tue, 30 Apr 2024 10:17:05 -0400 Subject: [PATCH 08/65] https://github.com/NIEHS/amadeus/issues/68 --- R/calculate_covariates.R | 46 ++++++++++++++++------ R/process.R | 34 +++++++++++++--- tests/testthat/test-calculate_covariates.R | 10 ++--- 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 93ad9bb9..0ed65753 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -123,6 +123,10 @@ calc_covariates <- #' @param ... Placeholders. #' @seealso [`process_koppen_geiger`] #' @returns a data.frame object +#' @note The returned `data.frame` object contains a +#' `$time` column to represent the temporal range covered by the +#' dataset. For more information, see +#' . #' @author Insang Song #' @importFrom terra vect #' @importFrom terra rast @@ -144,7 +148,9 @@ calc_koppen_geiger <- locs_tr <- locs if (!methods::is(locs, "SpatVector")) { - locs_tr <- terra::vect(locs) + locs_tr <- process_conformity( + locs = locs + ) } locs_kg <- terra::project(locs_tr, terra::crs(from)) locs_kg_extract <- terra::extract(from, locs_kg) @@ -203,9 +209,11 @@ calc_koppen_geiger <- kg_extracted <- cbind( locs_id = unlist(locs_kg_extract_e[[locs_id]]), + as.character(terra::metags(from)), df_ae_separated ) names(kg_extracted)[1] <- locs_id + names(kg_extracted)[2] <- "time" return(kg_extracted) } @@ -348,7 +356,7 @@ calc_ecoregion <- if (!methods::is(locs, "SpatVector")) { locs <- terra::vect(locs) } - + locs <- terra::project(locs, terra::crs(from)) locs_in <- terra::intersect(locs, from) locs_out <- @@ -393,7 +401,11 @@ calc_ecoregion <- as.data.frame() colnames(df_lv3) <- key3_num_unique - locs_ecoreg <- cbind(locs[[locs_id]], df_lv2, df_lv3) + locs_ecoreg <- cbind( + locs[[locs_id]], + paste0("1997 - ", data.table::year(Sys.Date())), + df_lv2, df_lv3) + names(locs_ecoreg)[2] <- "time" attr(locs_ecoreg, "ecoregion2_code") <- sort(unique(from$L2_KEY)) attr(locs_ecoreg, "ecoregion3_code") <- sort(unique(from$L3_KEY)) return(locs_ecoreg) @@ -1262,10 +1274,10 @@ calc_hms <- function( #' Calculate elevation covariates #' @description #' Extract elevation values at point locations. Returns a \code{data.frame} -#' object containing \code{locs_id} and elevation variable. Elevation variable -#' column name reflects the elevation statistic, spatial resolution of -#' \code{from}, and circular buffer radius (ie. Breakline Emphasis at 7.5 -#' arc-second resolution with 0 meter buffer: breakline_emphasis_r75_0). +#' object containing \code{locs_id}, year of release, and elevation variable. +#' Elevation variable column name reflects the elevation statistic, spatial +#' resolution of \code{from}, and circular buffer radius (ie. Breakline Emphasis +#' at 7.5 arc-second resolution with 0 meter buffer: breakline_emphasis_r75_0). #' @param from SpatRaster(1). Output from \code{process_gmted()}. #' @param locs data.frame. character to file path, SpatVector, or sf object. #' @param locs_id character(1). Column within `locations` CSV file @@ -1310,14 +1322,15 @@ calc_gmted <- function( radius = radius, fun = fun, variable = 2, - time = NULL, - time_type = "timeless" + time = 3, + time_type = "year" ) #### convert integer to numeric - sites_extracted[, 2] <- as.numeric(sites_extracted[, 2]) + sites_extracted[, 3] <- as.numeric(sites_extracted[, 3]) #### define column names colnames(sites_extracted) <- c( locs_id, + "time", paste0( gsub( " ", @@ -1572,7 +1585,11 @@ calc_sedac_population <- function( #' @param fun function(1). Function used to summarize the length of roads #' within sites location buffer (Default is `sum`). #' @param ... Placeholders. -#' @note Unit is km / sq km. +# nolint start +#' @note Unit is km / sq km. The returned `data.frame` object contains a +#' `$time` column to represent the temporal range covered by the +#' dataset. For more information, see . +# nolint end #' @author Insang Song #' @seealso [`process_sedac_groads`] #' @return a data.frame object with three columns. @@ -1640,8 +1657,11 @@ calc_sedac_groads <- function( sprintf("GRD_TOTAL_0_%05d", radius), sprintf("GRD_DENKM_0_%05d", radius)) ) - - return(from_clip) + #### time + from_clip$time <- "1980 - 2010" + #### reorder + from_clip_reorder <- from_clip[, c(1, 4, 2, 3)] + return(from_clip_reorder) } #' Calculate meteorological and atmospheric covariates diff --git a/R/process.R b/R/process.R index 0b43c19d..52731bf7 100644 --- a/R/process.R +++ b/R/process.R @@ -631,7 +631,18 @@ process_koppen_geiger <- year = NULL, ... ) { + # import data kg_rast <- terra::rast(path) + # identify time period + period <- strsplit( + names(kg_rast), + "_" + )[[1]][4] + if (period == "present") { + terra::metags(kg_rast) <- c(year = "1980 - 2016") + } else { + terra::meteags(kg_rast) <- c(year = "2071 - 2100") + } return(kg_rast) } @@ -699,6 +710,9 @@ process_ecoregion <- ) { ecoreg <- terra::vect(path) ecoreg <- ecoreg[, grepl("^(L2_KEY|L3_KEY)", names(ecoreg))] + ecoreg$time <- paste0( + "1997 - ", data.table::year(Sys.time()) + ) return(ecoreg) } @@ -1084,6 +1098,8 @@ process_sedac_population <- function( split2[1], "...\n" )) + #### year + terra::metags(data) <- c(year = split2[1]) } return(data) } @@ -1096,9 +1112,11 @@ process_sedac_population <- function( #' returning a single `SpatVector` object. #' @param path character(1). Path to geodatabase or shapefiles. #' @param ... Placeholders. -#' @note U.S. context. +#' @note U.S. context. The returned `SpatVector` object contains a +#' `$time` column to represent the temporal range covered by the +#' dataset. For more information, see . #' @author Insang Song -#' @returns a `SpatVector` boject +#' @returns a `SpatVector` object #' @importFrom terra vect #' @export # nolint end @@ -1112,6 +1130,8 @@ process_sedac_groads <- function( } #### import data data <- terra::vect(path) + #### time period + data$time <- "1980 - 2010" return(data) } @@ -1313,7 +1333,8 @@ process_hms <- function( #' @param ... Placeholders. #' @author Mitchell Manware #' @note -#' `SpatRaster` layer name indicates selected variable and resolution. +#' `SpatRaster` layer name indicates selected variable and resolution, and year +#' of release (2010). #' @return a `SpatRaster` object #' @importFrom terra rast #' @importFrom terra varnames @@ -1382,7 +1403,8 @@ process_gmted <- function( "_grd", "", names(data) - ) + ), + "_2010" ) #### varnames terra::varnames(data) <- paste0( @@ -1392,6 +1414,8 @@ process_gmted <- function( resolution, ")" ) + #### year + terra::metags(data) <- c(year = 2010) #### set coordinate reference system return(data) } @@ -2601,7 +2625,7 @@ process_prism <- #' Process OpenLandMap data #' @param path character giving OpenLandMap data path #' @param ... Placeholders. -#' @returns SpatRaster +#' @returns a `SpatRaster` object #' @author Insang Song #' @importFrom terra rast #' @export diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 13d55bb2..9c4174f9 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -35,9 +35,9 @@ testthat::test_that("calc_koppen_geiger works well", { # the result is a data frame testthat::expect_s3_class(kg_res, "data.frame") # ncol is equal to 6 - testthat::expect_equal(ncol(kg_res), 6) + testthat::expect_equal(ncol(kg_res), 7) # should have only one climate zone - testthat::expect_equal(sum(unlist(kg_res[, -1])), 1) + testthat::expect_equal(sum(unlist(kg_res[, c(-1, -2)])), 1) }) testthat::test_that("calc_dummies works well", { @@ -141,7 +141,7 @@ testthat::test_that("calc_ecoregion works well", { # the result is a data frame testthat::expect_s3_class(ecor_res, "data.frame") # ncol is equal to 2 + 5 + 2 + 1 + 1 - testthat::expect_equal(ncol(ecor_res), 3L) + testthat::expect_equal(ncol(ecor_res), 4L) # should have each of the indicator groups dum_cn <- grep("DUM_", colnames(ecor_res)) testthat::expect_equal( @@ -928,11 +928,11 @@ testthat::test_that("calc_gmted returns expected.", { ) # expect 2 columns expect_true( - ncol(gmted_covariate) == 2 + ncol(gmted_covariate) == 3 ) # expect numeric value expect_true( - class(gmted_covariate[, 2]) == "numeric" + class(gmted_covariate[, 3]) == "numeric" ) } } From f0b5af8b442ec1053de643c37d6f9d44af5de07a Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Tue, 30 Apr 2024 12:30:31 -0400 Subject: [PATCH 09/65] update process_locs_vector to accept data.frame, sf, and SpatVector; associated unit tests --- R/process_auxiliary.R | 54 +++++++++++++++++------------------ tests/testthat/test-process.R | 30 +++++++++++++++++-- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/R/process_auxiliary.R b/R/process_auxiliary.R index da571122..3077f757 100644 --- a/R/process_auxiliary.R +++ b/R/process_auxiliary.R @@ -319,7 +319,8 @@ process_locs_radius <- #' Process locations as `SpatVector` #' @description -#' Convert locations from class \code{data.frame} or \code{data.table} to +#' Detect `SpatVector` object, or convert locations from class \code{sf}, +#' \code{data.frame} or \code{data.table} to #' `SpatVector` object, project to coordinate reference system, and apply #' circular buffer. #' @param locs data.frame(1). Data frame containing columns for unique @@ -338,35 +339,32 @@ process_locs_vector <- function( locs, crs, - radius) { - #### sites as data frame - if ("data.table" %in% class(locs)) { - sites_df <- data.frame(locs) - } else if ("data.frame" %in% class(locs) && - !("data.table" %in% class(locs))) { - sites_df <- locs - } else if (!("data.table" %in% class(locs)) && - !("data.frame" %in% class(locs))) { - stop( - paste0( - "Detected a ", - class(locs)[1], - " object. Sites must be class data.frame or data.table.\n" - ) + radius + ) { + #### detect SpatVector + if (methods::is(locs, "SpatVector")) { + cat("Detected `SpatVector` extraction locations...\n") + sites_v <- locs + #### detect sf object + } else if (methods::is(locs, "sf")) { + cat("Detected `sf` extraction locations...\n") + sites_v <- terra::vect(locs) + ### detect data.frame object + } else if (methods::is(locs, "data.frame")) { + cat("Detected `data.frame` extraction locations...\n") + #### columns + if (any(!(c("lon", "lat") %in% colnames(locs)))) { + stop(paste0( + "`locs` is missing 'lon', 'lat', or both.\n" + )) + } + sites_v <- terra::vect( + data.frame(locs), + geom = c("lon", "lat"), + crs = "EPSG:4326" ) } - #### columns - if (any(!(c("lon", "lat") %in% colnames(locs)))) { - stop(paste0( - "Sites data is missing 'lon', 'lat', or both.\n" - )) - } - #### as SpatVector - sites_v <- terra::vect( - sites_df, - geom = c("lon", "lat"), - crs = "EPSG:4326" - ) + ##### project to desired coordinate reference system sites_p <- terra::project( sites_v, crs diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index b0106f78..a0a9026e 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -928,8 +928,8 @@ testthat::test_that("process_locs_vector vector data and missing columns.", { locs_id = "site_id" ) ) - # expect error when sites are SpatVector - expect_error( + # expect error when sites are SpatVector (points) + expect_no_error( calc_narr( from = narr, locs = terra::vect( @@ -940,6 +940,32 @@ testthat::test_that("process_locs_vector vector data and missing columns.", { locs_id = "site_id" ) ) + # expect error when sites are SpatVector (polygons) + expect_no_error( + calc_narr( + from = narr, + locs = terra::centroids( + terra::vect( + ncp, + geom = c("lon", "lat"), + crs = "EPSG:4326" + ) + ), + locs_id = "site_id" + ) + ) + # expect error when sites are sf + expect_no_error( + calc_narr( + from = narr, + locs = sf::st_as_sf( + ncp, + coords = c("lon", "lat"), + crs = "EPSG:4326" + ), + locs_id = "site_id" + ) + ) }) # test AQS #### From afcb0a70132e95eef20ab3fbe6c2ae6e2f6f2f69 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 30 Apr 2024 16:25:59 -0400 Subject: [PATCH 10/65] process_hms patch - To read shp files only --- R/process.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/R/process.R b/R/process.R index 29824141..e1dd85d4 100644 --- a/R/process.R +++ b/R/process.R @@ -1158,7 +1158,7 @@ process_hms <- function( #### identify file paths paths <- list.files( path, - pattern = "hms_smoke", + pattern = "hms_smoke*.*.shp$", full.names = TRUE ) paths <- paths[grep( @@ -1423,12 +1423,14 @@ process_narr <- function( data_paths <- list.files( path, pattern = variable, + recursive = TRUE, full.names = TRUE ) - data_paths <- data_paths[grep( - ".nc", - data_paths - )] + data_paths <- grep( + sprintf("%s*.*.nc", variable), + data_paths, + value = TRUE + ) #### define date sequence date_sequence <- generate_date_sequence( date[1], From 9830c507414e0532876411e2da0f5e11e7621879 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Wed, 1 May 2024 08:48:29 -0400 Subject: [PATCH 11/65] extraction functions; geom = parameter --- R/calculate_covariates.R | 13 +++++- R/calculate_covariates_auxiliary.R | 69 +++++++++++++++++++++++------- R/process_auxiliary.R | 11 ++++- tests/testthat/test-process.R | 5 ++- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 0ed65753..41fa61a9 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -1381,6 +1381,12 @@ calc_gmted <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders #' @author Mitchell Manware #' @seealso [`process_narr`] @@ -1398,13 +1404,15 @@ calc_narr <- function( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1427,7 +1435,8 @@ calc_narr <- function( variable = 1, time = narr_time, time_type = "date", - level = narr_level + level = narr_level, + ... ) #### return data.frame return(data.frame(sites_extracted)) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index c1aa179e..9aa389bb 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -40,6 +40,10 @@ calc_setcolumns <- function( ) stopifnot(length(time_index) <= 1) names_return[time_index] <- "time" + #### geometry + geometry_index <- which(names_from == "geometry") + stopifnot(length(geometry_index) <= 1) + names_return[geometry_index] <- "geometry" #### latitude and longitude lat_index <- which( tolower(names_from) %in% c("lat", "latitude") @@ -65,7 +69,7 @@ calc_setcolumns <- function( genre <- substr(dataset, 1, 3) #### covariates cov_index <- which( - !(c(names_from %in% c(locs_id, "time", "lat", "lon", "level"))) + !(c(names_from %in% c(locs_id, "geometry" "time", "lat", "lon", "level"))) ) for (c in seq_along(cov_index)) { name_covariate <- names_return[cov_index[c]] @@ -196,6 +200,8 @@ calc_message <- function( #' Passed from \code{calc_\*()}. #' @param radius integer(1). Circular buffer distance around site locations. #' (Default = 0). Passed from \code{calc_\*()}. +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. #' @return A `list` containing `SpatVector` and `data.frame` objects #' @seealso [`process_locs_vector()`], [`check_for_null_parameters()`] #' @keywords internal @@ -206,7 +212,8 @@ calc_prepare_locs <- function( from, locs, locs_id, - radius) { + radius, + geom = FALSE) { #### check for null parameters check_for_null_parameters(mget(ls())) #### prepare sites @@ -215,11 +222,19 @@ calc_prepare_locs <- function( terra::crs(from), radius ) + #### site identifiers and geometry + if (geom) { + sites_id <- subset( + terra::as.data.frame(sites_e, geom = "WKT"), + select = c(locs_id, "geometry") + ) + } else { #### site identifiers only - sites_id <- subset( - terra::as.data.frame(sites_e), - select = locs_id - ) + sites_id <- subset( + terra::as.data.frame(sites_e), + select = locs_id + ) + } return(list(sites_e, sites_id)) } @@ -281,8 +296,15 @@ calc_time <- function( #' pressure level value (if applicable). Default = `NULL`. #' @param radius integer(1). Buffer distance (m). Passed from #' \code{calc_prepare_locs()}. Used in column naming. +#' @param max_cells integer(1). Maximum number of cells to be read at once. +#' Higher values will expedite processing, but will increase memory usage. +#' Maximum possible value is `2^31 - 1`. +#' See [`exactextractr::exact_extract`] for details. +#' @param ... Placeholders. #' @importFrom terra nlyr #' @importFrom terra extract +#' @importFrom exactextractr exact_extract +#' @importFrom sf st_as_sf #' @return a `data.frame` object #' @keywords internal #' @export @@ -296,7 +318,9 @@ calc_worker <- function( time, time_type = c("date", "hour", "year", "yearmonth", "timeless"), radius, - level = NULL) { + level = NULL, + max_cells = 1e8, + ...) { #### empty location data.frame sites_extracted <- NULL for (l in seq_len(terra::nlyr(from))) { @@ -331,15 +355,28 @@ calc_worker <- function( level = data_level ) #### extract layer data at sites - sites_extracted_layer <- terra::extract( - data_layer, - locs_vector, - fun = fun, - method = "simple", - ID = FALSE, - bind = FALSE, - na.rm = TRUE - ) + if (terra::geomtype(locs_vector) == "polygons") { + ### apply exactextractr::exact_extract for polygons + sites_extracted_layer <- exactextractr::exact_extract( + data_layer, + sf::st_as_sf(locs_vector), + progress = FALSE, + force_df = TRUE, + fun = fun, + max_cells_in_memory = max_cells, + ... + ) + } else if (terra::geomtype(locs_vector) == "points") { + #### apply terra::extract for points + sites_extracted_layer <- terra::extract( + data_layer, + locs_vector, + method = "simple", + ID = FALSE, + bind = FALSE, + na.rm = TRUE + ) + } # merge with site_id, time, and pressure levels (if applicable) if (time_type == "timeless") { sites_extracted_layer <- cbind( diff --git a/R/process_auxiliary.R b/R/process_auxiliary.R index 3077f757..31d2f794 100644 --- a/R/process_auxiliary.R +++ b/R/process_auxiliary.R @@ -343,7 +343,13 @@ process_locs_vector <- ) { #### detect SpatVector if (methods::is(locs, "SpatVector")) { - cat("Detected `SpatVector` extraction locations...\n") + cat( + paste0( + "Detected `SpatVector` (", + terra::geomtype(locs), + ") extraction locations...\n" + ) + ) sites_v <- locs #### detect sf object } else if (methods::is(locs, "sf")) { @@ -361,7 +367,8 @@ process_locs_vector <- sites_v <- terra::vect( data.frame(locs), geom = c("lon", "lat"), - crs = "EPSG:4326" + crs = "EPSG:4326", + keepgeom = TRUE ) } ##### project to desired coordinate reference system diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index a0a9026e..270f2fee 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -944,12 +944,13 @@ testthat::test_that("process_locs_vector vector data and missing columns.", { expect_no_error( calc_narr( from = narr, - locs = terra::centroids( + locs = terra::buffer( terra::vect( ncp, geom = c("lon", "lat"), crs = "EPSG:4326" - ) + ), + 1000 ), locs_id = "site_id" ) From 53f99c3fec2600f7a4d8bc408a084f8e4c4361f2 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Wed, 1 May 2024 11:54:55 -0400 Subject: [PATCH 12/65] update calc_koppen_geiger and calc_nlcd\n - utilize calc_process_locs\n - tests for retaining geometry --- R/calculate_covariates.R | 95 +++++++++++++++------- R/calculate_covariates_auxiliary.R | 9 +- R/process_auxiliary.R | 6 ++ tests/testthat/test-calculate_covariates.R | 56 +++++++++---- 4 files changed, 122 insertions(+), 44 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 41fa61a9..489b9fec 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -120,11 +120,17 @@ calc_covariates <- #' @param locs sf/SpatVector. Unique locs. Should include #' a unique identifier field named `locs_id` #' @param locs_id character(1). Name of unique identifier. +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders. #' @seealso [`process_koppen_geiger`] #' @returns a data.frame object #' @note The returned `data.frame` object contains a -#' `$time` column to represent the temporal range covered by the +#' `$description` column to represent the temporal range covered by the #' dataset. For more information, see #' . #' @author Insang Song @@ -143,16 +149,18 @@ calc_koppen_geiger <- from = NULL, locs = NULL, locs_id = "site_id", + geom = FALSE, ...) { - ## You will get "locs" in memory after sourcing the file above - locs_tr <- locs - - if (!methods::is(locs, "SpatVector")) { - locs_tr <- process_conformity( - locs = locs - ) - } - locs_kg <- terra::project(locs_tr, terra::crs(from)) + # prepare locations + locs_prepared <- calc_prepare_locs( + from = from, + locs = locs, + locs_id = locs_id, + radius = 0, + geom = geom + ) + locs_kg <- locs_prepared[[1]] + locs_df <- locs_prepared[[2]] locs_kg_extract <- terra::extract(from, locs_kg) # The starting value is NA as the color table has 0 value in it @@ -170,7 +178,10 @@ calc_koppen_geiger <- class_kg = kg_class ) - locs_kg_extract[[locs_id]] <- unlist(locs_kg[[locs_id]]) + locs_kg_extract[[locs_id]] <- locs_df[,1] + if (geom) { + locs_kg_extract$geometry <- locs_df[,2] + } colnames(locs_kg_extract)[2] <- "value" locs_kg_extract_e <- merge(locs_kg_extract, kg_colclass, by = "value") @@ -208,12 +219,16 @@ calc_koppen_geiger <- kg_extracted <- cbind( - locs_id = unlist(locs_kg_extract_e[[locs_id]]), + locs_id = locs_df, as.character(terra::metags(from)), df_ae_separated ) names(kg_extracted)[1] <- locs_id - names(kg_extracted)[2] <- "time" + if (geom) { + names(kg_extracted)[2:3] <- c("geometry", "description") + } else { + names(kg_extracted)[2] <- "description" + } return(kg_extracted) } @@ -232,6 +247,12 @@ calc_koppen_geiger <- #' Higher values will expedite processing, but will increase memory usage. #' Maximum possible value is `2^31 - 1`. #' See [`exactextractr::exact_extract`] for details. +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders. #' @seealso [`process_nlcd`] #' @returns a data.frame object @@ -254,38 +275,51 @@ calc_nlcd <- function(from, locs_id = "site_id", radius = 1000, max_cells = 1e8, + geom = FALSE, ...) { # check inputs if (!is.numeric(radius)) { stop("radius is not a numeric.") } - if (radius <= 0) { + if (radius <= 0 && terra::geomtype(locs) == "points") { stop("radius has not a likely value.") } - if (!methods::is(locs, "SpatVector")) { - stop("locs is not a terra::SpatVector.") - } if (!methods::is(from, "SpatRaster")) { stop("from is not a SpatRaster.") } + + # prepare locations + locs_prepared <- calc_prepare_locs( + from = from, + locs = locs, + locs_id = locs_id, + radius = radius, + geom = geom + ) + locs_vector <- locs_prepared[[1]] + locs_df <- locs_prepared[[2]] + year <- try(as.integer(terra::metags(from, name = "year"))) # select points within mainland US and reproject on nlcd crs if necessary us_main <- terra::ext(c(xmin = -127, xmax = -65, ymin = 24, ymax = 51)) |> terra::vect() |> terra::set.crs("EPSG:4326") |> - terra::project(y = terra::crs(locs)) - data_vect_b <- locs |> + terra::project(y = terra::crs(locs_vector)) + data_vect_b <- locs_vector |> terra::intersect(x = us_main) + # subset locs_df to those in us extent + locs_df <- locs_df[ + unlist(locs_df[[locs_id]]) %in% unlist(data_vect_b[[locs_id]]), + ] if (!terra::same.crs(data_vect_b, from)) { data_vect_b <- terra::project(data_vect_b, terra::crs(from)) } # create circle buffers with buf_radius - bufs_pol <- terra::buffer(data_vect_b, width = radius) |> - sf::st_as_sf() + # bufs_pol <- sf::st_as_sf(data_vect_b) # ratio of each nlcd class per buffer nlcd_at_bufs <- exactextractr::exact_extract(from, - sf::st_geometry(bufs_pol), + sf::st_as_sf(data_vect_b), fun = "frac", stack_apply = TRUE, force_df = TRUE, @@ -313,10 +347,13 @@ calc_nlcd <- function(from, } ) names(nlcd_at_bufs) <- new_names - # merge data_vect with nlcd class fractions (and reproject) - new_data_vect <- cbind(data_vect_b, nlcd_at_bufs) - new_data_vect <- terra::project(new_data_vect, terra::crs(locs)) - new_data_vect$time <- as.integer(year) + # merge locs_df with nlcd class fractions + new_data_vect <- cbind(locs_df, year, nlcd_at_bufs) + if (geom) { + names(new_data_vect)[1:3] <- c(locs_id, "geometry", "time") + } else { + names(new_data_vect)[1:2] <- c(locs_id, "time") + } return(new_data_vect) } @@ -405,7 +442,7 @@ calc_ecoregion <- locs[[locs_id]], paste0("1997 - ", data.table::year(Sys.Date())), df_lv2, df_lv3) - names(locs_ecoreg)[2] <- "time" + names(locs_ecoreg)[2] <- "description" attr(locs_ecoreg, "ecoregion2_code") <- sort(unique(from$L2_KEY)) attr(locs_ecoreg, "ecoregion3_code") <- sort(unique(from$L3_KEY)) return(locs_ecoreg) @@ -1666,8 +1703,8 @@ calc_sedac_groads <- function( sprintf("GRD_TOTAL_0_%05d", radius), sprintf("GRD_DENKM_0_%05d", radius)) ) - #### time - from_clip$time <- "1980 - 2010" + #### time period + from_clip$description <- "1980 - 2010" #### reorder from_clip_reorder <- from_clip[, c(1, 4, 2, 3)] return(from_clip_reorder) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 9aa389bb..78546eac 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -40,6 +40,10 @@ calc_setcolumns <- function( ) stopifnot(length(time_index) <= 1) names_return[time_index] <- "time" + #### description (for time period coverage) + description_index <- which(names_from == "description") + stopifnot(length(geometry_index) <= 1) + names_return[geometry_index] <- "description" #### geometry geometry_index <- which(names_from == "geometry") stopifnot(length(geometry_index) <= 1) @@ -69,7 +73,10 @@ calc_setcolumns <- function( genre <- substr(dataset, 1, 3) #### covariates cov_index <- which( - !(c(names_from %in% c(locs_id, "geometry" "time", "lat", "lon", "level"))) + !(c(names_from %in% c( + locs_id, "geometry", "time", "lat", "lon", "level", "description" + )) + ) ) for (c in seq_along(cov_index)) { name_covariate <- names_return[cov_index[c]] diff --git a/R/process_auxiliary.R b/R/process_auxiliary.R index 31d2f794..ed513b85 100644 --- a/R/process_auxiliary.R +++ b/R/process_auxiliary.R @@ -370,6 +370,12 @@ process_locs_vector <- crs = "EPSG:4326", keepgeom = TRUE ) + } else { + stop( + paste0( + "`locs` is not a `SpatVector`, `sf`, or `data.frame` object.\n" + ) + ) } ##### project to desired coordinate reference system sites_p <- terra::project( diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 9c4174f9..d129a1bc 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -34,10 +34,20 @@ testthat::test_that("calc_koppen_geiger works well", { ) # the result is a data frame testthat::expect_s3_class(kg_res, "data.frame") - # ncol is equal to 6 + # ncol is equal to 7 testthat::expect_equal(ncol(kg_res), 7) # should have only one climate zone testthat::expect_equal(sum(unlist(kg_res[, c(-1, -2)])), 1) + # with included geometry + testthat::expect_no_error( + kg_geom <- calc_koppen_geiger( + from = kgras, + locs = sf::st_as_sf(site_faux), + geom = TRUE + ) + ) + testthat::expect_equal(ncol(kg_geom), 8) + testthat::expect_true("geometry" %in% names(kg_geom)) }) testthat::test_that("calc_dummies works well", { @@ -413,10 +423,10 @@ testthat::test_that("Check calc_nlcd works", { withr::local_package("terra") withr::local_package("exactextractr") - point_us1 <- cbind(lon = -114.7, lat = 38.9, dem = 40) - point_us2 <- cbind(lon = -114, lat = 39, dem = 15) - point_ak <- cbind(lon = -155.997, lat = 69.3884, dem = 100) # alaska - point_fr <- cbind(lon = 2.957, lat = 43.976, dem = 15) # france + point_us1 <- cbind(lon = -114.7, lat = 38.9, id = 1) + point_us2 <- cbind(lon = -114, lat = 39, id = 2) + point_ak <- cbind(lon = -155.997, lat = 69.3884, id = 3) # alaska + point_fr <- cbind(lon = 2.957, lat = 43.976, id = 4) # france eg_data <- rbind(point_us1, point_us2, point_ak, point_fr) |> as.data.frame() |> terra::vect(crs = "EPSG:4326") @@ -466,7 +476,7 @@ testthat::test_that("Check calc_nlcd works", { testthat::expect_error( calc_nlcd(locs = 12, from = nlcdras), - "locs is not a terra::SpatVector." + "`locs` is not a `SpatVector`, `sf`, or `data.frame` object." ) testthat::expect_error( calc_nlcd(locs = eg_data, @@ -492,36 +502,54 @@ testthat::test_that("Check calc_nlcd works", { testthat::expect_no_error( calc_nlcd( locs = eg_data, + locs_id = "id", from = nlcdras, radius = buf_radius ) ) output <- calc_nlcd( locs = eg_data, + locs_id = "id", radius = buf_radius, from = nlcdras ) - # -- returns a SpatVector - testthat::expect_equal(class(output)[1], "SpatVector") - # -- crs is the same than input - testthat::expect_true(terra::same.crs(eg_data, output)) + # -- returns a data.frame + testthat::expect_equal(class(output)[1], "data.frame") # -- out-of-mainland-US points removed (France and Alaska) testthat::expect_equal(nrow(output), 2) - # -- initial names are still in the output SpatVector + # -- initial names are still in the output data.frame testthat::expect_true(all(names(eg_data) %in% names(output))) # -- check the value of some of the points in the US testthat::expect_equal( - output$LDU_TEFOR_0_03000[1], 0.7940682, tolerance = 1e-7 + output$LDU_TEFOR_0_03000[2], 0.7940682, tolerance = 1e-7 ) testthat::expect_equal( - output$LDU_TSHRB_0_03000[2], 0.9987249, tolerance = 1e-7 + output$LDU_TSHRB_0_03000[1], 0.9987249, tolerance = 1e-7 ) # -- class fraction rows should sum to 1 testthat::expect_equal( - rowSums(as.data.frame(output[, 2:(ncol(output) - 1)])), + rowSums(as.data.frame(output[, 3:(ncol(output))])), rep(1, 2), tolerance = 1e-7 ) + # without geometry will have 11 columns + expect_equal( + ncol(output), 11 + ) + output_geom <- calc_nlcd( + locs = eg_data, + locs_id = "id", + radius = buf_radius, + from = nlcdras, + geom = TRUE + ) + # with geometry will have 12 columns + expect_equal( + ncol(output_geom), 12 + ) + expect_true( + "geometry" %in% names(output_geom) + ) }) From 30af05681fc29c71a85f23be5455cd1d9caeecdd Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Wed, 1 May 2024 14:29:44 -0400 Subject: [PATCH 13/65] update calc_gmted, calc_ecoregion, calc_sedac*, calc_narr, calc_geos, calc_merra2, calc_gridet, calc_terraclimate --- R/calculate_covariates.R | 156 ++++++++++++++++----- R/calculate_covariates_auxiliary.R | 9 +- tests/testthat/test-calculate_covariates.R | 54 +++++++ 3 files changed, 184 insertions(+), 35 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 489b9fec..17b4dab5 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -368,6 +368,12 @@ calc_nlcd <- function(from, #' @param locs sf/SpatVector. Unique locs. Should include #' a unique identifier field named `locs_id` #' @param locs_id character(1). Name of unique identifier. +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders. #' @seealso [`process_ecoregion`] #' @returns a data.frame object with dummy variables and attributes of: @@ -387,14 +393,20 @@ calc_ecoregion <- from = NULL, locs, locs_id = "site_id", + geom = FALSE, ... ) { - - if (!methods::is(locs, "SpatVector")) { - locs <- terra::vect(locs) - } + # prepare locations + locs_prepared <- calc_prepare_locs( + from = from, + locs = locs, + locs_id = locs_id, + radius = 0, + geom = geom + ) + locs <- locs_prepared[[1]] + locs_df <- locs_prepared[[2]] - locs <- terra::project(locs, terra::crs(from)) locs_in <- terra::intersect(locs, from) locs_out <- locs[!unlist(locs[[locs_id]]) %in% unlist(locs_in[[locs_id]]), ] @@ -439,10 +451,14 @@ calc_ecoregion <- colnames(df_lv3) <- key3_num_unique locs_ecoreg <- cbind( - locs[[locs_id]], + locs_df, paste0("1997 - ", data.table::year(Sys.Date())), df_lv2, df_lv3) - names(locs_ecoreg)[2] <- "description" + if (geom) { + names(locs_ecoreg)[3] <- "description" + } else { + names(locs_ecoreg)[2] <- "description" + } attr(locs_ecoreg, "ecoregion2_code") <- sort(unique(from$L2_KEY)) attr(locs_ecoreg, "ecoregion3_code") <- sort(unique(from$L3_KEY)) return(locs_ecoreg) @@ -1323,6 +1339,12 @@ calc_hms <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders #' @author Mitchell Manware #' @seealso [`process_gmted()`] @@ -1340,13 +1362,15 @@ calc_gmted <- function( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1362,13 +1386,8 @@ calc_gmted <- function( time = 3, time_type = "year" ) - #### convert integer to numeric - sites_extracted[, 3] <- as.numeric(sites_extracted[, 3]) - #### define column names - colnames(sites_extracted) <- c( - locs_id, - "time", - paste0( + #### variable column name + variable_name <- paste0( gsub( " ", "_", @@ -1399,7 +1418,16 @@ calc_gmted <- function( "_", radius ) - ) + if (geom) { + #### convert integer to numeric + sites_extracted[, 4] <- as.numeric(sites_extracted[, 4]) + names(sites_extracted) <- c(locs_id, "geometry", "time", variable_name) + } else { + #### convert integer to numeric + sites_extracted[, 3] <- as.numeric(sites_extracted[, 3]) + names(sites_extracted) <- c(locs_id, "time", variable_name) + + } #### return data.frame return(data.frame(sites_extracted)) } @@ -1495,7 +1523,13 @@ calc_narr <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). -#' @param ... Placeholders +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. +#' @param ... Placeholders. #' @author Mitchell Manware #' @seealso [process_geos()] #' @return a data.frame object @@ -1513,13 +1547,15 @@ calc_geos <- function( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1534,7 +1570,8 @@ calc_geos <- function( variable = 1, time = c(3, 4), time_type = "hour", - level = 2 + level = 2, + ... ) #### return data.frame return(data.frame(sites_extracted)) @@ -1554,6 +1591,12 @@ calc_geos <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders #' @author Mitchell Manware #' @seealso [process_sedac_population()] @@ -1566,13 +1609,15 @@ calc_sedac_population <- function( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1607,7 +1652,8 @@ calc_sedac_population <- function( fun = fun, variable = 3, time = 4, - time_type = "year" + time_type = "year", + ... ) #### return data.frame return(data.frame(sites_extracted)) @@ -1630,6 +1676,12 @@ calc_sedac_population <- function( #' (Default = 1000). #' @param fun function(1). Function used to summarize the length of roads #' within sites location buffer (Default is `sum`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders. # nolint start #' @note Unit is km / sq km. The returned `data.frame` object contains a @@ -1656,7 +1708,8 @@ calc_sedac_groads <- function( locs = NULL, locs_id = NULL, radius = 1000, - fun = sum, + fun = "sum", + geom = FALSE, ...) { #### check for null parameters if (radius <= 0) { @@ -1667,7 +1720,8 @@ calc_sedac_groads <- function( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] @@ -1705,8 +1759,13 @@ calc_sedac_groads <- function( ) #### time period from_clip$description <- "1980 - 2010" - #### reorder - from_clip_reorder <- from_clip[, c(1, 4, 2, 3)] + if (geom) { + from_clip$geometry <- sites_list[[2]]$geometry + from_clip_reorder <- from_clip[, c(1, 5, 4, 2, 3)] + } else { + #### reorder + from_clip_reorder <- from_clip[, c(1, 4, 2, 3)] + } return(from_clip_reorder) } @@ -1724,6 +1783,12 @@ calc_sedac_groads <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. #' @param ... Placeholders #' @author Mitchell Manware #' @seealso [calc_geos()], [process_merra2()] @@ -1742,13 +1807,15 @@ calc_merra2 <- function( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1771,7 +1838,8 @@ calc_merra2 <- function( variable = 1, time = merra2_time, time_type = "hour", - level = merra2_level + level = merra2_level, + ... ) #### return data.frame return(data.frame(sites_extracted)) @@ -1790,6 +1858,13 @@ calc_merra2 <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. +#' @param ... Placeholders. #' @author Mitchell Manware #' @seealso [`process_gridmet()`] #' @return a data.frame object @@ -1805,13 +1880,16 @@ calc_gridmet <- function( locs, locs_id = NULL, radius = 0, - fun = "mean") { + fun = "mean", + geom = FALSE, + ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1825,7 +1903,8 @@ calc_gridmet <- function( fun = fun, variable = 1, time = 2, - time_type = "date" + time_type = "date", + ... ) #### return data.frame return(data.frame(sites_extracted)) @@ -1845,6 +1924,13 @@ calc_gridmet <- function( #' (Default = 0). #' @param fun character(1). Function used to summarize multiple raster cells #' within sites location buffer (Default = `mean`). +#' @param geom logical(1). Should the geometry of `locs` be returned in the +#' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain +#' polygon geometries, the `$geometry` column in the returned data frame may +#' make the `data.frame` difficult to read due to long geometry strings. The +#' coordinate reference system of the `$geometry` is the coordinate +#' reference system of `from`. +#' @param ... Placeholders. #' @note #' TerraClimate data has monthly temporal resolution, so the `$time` column #' will contain the year and month in YYYYMM format (ie. January, 2018 = @@ -1864,13 +1950,16 @@ calc_terraclimate <- function( locs, locs_id = NULL, radius = 0, - fun = "mean") { + fun = "mean", + geom = geom, + ...) { #### prepare locations list sites_list <- calc_prepare_locs( from = from, locs = locs, locs_id = locs_id, - radius = radius + radius = radius, + geom = geom ) sites_e <- sites_list[[1]] sites_id <- sites_list[[2]] @@ -1884,7 +1973,8 @@ calc_terraclimate <- function( fun = fun, variable = 1, time = 2, - time_type = "yearmonth" + time_type = "yearmonth", + ... ) #### return data.frame return(data.frame(sites_extracted)) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 78546eac..ca2927ff 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -42,8 +42,8 @@ calc_setcolumns <- function( names_return[time_index] <- "time" #### description (for time period coverage) description_index <- which(names_from == "description") - stopifnot(length(geometry_index) <= 1) - names_return[geometry_index] <- "description" + stopifnot(length(description_index) <= 1) + names_return[description_index] <- "description" #### geometry geometry_index <- which(names_from == "geometry") stopifnot(length(geometry_index) <= 1) @@ -223,6 +223,11 @@ calc_prepare_locs <- function( geom = FALSE) { #### check for null parameters check_for_null_parameters(mget(ls())) + if (!locs_id %in% names(locs)) { + stop(sprintf("locs should include columns named %s.\n", + locs_id) + ) + } #### prepare sites sites_e <- process_locs_vector( locs, diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index d129a1bc..edde6b56 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -157,6 +157,21 @@ testthat::test_that("calc_ecoregion works well", { testthat::expect_equal( sum(unlist(ecor_res[, dum_cn])), 2L ) + + testthat::expect_no_error( + ecor_geom <- calc_ecoregion( + from = erras, + locs = site_faux, + locs_id = "site_id", + geom = TRUE + ) + ) + testthat::expect_equal( + ncol(ecor_geom), 5 + ) + testthat::expect_true( + "geometry" %in% names(ecor_geom) + ) }) @@ -965,6 +980,28 @@ testthat::test_that("calc_gmted returns expected.", { } } } + testthat::expect_no_error( + gmted <- process_gmted( + variable = c("Breakline Emphasis", "7.5 arc-seconds"), + testthat::test_path( + "..", "testdata", "gmted", "be75_grd" + ) + ) + ) + testthat::expect_no_error( + gmted_geom <- calc_gmted( + gmted, + ncp, + "site_id", + geom = TRUE + ) + ) + testthat::expect_equal( + ncol(gmted_geom), 4 + ) + testthat::expect_true( + "geometry" %in% names(gmted_geom) + ) }) testthat::test_that("calc_narr returns expected.", { @@ -1197,6 +1234,23 @@ testthat::test_that("groads calculation works", { # expect data.frame testthat::expect_s3_class(groads_res, "data.frame") + + # return with geometry + testthat::expect_no_error( + groads_geom <- calc_sedac_groads( + from = groads, + locs = ncp, + locs_id = "site_id", + radius = 5000, + geom = TRUE + ) + ) + testthat::expect_equal( + ncol(groads_geom), 5 + ) + testthat::expect_true( + "geometry" %in% names(groads_geom) + ) }) testthat::test_that("calc_merra2 returns as expected.", { From f4a891c1ed29b3f2ef092cb0c551c5edc7126fc1 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 1 May 2024 14:42:05 -0400 Subject: [PATCH 14/65] 0.1.8 dev (05012024) - To make calc_tri, calc_nlcd, calc_sedac_population abide by the protocol discussed in #68 --- R/calculate_covariates.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 908b2eb3..315f68c4 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -311,7 +311,7 @@ calc_nlcd <- function(from, # merge data_vect with nlcd class fractions (and reproject) new_data_vect <- cbind(data_vect_b, nlcd_at_bufs) new_data_vect <- terra::project(new_data_vect, terra::crs(locs)) - new_data_vect$time <- as.integer(year) + new_data_vect$nlcd_year <- as.integer(year) return(new_data_vect) } @@ -1033,7 +1033,7 @@ calc_tri <- function( df_tri <- dplyr::left_join(as.data.frame(locs), df_tri) } # read attr - df_tri$time <- attr(from, "tri_year") + df_tri$tri_year <- attr(from, "tri_year") return(df_tri) } @@ -1551,7 +1551,7 @@ calc_sedac_population <- function( fun = fun, variable = 3, time = 4, - time_type = "year" + time_type = "sedac_population_year" ) #### return data.frame return(data.frame(sites_extracted)) From 492f99596d3793e9d78dcf50b6d7c93337db264d Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 08:53:59 -0400 Subject: [PATCH 15/65] fix calc_lagged --- R/calculate_covariates.R | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 17b4dab5..e352d829 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -1997,6 +1997,8 @@ calc_terraclimate <- function( #' least the number of lag days before the desired start date. For example, if #' `date = c("2024-01-01", "2024-01-31)` and `lag = 1`, `from` must contain data #' starting at 2023-12-31. +#' If `from` contains geometry features, `calc_lagged` will return a column +#' with geometry features of the same name. #' \code{calc_lagged()} assumes that all columns other than `time_id`, #' `locs_id`, and fixed columns of "lat" and "lon", follow the genre, variable, #' lag, buffer radius format adopted in \code{calc_setcolumns()}. @@ -2008,9 +2010,9 @@ calc_lagged <- function( date, lag, locs_id, - time_id) { + time_id = "time") { #### check input data types - stopifnot(class(from) %in% c("data.frame", "data.table")) + stopifnot(methods::is(from, "data.frame")) #### check if time_id is not null stopifnot(!is.null(time_id)) #### return from if lag == 0 @@ -2028,28 +2030,39 @@ calc_lagged <- function( ) ) } - #### etract variables - variables <- from[ - , !(names(from) %in% c(time_id, locs_id, "lon", "lat")), - drop = FALSE - ] - #### apply lag using dplyr::lag - variables_lag <- dplyr::lag(variables, lag, default = NA) - colnames(variables_lag) <- gsub( - paste0("_[0-9]{1}_"), - paste0("_", lag, "_"), - colnames(variables_lag) - ) - #### create the return dataframe - variables_return <- cbind(from[[locs_id]], time, variables_lag) - colnames(variables_return)[1:2] <- c(locs_id, time_id) - #### identify dates of interest - date_sequence <- generate_date_sequence( - date[1], - date[2], - sub_hyphen = FALSE - ) - #### filter to dates of interest - variables_return_date <- variables_return[time %in% date_sequence, ] - return(variables_return_date) + unique_locs <- unique(from[[locs_id]]) + variables_merge <- NULL + for (u in seq_along(unique_locs)) { + from_u <- subset( + from, + from[[locs_id]] == unique_locs[u] + ) + time_u <- from_u[[time_id]] + #### extract variables + variables <- from_u[ + , !(names(from_u) %in% c(locs_id, time_id)), + drop = FALSE + ] + #### apply lag using dplyr::lag + variables_lag <- dplyr::lag(variables, lag, default = NA) + colnames(variables_lag) <- gsub( + paste0("_[0-9]{1}_"), + paste0("_", lag, "_"), + colnames(variables_lag) + ) + #### create the return dataframe + variables_return <- cbind(from_u[[locs_id]], time_u, variables_lag) + colnames(variables_return)[1:2] <- c(locs_id, time_id) + #### identify dates of interest + date_sequence <- generate_date_sequence( + date[1], + date[2], + sub_hyphen = FALSE + ) + #### filter to dates of interest + variables_return_date <- variables_return[time_u %in% date_sequence, ] + #### merge with other locations + variables_merge <- rbind(variables_merge, variables_return_date) + } + return(variables_merge) } From b1f8b425115905a9b5425ec1c6dadfb89509aed3 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 11:12:46 -0400 Subject: [PATCH 16/65] update README.md and vignettes/download_functions.Rmd --- README.md | 171 ++++++++++++++++--------------- vignettes/download_functions.Rmd | 82 +++++++-------- 2 files changed, 130 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index f4a40a53..a3408139 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# **A** **M**achine for **D**ata, **E**nvironments, and **U**ser **S**etup for common environmental and climate health datasets - +# `amadeus`: a mechanism for data, environments, and user setup for common environmental and climate health datasets in R [![R-CMD-check](https://github.com/NIEHS/amadeus/actions/workflows/check-standard.yaml/badge.svg)](https://github.com/NIEHS/amadeus/actions/workflows/check-standard.yaml) [![cov](https://NIEHS.github.io/amadeus/badges/coverage.svg)](https://github.com/NIEHS/amadeus/actions) @@ -10,135 +9,147 @@ experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](h `amadeus` is an R package developed to improve and expedite users' access to large, publicly available geospatial datasets. The functions in `amadeus` allow users to download and import cleaned geospatial data directly in R, useful for automated run scripts, analysis pipelines, and reproducible science in general. +## Installation + +`amadeus` is not yet available from CRAN, but it can be installed with the `devtools`, `remotes`, or `pak` packages. + +``` +devtools::install_github("NIEHS/amadeus") +``` + +``` +remotes::install_github("NIEHS/amadeus") +``` + +``` +pak::pak("NIEHS/amadeus") +``` + +## Contribution + +To add or edit functionality for new data sources or datasets, open a [Pull request](https://github.com/NIEHS/amadeus/pulls) into the main branch with a detailed description of the proposed changes. Pull requests must pass all status checks, and then will be approved or rejected by `amadeus`'s authors. + +Utilize [Issues](https://github.com/NIEHS/amadeus/issues) to notify the authors of bugs, questions, or recommendations. Identify each issue with the appropriate label to help ensure a timely response. + ## Download -`download_data()` accesses and downloads raw geospatial data from a variety of open source data repositories. The function is a wrapper that calls source-specific download functions, each of which account for the source's unique combination of URL, file naming conventions, and data types. Download functions cover the following sources: +`download_data` accesses and downloads raw geospatial data from a variety of open source data repositories. The function is a wrapper that calls source-specific download functions, each of which account for the source's unique combination of URL, file naming conventions, and data types. Download functions cover the following sources: | Source | Data Type | Genre | | :--- | :--- | :--- | -| [US EPA Air Data Pre-Generated Data Files](https://aqs.epa.gov/aqsweb/airdata/download_files.html) | CSV | Air Pollution | -| [US EPA Ecoregions](https://www.epa.gov/eco-research/ecoregion) | Shapefile | Climate Regions | -| [NASA Goddard Earth Observing System Composition Forcasting (GEOS-CF)](https://gmao.gsfc.nasa.gov/GEOS_systems/) | netCDF | Atmosphere, Meteorology | -| [USGS Global Multi-resolution Terrain Elevation Data (GMTED2010)](https://www.usgs.gov/coastal-changes-and-impacts/gmted2010) | ESRI ASCII Grid | Elevation | +| [Climatology Lab TerraClimate](https://www.climatologylab.org/terraclimate.html) | netCDF | Climate, Water | +| [Climatology Lab GridMet](https://www.climatologylab.org/gridmet.html) | netCDF | Meteorology | | [Köppen-Geiger Climate Classification (Beck et al., 2018)](https://www.nature.com/articles/sdata2018214) | GeoTIFF | Climate Classification | -| [NASA Modern-Era Retrospective analysis for Research and Applications, Version 2 (MERRA-2)](https://www.nature.com/articles/sdata2018214) | netCDF | Atmosphere, Meteorology | -| [NASA Moderate Resolution Imaging Spectroradiometer (MODIS)](https://modis.gsfc.nasa.gov/data/) | HDF | Atmosphere, Meteorology, Land Use, Satellite | -| [NOAA NCEP North American Regional Reanalysis (NARR)](https://psl.noaa.gov/data/gridded/data.narr.html) | netCDF | Atmosphere, Meteorology | | [MRLC Consortium National Land Cover Database (NLCD)](https://www.mrlc.gov/data) | GeoTIFF | Land Use | -| [NOAA Hazard Mapping System Fire and Smoke Product](https://www.ospo.noaa.gov/Products/land/hms.html#0) | Shapefile, KML | Wildfire Smoke | -| [NASA SEDAC Global Roads Open Access Data Set](https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/data-download) | Shapefile, Geodatabase | Roadways | +| [NASA Moderate Resolution Imaging Spectroradiometer (MODIS)](https://modis.gsfc.nasa.gov/data/) | HDF | Atmosphere, Meteorology, Land Use, Satellite | +| [NASA Modern-Era Retrospective analysis for Research and Applications, Version 2 (MERRA-2)](https://www.nature.com/articles/sdata2018214) | netCDF | Atmosphere, Meteorology | | [NASA SEDAC UN WPP-Adjusted Population Density](https://sedac.ciesin.columbia.edu/data/set/gpw-v4-population-density-adjusted-to-2015-unwpp-country-totals-rev11) | GeoTIFF, netCDF | Population | -| [Climatology Lab TerraClimate](https://www.climatologylab.org/terraclimate.html) | netCDF | Climate, Water | -| [Climatology Lab GridMet](https://www.climatologylab.org/gridmet.html) | netCDF | Meteorology | +| [NASA SEDAC Global Roads Open Access Data Set](https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/data-download) | Shapefile, Geodatabase | Roadways | +| [NASA Goddard Earth Observing System Composition Forcasting (GEOS-CF)](https://gmao.gsfc.nasa.gov/GEOS_systems/) | netCDF | Atmosphere, Meteorology | +| [NOAA Hazard Mapping System Fire and Smoke Product](https://www.ospo.noaa.gov/Products/land/hms.html#0) | Shapefile, KML | Wildfire Smoke | +| [NOAA NCEP North American Regional Reanalysis (NARR)](https://psl.noaa.gov/data/gridded/data.narr.html) | netCDF | Atmosphere, Meteorology | +| [US EPA Air Data Pre-Generated Data Files](https://aqs.epa.gov/aqsweb/airdata/download_files.html) | CSV | Air Pollution | +| [US EPA Ecoregions](https://www.epa.gov/eco-research/ecoregion) | Shapefile | Climate Regions | +| [USGS Global Multi-resolution Terrain Elevation Data (GMTED2010)](https://www.usgs.gov/coastal-changes-and-impacts/gmted2010) | ESRI ASCII Grid | Elevation | + -See the `download_functions` vignette for a detailed description of source-specific download functions. +See the "`download_data` and NASA EarthData Account" vignette for a detailed description of source-specific download functions. -Example use of `download_data()` using NOAA NCEP North American Regional Reanalysis's (NARR) "weasd" (Daily Accumulated Snow at Surface) variable. +Example use of `download_data` using NOAA NCEP North American Regional Reanalysis's (NARR) "weasd" (Daily Accumulated Snow at Surface) variable. ``` +> directory <- "/ EXAMPLE / FILE / PATH /" > download_data( + dataset_name = "narr_monolevel", + year_start = 2022, + year_end = 2022, + variable = "weasd", -+ directory_to_save = directory_to_save, -+ data_download_acknowledgement = TRUE, ++ directory_to_save = directory, ++ acknowledgement = TRUE, + download = TRUE + ) Downloading requested files... Requested files have been downloaded. -> list.files(paste0(directory_to_save, "weasd/")) +> list.files(paste0(directory, "weasd")) [1] "weasd.2022.nc" ``` ## Process -`process_covariates()` imports and cleans raw geospatial data (downloaded with `download_data()`), and returns a single `SpatRaster` or `SpatVector` into the user's R environment. `process_covariates()` "cleans" the data by defining interpretable layer names, ensuring a coordinate reference system is present, and managing `time` data (if applicable). +`process_covariates` imports and cleans raw geospatial data (downloaded with `download_data`), and returns a single `SpatRaster` or `SpatVector` into the user's R environment. `process_covariates` "cleans" the data by defining interpretable layer names, ensuring a coordinate reference system is present, and managing `timedata (if applicable). -To avoid errors when using `process_covariates()`, **do not edit the raw downloaded data files**. Passing user-generated or edited data into `process_covariates()` may result in errors as the underlying functions are adapted to each sources' raw data file type. +To avoid errors when using `process_covariates`, **do not edit the raw downloaded data files**. Passing user-generated or edited data into `process_covariates` may result in errors as the underlying functions are adapted to each sources' raw data file type. -Example use of `process_covariates()` using the downloaded "weasd" data. +Example use of `process_covariates` using the downloaded "weasd" data. ``` > weasd <- process_covariates( + covariate = "narr", + date = c("2022-01-01", "2022-01-05"), + variable = "weasd", -+ path = path ++ path = paste0(directory, "weasd") + ) -Cleaning weasd data for year 2022... +Cleaning weasd data for January, 2022... +Detected monolevel data... Returning daily weasd data from 2022-01-01 to 2022-01-05. > weasd -class : SpatRaster +class : SpatRaster dimensions : 277, 349, 5 (nrow, ncol, nlyr) resolution : 32462.99, 32463 (x, y) extent : -16231.49, 11313351, -16231.5, 8976020 (xmin, xmax, ymin, ymax) -coord. ref. : +proj=lcc +lat_0=50 +lon_0=-107 +lat_1=50 +lat_2=50 +x_0=5632642.22547 +y_0=4612545.65137 +datum=WGS84 +units=m +no_defs -source : weasd.2022.nc:weasd -varname : weasd (Daily Accumulated Snow at Surface) -names : weasd_20220101, weasd_20220102, weasd_20220103, weasd_20220104, weasd_20220105 -unit : kg/m^2, kg/m^2, kg/m^2, kg/m^2, kg/m^2 -time : 2022-01-01 to 2022-01-05 UTC +coord. ref. : +proj=lcc +lat_0=50 +lon_0=-107 +lat_1=50 +lat_2=50 +x_0=5632642.22547 +y_0=4612545.65137 +datum=WGS84 +units=m +no_defs +source : weasd.2022.nc:weasd +varname : weasd (Daily Accumulated Snow at Surface) +names : weasd_20220101, weasd_20220102, weasd_20220103, weasd_20220104, weasd_20220105 +unit : kg/m^2, kg/m^2, kg/m^2, kg/m^2, kg/m^2 +time : 2022-01-01 to 2022-01-05 UTC ``` ## Calculate Covariates -`calc_covariates()` stems from the `beethoven` package, and the *air pollution model's (citation)* need for various types of data extracted at precise locations. `calc_covariates()`, therefore, extracts data from the "cleaned" `SpatRaster` or `SpatVector` object at user defined locations. Users can choose to buffer the locations. The function returns a `data.frame` with data extracted at all locations for each layer or row in the `SpatRaster` or `SpatVector` object, respectively. +`calc_covariates` stems from the `beethoven` package's need for various types of data extracted at precise locations. `calc_covariates`, therefore, extracts data from the "cleaned" `SpatRaster` or `SpatVector` object at user defined locations. Users can choose to buffer the locations. The function returns a `data.frame` with data extracted at all locations for each layer or row in the `SpatRaster` or `SpatVector` object, respectively. -Example of `calc_covariates()` using processed "weasd" data. +Example of `calc_covariates` using processed "weasd" data. ``` +> locs <- data.frame(lon = -78.8277, lat = 35.95013) +> locs$id <- "0001" > weasd_covar <- calc_covariates( + covariate = "narr", -+ from = weasd, ++ from = weasd_process, + locs = locs, -+ locs_id = "site_id", -+ radius = 0 ++ locs_id = "id", ++ radius = 0, ++ geom = FALSE + ) -Converting data.table to data.frame... -Projecting data to desired coordinate reference system... -Utilizing 0 meter buffer for covariate calculations. -Calculating daily weasd covariates at monolevel for date 2022-01-01... -Calculating daily weasd covariates at monolevel for date 2022-01-02... -Calculating daily weasd covariates at monolevel for date 2022-01-03... -Calculating daily weasd covariates at monolevel for date 2022-01-04... -Calculating daily weasd covariates at monolevel for date 2022-01-05... -Returning weasd covariates. +Detected `data.frame` extraction locations... +Calculating weasd covariates for 2022-01-01... +Calculating weasd covariates for 2022-01-02... +Calculating weasd covariates for 2022-01-03... +Calculating weasd covariates for 2022-01-04... +Calculating weasd covariates for 2022-01-05... +Returning extracted covariates. > weasd_covar - site_id date level weasd_0 -1 37183001488101 2022-01-01 monolevel 0.000000000 -2 37183002188101 2022-01-01 monolevel 0.000000000 -3 37063001588101 2022-01-01 monolevel 0.000000000 -4 37183001488101 2022-01-02 monolevel 0.000000000 -5 37183002188101 2022-01-02 monolevel 0.000000000 -6 37063001588101 2022-01-02 monolevel 0.000000000 -7 37183001488101 2022-01-03 monolevel 0.000000000 -8 37183002188101 2022-01-03 monolevel 0.000000000 -9 37063001588101 2022-01-03 monolevel 0.000000000 -10 37183001488101 2022-01-04 monolevel 0.000000000 -11 37183002188101 2022-01-04 monolevel 0.000000000 -12 37063001588101 2022-01-04 monolevel 0.000000000 -13 37183001488101 2022-01-05 monolevel 0.003906250 -14 37183002188101 2022-01-05 monolevel 0.001953125 -15 37063001588101 2022-01-05 monolevel 0.001953125 + id time weasd_0 +1 0001 2022-01-01 0.000000000 +2 0001 2022-01-02 0.000000000 +3 0001 2022-01-03 0.000000000 +4 0001 2022-01-04 0.000000000 +5 0001 2022-01-05 0.001953125 ``` -## Other sources -- Below is a list of other data sources that can be accessed via R packages for climate and weather datasets. - -| Source | Link | R package | -| :----- | :--- | :-------- | -| Monitoring Trends in Burn Severity (MTBS) | https://www.mtbs.gov/ | | -| Daymet | https://daac.ornl.gov/cgi-bin/dataset_lister.pl?p=32 | [`daymetr`](https://cran.r-project.org/web/packages/daymetr/index.html) | -| Gridmet | https://www.climatologylab.org/gridmet.html | [`climateR`](https://github.com/mikejohnson51/climateR?tab=readme-ov-file) | -| NEX-GDDP-CMIP6 | | [`RClimChange`*](https://github.com/hllauca/RClimChange/) | -| ECMWF (e.g., ERA5) | https://www.ecmwf.int/en/forecasts/dataset/ecmwf-reanalysis-v5 | [`ecmwfr`](https://cran.r-project.org/web/packages/ecmwfr/index.html) | -| Copernicus/Sentinel | https://sentinels.copernicus.eu/web/sentinel/home | [`sen2r`**](https://github.com/ranghetti/sen2r) | -| USGS and EPA Hydrology and Water Quality Data | | [`dataRetrieval`](https://cran.r-project.org/web/packages/dataRetrieval/index.html) | -| NASA and USGS Satellite Products | | [`luna`](https://github.com/rspatial/luna) | -| NOAA Operational Model Archive | [https://nomads.ncep.noaa.gov] | [`rNOMADS`](https://cran.r-project.org/web/packages/rNOMADS/) | - -* Updated longer than two years before. -** Archived; no longer maintained. - -## References +## Additional Resources + +The following R packages can also be used to access climate and weather data in R, but each differs from `amadeus` in the data sources covered or type of functionality provided. + +| Package | Source | +| :--- | :----- | +| [`dataRetrieval`](https://cran.r-project.org/web/packages/dataRetrieval/index.html) | [USGS Hydrological Data](https://www.usgs.gov/mission-areas/water-resources/data) and [EPA Water Quality Data](https://www.epa.gov/waterdata/water-quality-data) | +| [`daymetr`](https://cran.r-project.org/web/packages/daymetr/index.html) | [Daymet](https://daac.ornl.gov/cgi-bin/dataset_lister.pl?p=32) | +| [`ecmwfr`](https://cran.r-project.org/web/packages/ecmwfr/index.html) | [ECMWF Reanalysis v5 (ERA5)](https://www.ecmwf.int/en/forecasts/dataset/ecmwf-reanalysis-v5) | +| [`RClimChange`[^1]](https://github.com/hllauca/RClimChange/) | [NASA Earth Exchange Global Daily Downscaled Projections (NEX-GDDP-CMIP6)](https://www.nccs.nasa.gov/services/data-collections/land-based-products/nex-gddp-cmip6) | +| [`rNOMADS`](https://cran.r-project.org/web/packages/rNOMADS/) | [NOAA Operational Model Archive and Distribution System](https://nomads.ncep.noaa.gov/) | +| [`sen2r`[^2]](https://github.com/ranghetti/sen2r) | [Sentinel-2](https://sentinels.copernicus.eu/web/sentinel/missions/sentinel-2) | + +[^1]: Last updated more than two years ago. +[^2]: Archived; no longer maintained. diff --git a/vignettes/download_functions.Rmd b/vignettes/download_functions.Rmd index a69c9dd2..3bc44f93 100644 --- a/vignettes/download_functions.Rmd +++ b/vignettes/download_functions.Rmd @@ -5,32 +5,28 @@ vignette: > %\VignetteIndexEntry{download_data() and NASA EarthData Account} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} -date: "2024-01-18" +date: "2024-05-02" author: "Mitchell Manware" --- -```{r, echo = FALSE, warning = FALSE, message = FALSE} +```{r, echo = FALSE, warning = FALSE, message = FALSE, results = "hide"} # packages library(knitr) library(testthat) +library(devtools) # source functions -sapply( - list.files( - "../R/", - pattern = "download", - full.names = TRUE - ), - source -) +devtools::load_all("../") ``` ## Motivation -The `data_download()` function was developed to improve researchers' access to publicly available geospatial datasets. Although the data are already available online, using a web browser to manually download hundreds or thousands of data files is slow, arduous, and not (efficiently) repeatable. Additionally, as users may not be familiar with creating download recipes in Bash (Unix shell), `data_download()` allows researchers to download data directly with R, a common coding language in the field of environmental health research. Finally, function-izing data downloads is useful for repeated code or automated analysis pipelines. +The `data_download` function was developed to improve researchers' access to publicly available geospatial datasets. Although the data are already available online, using a web browser to manually download hundreds or thousands of data files is slow, arduous, and not efficiently repeatable. A function which downloads raw data files onto the user's machine allows for re + +Additionally, as users may not be familiar with creating download recipes in Bash (Unix shell), `data_download` allows researchers to download data directly with R, a common coding language in the field of environmental health research. Finally, function-izing data downloads is useful for repeated code or automated analysis pipelines. -## data_download() +## data_download -`data_download()` is capable of accessing and downloading geospatial datasets, collections, and variables from a variety of sources. This wrapper function calls on source-specific data download functions, each utilizing a unique combination of input parameters, host URL, naming convention, and data formats. +`data_download` is capable of accessing and downloading geospatial datasets, collections, and variables from a variety of sources. This wrapper function calls on source-specific data download functions, each utilizing a unique combination of input parameters, host URL, naming convention, and data formats. ```{r, echo = FALSE} functions <- c( @@ -102,7 +98,7 @@ kable(functions_sources, ) ``` -It is important to note that `data_download()` calls a source-specific function based on the `dataset_name =` parameter. Using the source-specific function directly will return the exact same data (**if the parameters are the same**), but `data_download()` may be beneficial if using a `for` loop to download data from various sources. For example, `download_data(dataset_name = "hms", ...)` will return the same data as `download_hms(...)` assuming that `...` indicates the same parameters. +It is important to note that `data_download` calls a source-specific function based on the `dataset_name` parameter. Using the source-specific function directly will return the exact same data (**if the parameters are the same**), but the error messages produced by each differ slightly/ ### Parameters @@ -113,7 +109,7 @@ names(formals(download_hms)) names(formals(download_narr_monolevel)) ``` -The two functions have different required parameters because `download_hms()` uses a daily temporal resolution while `download_narr_monolevel()` uses yearly, but they share some common, standard parameters. +The two functions have different required parameters because `download_hms` uses a daily temporal resolution while `download_narr_monolevel` uses yearly, but they share some common, standard parameters. #### Standard parameters @@ -152,11 +148,11 @@ colnames(parameter_descriptions) <- c("Parameter", "Type", "Description") kable(parameter_descriptions) ``` -Additionally, the `dataset_name =` parameter must be specified when using `data_download()`, but is assumed when using a source-specific download function. +Additionally, the `dataset_name` parameter must be specified when using `data_download`, but is assumed when using a source-specific download function. ### Function Structure -Although each source-specific download function is unique, they all follow the same general structure. The following chunks of code have been **adopted** from `download_hms()` to demonstrate the functions' structure. +Although each source-specific download function is unique, they all follow the same general structure. The following chunks of code have been adopted from `download_hms` to demonstrate the functions' structure. [1. Clean Parameters] @@ -196,7 +192,7 @@ date_sequence #### 2. Generate download URLs -The URL base and pattern are identified by manually inspecting the download link on the source-specific web page. `download_hms()` utilizes the year, month, date, and data format to generate the download url. +The URL base and pattern are identified by manually inspecting the download link on the source-specific web page. `download_hms` utilizes the year, month, date, and data format to generate the download url. ```{r} # user defined parameters @@ -257,13 +253,13 @@ A download URL is created for each date in `date_sequence` based on the fixed pa #### 4. Initiate "...commands.txt" -An important aspect of the data download function is its `sink()...cat()...sink()` structure. Rather than using the `utils::download.file()` function, a text file is created to store all of the download commands generated from the URLs and file names. +An important aspect of the data download function is its `sink...cat...sink` structure. Rather than using the `utils::download.file` function, a text file is created to store all of the download commands generated from the URLs and file names. This structure is utilized for several reasons: - Consistent structure for all the source-specific download functions. -- The `download.file()` function cannot accept vectors of URLs and destination files for downloading. An additional `for` loop to download data will increase function complexity and may reduce performance. +- The `download.file` function cannot accept vectors of URLs and destination files for downloading. An additional `for` loop to download data will increase function complexity and may reduce performance. - Writing commands in Bash (Unix shell) script allows for specific arguments and flags. @@ -311,7 +307,7 @@ for (d in seq_along(date_sequence)) { #### 6. Finalize "...commands.txt" -After the download commands have been concatenated to the commands text file, a second `sink()` command is run to finalize the file and stop the appending of R output. +After the download commands have been concatenated to the commands text file, a second `sink` command is run to finalize the file and stop the appending of R output. ```{r, eval = FALSE} sink() @@ -330,7 +326,7 @@ system_command <- paste0( system_command ``` -Running the `system_command` deploys a "helper function", `download_run()`, a function created to reduce repeated code across the source-specific download functions. The function takes two parameters, `system_command =`, which indicates the command to be run, and `download =`, a user-defined logical parameter. +Running the `system_command` deploys a "helper function", `download_run`, a function created to reduce repeated code across the source-specific download functions. The function takes two parameters, `system_command`, which indicates the command to be run, and `download`, a user-defined logical parameter. ```{r} download_run <- function( @@ -347,7 +343,7 @@ download_run <- function( } ``` -The data download is initiated by running `download_run()` with the system command identified and `download = TRUE`. +The data download is initiated by running `download_run` with the system command identified and `download = TRUE`. ```{r, eval = FALSE} download_run( @@ -369,9 +365,9 @@ paste0("hms_smoke_Shapefile_", date_sequence, ".zip") #### 8. Zip files (if applicable) {#zip-files-if-applicable} -All of the source-specific data download functions follow this general pattern, but those functions which download zip files require additional steps to inflate and remove the downloaded zip files, if desired. Each of these two steps are run by helper functions, and they are run by the user-defined `unzip = ` and `remove_zip = ` parameters in `data_download()`. +All of the source-specific data download functions follow this general pattern, but those functions which download zip files require additional steps to inflate and remove the downloaded zip files, if desired. Each of these two steps are run by helper functions, and they are run by the user-defined `unzip` and `remove_zip` parameters in `data_download`. -`download_unzip()` inflates zip files if `unzip = TRUE`, and skips inflation if `unzip = FALSE`. +`download_unzip` inflates zip files if `unzip = TRUE`, and skips inflation if `unzip = FALSE`. ```{r} download_unzip <- @@ -394,7 +390,7 @@ download_unzip <- } ``` -`download_remove_zips()` removes the downloaded zip files if `remove = TRUE`, and skips removal if `remove = FALSE`. +`download_remove_zips` removes the downloaded zip files if `remove = TRUE`, and skips removal if `remove = FALSE`. ```{r} download_remove_zips <- @@ -464,7 +460,7 @@ The previous outline successfully cleaned parameters, generated URLs, and downlo ### Helper functions -`read_commands()` imports the commands text file and converts the data frame to a vector. +`read_commands` imports the commands text file and converts the data frame to a vector. ```{r} read_commands <- function( @@ -475,7 +471,7 @@ read_commands <- function( } ``` -`extract_urls()` extracts each download URL from the vector of commands. The `position =` of the URL within the download command is determined in [5. Concatenate download commands]. +`extract_urls` extracts each download URL from the vector of commands. The `position` of the URL within the download command is determined in [5. Concatenate download commands]. ```{r} # function to extract URLs from vector @@ -495,7 +491,7 @@ extract_urls <- function( } ``` -`check_url_status()` is the most important of the download test "helper" functions. This function utilizes `httr::HEAD()` and `httr::GET()` to check the HTTP response status of a given URL. The desired HTTP response status is 200, which means the URL is valid and accessible. `check_url_status()` returns a logical value to indicate whether the URL returns HTTP status 200 (`TRUE`) or not (`FALSE`). For more information on HTTP status', see [HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). +`check_url_status` is the most important of the download test "helper" functions. This function utilizes `httr::HEAD` and `httr::GET` to check the HTTP response status of a given URL. The desired HTTP response status is 200, which means the URL is valid and accessible. `check_url_status` returns a logical value to indicate whether the URL returns HTTP status 200 (`TRUE`) or not (`FALSE`). For more information on HTTP status', see [HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). ```{r} check_url_status <- function( @@ -512,7 +508,7 @@ check_url_status <- function( } ``` -`check_urls()` applies `check_url_status()` to a random sample of URLs extracted by `extract_urls()`. The sample size will vary based on the dataset and spatio-temporal parameters being tested. The function returns a logical vector containing the output from `check_url_status()`. +`check_urls` applies `check_url_status` to a random sample of URLs extracted by `extract_urls`. The sample size will vary based on the dataset and spatio-temporal parameters being tested. The function returns a logical vector containing the output from `check_url_status`. ```{r} check_urls <- function( @@ -537,7 +533,7 @@ check_urls <- function( ### testthat -To demonstrate a test in action, test the URLs generated by `download_data()` for the NOAA HMS Smoke dataset. +To demonstrate a test in action, test the URLs generated by `download_data` for the NOAA HMS Smoke dataset. For more information see [testthat](https://testthat.r-lib.org/). @@ -626,9 +622,9 @@ testthat::test_that( ``` -Although the `testthat::test_that(...)` chunk contains 32 lines of code, the unit test is performed by `expect_true(all(url_status))`. In words, this line is expecting (`expect_true()`) that all (`all()`) of the sampled URLs return HTTP response status 200 (`url_status`). Since this expectation was met, the test passed! +Although the `testthat::test_that(...)` chunk contains 32 lines of code, the unit test is performed by `expect_true(all(url_status))`. In words, this line is expecting (`expect_true`) that all (`all`) of the sampled URLs return HTTP response status 200 (`url_status`). Since this expectation was met, the test passed! -For an alternate example, we can use a start and end date that are known to not have data. As the URLs associated with these dates do not exist, we expect the function will fail. This test utilizes `expect_error()` because the `data_download()` wrapper function returns an error message if the underlying source-specific download function returns an error. +For an alternate example, we can use a start and end date that are known to not have data. As the URLs associated with these dates do not exist, we expect the function will fail. This test utilizes `expect_error()` because the `data_download` wrapper function returns an error message if the underlying source-specific download function returns an error. ```{r} testthat::test_that( @@ -659,7 +655,7 @@ testthat::test_that( ``` -This test utilizes `expect_error()` because the `data_download()` wrapper function returns an error message if the underlying source-specific download function returns an error. If we directly used the `download_hms` function, we would expect and receive an error. +This test utilizes `testthat::expect_error` because the `data_download` wrapper function returns an error message if the underlying source-specific download function returns an error. If we directly used the `download_hms` function, we would expect and receive an error. ```{r} testthat::test_that( @@ -691,7 +687,7 @@ testthat::test_that( As expected, the test passes because the NOAA HMS Smoke dataset does not contain data for January 1-2, 1800. -These unit tests are just two of many implemented on `download_data()` and the accompanying source-specific download functions, but they demonstrate how unit testing helps build stable code. +These unit tests are just two of many implemented on `download_data` and the accompanying source-specific download functions, but they demonstrate how unit testing helps build stable code. ## Download Example @@ -771,7 +767,7 @@ zips ## NASA EarthData Account -As mentioned in [Motivation], `data_download()` provides access to **publicly available** geospatial data. Although publicly available, some of the NASA data sources require a NASA EarthData Account. +As mentioned in [Motivation], `data_download` provides access to publicly available geospatial data. Although publicly available, some of the NASA data sources require a NASA EarthData Account. For example, the UN WPP-Adjusted population density data from NASA Socioeconomic Data and Applications Center (SEDAC) requires an EarthData account. Without an EarthData Account and the prerequisite files prepared, the data download functions will return an error. @@ -855,13 +851,13 @@ if (.Platform$OS.type == "unix") { } ``` -Create a file named `.netrc` with `file.create()`. +Create a file named `.netrc` with `file.create`. ```{r, eval = FALSE} file.create(".netrc") ``` -Open a connection to `.netrc` with `sink()`. Write the line `machine urs...` replacing `YOUR_USERNAME` and `YOUR_PASSWORD` with your NASA EarthData username and password, respectively. After writing the line, close the connection with `sink()` again. +Open a connection to `.netrc` with `sink`. Write the line `machine urs...` replacing `YOUR_USERNAME` and `YOUR_PASSWORD` with your NASA EarthData username and password, respectively. After writing the line, close the connection with `sink` again. ```{r, eval = FALSE} sink(".netrc") @@ -912,7 +908,7 @@ if (.Platform$OS.type == "unix") { } ``` -Create a file named `.netrc` with `file.create()`. +Create a file named `.netrc` with `file.create`. ```{r, eval = FALSE} file.create(".urs_cookies") @@ -942,13 +938,13 @@ if (.Platform$OS.type == "unix") { } ``` -Create a file named ".dodsrc" with `file.create()` +Create a file named ".dodsrc" with `file.create`. ```{r, eval = FALSE} file.create(".dodsrc") ``` -Open a connection to `.dodsrc` with `sink()`. Write the lines beginning with `HTTP.`, replacing `YOUR_USERNAME` and `YOUR_PASSWORD` with your NASA EarthData username and password, respectively. After writing the line, close the connection with `sink()` again. +Open a connection to `.dodsrc` with `sink`. Write the lines beginning with `HTTP.`, replacing `YOUR_USERNAME` and `YOUR_PASSWORD` with your NASA EarthData username and password, respectively. After writing the line, close the connection with `sink` again. ```{r, eval = FALSE} sink(".dodsrc") @@ -984,7 +980,7 @@ paste0( ) ``` -If working on a **Windows** machine, copy the `.dodsrc` file to the project working directory. Replace `YOUR_WORKING_DIRECTORY` with the absolute path to the project working directory. +If working on a Windows machine, copy the `.dodsrc` file to the project working directory. Replace `YOUR_WORKING_DIRECTORY` with the absolute path to the project working directory. ```{r} if (.Platform$OS.type == "windows") { @@ -1055,7 +1051,7 @@ sedac_files ## Code Example -The following is the entire R code used to create `download_hms()`. +The following is the entire R code used to create `download_hms`. ```{r} download_hms From 84b0d1262fac3faf2593308fc200d13d2a01859d Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 11:21:56 -0400 Subject: [PATCH 17/65] update README.md 2 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a3408139..889ec98c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# `amadeus`: a mechanism for data, environments, and user setup for common environmental and climate health datasets in R +# amadeus [![R-CMD-check](https://github.com/NIEHS/amadeus/actions/workflows/check-standard.yaml/badge.svg)](https://github.com/NIEHS/amadeus/actions/workflows/check-standard.yaml) [![cov](https://NIEHS.github.io/amadeus/badges/coverage.svg)](https://github.com/NIEHS/amadeus/actions) @@ -7,7 +7,7 @@ [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) -`amadeus` is an R package developed to improve and expedite users' access to large, publicly available geospatial datasets. The functions in `amadeus` allow users to download and import cleaned geospatial data directly in R, useful for automated run scripts, analysis pipelines, and reproducible science in general. +`amadeus` is **a** **m**ech**a**nism for **d**ata, **e**nvironments, and **u**ser **s**etup for common environmental and climate health datasets in R. `amadeus` has been developed to improve access to and utility with large scale, publicly available environmental data in R. ## Installation From c264ed09f50a370191afd8871a03aa7fe9c1d757 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 11:43:46 -0400 Subject: [PATCH 18/65] update README.md 3 --- README.md | 4 ++++ vignettes/images/readme_issues.png | Bin 0 -> 464413 bytes 2 files changed, 4 insertions(+) create mode 100644 vignettes/images/readme_issues.png diff --git a/README.md b/README.md index 889ec98c..04755a49 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ To add or edit functionality for new data sources or datasets, open a [Pull requ Utilize [Issues](https://github.com/NIEHS/amadeus/issues) to notify the authors of bugs, questions, or recommendations. Identify each issue with the appropriate label to help ensure a timely response. +
+ +
+ ## Download `download_data` accesses and downloads raw geospatial data from a variety of open source data repositories. The function is a wrapper that calls source-specific download functions, each of which account for the source's unique combination of URL, file naming conventions, and data types. Download functions cover the following sources: diff --git a/vignettes/images/readme_issues.png b/vignettes/images/readme_issues.png new file mode 100644 index 0000000000000000000000000000000000000000..5e90a62887a1b0326b1e030cb824798f595588db GIT binary patch literal 464413 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

=$w%F0?X4YWG)tahZqXV~I5h0kc2ZlP)x=Pk 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 z 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

-Yt_U1r$LbY~E2*l~-L`#RkLSkjT}h0UW@0MB_| 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�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 O9}`0}gK|B$`~L& Date: Thu, 2 May 2024 11:46:32 -0400 Subject: [PATCH 19/65] udpate README.md 4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04755a49..f89746c8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To add or edit functionality for new data sources or datasets, open a [Pull requ Utilize [Issues](https://github.com/NIEHS/amadeus/issues) to notify the authors of bugs, questions, or recommendations. Identify each issue with the appropriate label to help ensure a timely response.

## Download From 57d117f78673cba389b2355e21390022bbfe78de Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 12:41:09 -0400 Subject: [PATCH 20/65] update calc_time with as.POSIXlt wrap; calc_time_check for all calc_* functions --- R/calculate_covariates.R | 25 +++++++--- R/calculate_covariates_auxiliary.R | 58 +++++++++++++++++----- README.md | 4 +- tests/testthat/test-calculate_covariates.R | 14 ++++++ vignettes/download_functions.Rmd | 2 +- 5 files changed, 81 insertions(+), 22 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index e352d829..b6a1bf5b 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -348,12 +348,13 @@ calc_nlcd <- function(from, ) names(nlcd_at_bufs) <- new_names # merge locs_df with nlcd class fractions - new_data_vect <- cbind(locs_df, year, nlcd_at_bufs) + new_data_vect <- cbind(locs_df, as.integer(year), nlcd_at_bufs) if (geom) { names(new_data_vect)[1:3] <- c(locs_id, "geometry", "time") } else { names(new_data_vect)[1:2] <- c(locs_id, "time") } + calc_check_time(covar = new_data_vect, POSIXt = FALSE) return(new_data_vect) } @@ -526,9 +527,10 @@ calc_modis_daily <- function( ) } if (!"time" %in% names(locs)) { - locs$time <- date + locs$time <- as.POSIXlt(date) } - + stopifnot(methods::is(locs$time, "POSIXt")) + extract_with_buffer <- function( points, surf, @@ -579,6 +581,7 @@ calc_modis_daily <- function( # multiple columns will get proper names name_range <- seq(ncol(extracted) - name_offset + 1, ncol(extracted), 1) colnames(extracted)[name_range] <- name_extracted + calc_check_time(covar = extracted, POSIXt = TRUE) return(extracted) } @@ -864,7 +867,7 @@ calc_temporal_dummies <- dt_month_dum, dt_wday_dum ) - + calc_check_time(covar = locs_dums, POSIXt = TRUE) return(locs_dums) } @@ -1011,7 +1014,7 @@ The result may not be accurate.\n", attr(res_sedc, "sedc_bandwidth") <- sedc_bandwidth attr(res_sedc, "sedc_threshold") <- sedc_bandwidth * 2 - + calc_check_time(covar = extracted, POSIXt = TRUE) return(res_sedc) } @@ -1096,6 +1099,7 @@ calc_tri <- function( } # read attr df_tri$time <- attr(from, "tri_year") + calc_check_time(covar = df_tri, POSIXt = FALSE) return(df_tri) } @@ -1129,7 +1133,7 @@ calc_nei <- function( # spatial join locs_re <- terra::project(locs, terra::crs(from)) locs_re <- terra::intersect(locs_re, from) - + calc_check_time(covar = locs_re, POSIXt = FALSE) return(locs_re) } @@ -1320,6 +1324,7 @@ calc_hms <- function( layer_name, " covariates.\n" )) + calc_check_time(covar = sites_extracted_ordered, POSIXt = TRUE) #### return data.frame return(data.frame(sites_extracted_ordered)) } @@ -1428,6 +1433,7 @@ calc_gmted <- function( names(sites_extracted) <- c(locs_id, "time", variable_name) } + calc_check_time(covar = sites_extracted, POSIXt = FALSE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1503,6 +1509,7 @@ calc_narr <- function( level = narr_level, ... ) + calc_check_time(covar = sites_extracted, POSIXt = TRUE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1573,6 +1580,7 @@ calc_geos <- function( level = 2, ... ) + calc_check_time(covar = sites_extracted, POSIXt = TRUE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1655,6 +1663,7 @@ calc_sedac_population <- function( time_type = "year", ... ) + calc_check_time(covar = sites_extracted, POSIXt = FALSE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1841,6 +1850,7 @@ calc_merra2 <- function( level = merra2_level, ... ) + calc_check_time(covar = sites_extracted, POSIXt = TRUE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1906,6 +1916,7 @@ calc_gridmet <- function( time_type = "date", ... ) + calc_check_time(covar = sites_extracted, POSIXt = TRUE) #### return data.frame return(data.frame(sites_extracted)) } @@ -1976,6 +1987,7 @@ calc_terraclimate <- function( time_type = "yearmonth", ... ) + calc_check_time(covar = sites_extracted, POSIXt = FALSE) #### return data.frame return(data.frame(sites_extracted)) } @@ -2064,5 +2076,6 @@ calc_lagged <- function( #### merge with other locations variables_merge <- rbind(variables_merge, variables_return_date) } + calc_check_time(covar = sites_extracted, POSIXt = TRUE) return(variables_merge) } diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index ca2927ff..1644f72a 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -256,8 +256,9 @@ calc_prepare_locs <- function( #' value. #' @param time Time value #' @param format Type of time to return in the `$time` column. Can be -#' "timeless" (ie. GMTED data), "date" (ie. NARR data), "hour", (ie. GEOS data), -#' "year" (ie. SEDAC population data), or "yearmonth" (ie. TerraClimate data). +#' "timeless" (ie. Ecoregions data), "date" (ie. NARR data), "hour" +#' (ie. GEOS data), "year" (ie. SEDAC population data), or "yearmonth" +#' (ie. TerraClimate data). #' @return a `Date`, `POSIXt`, or `integer` object based on `format =` #' @keywords internal #' @export @@ -267,19 +268,22 @@ calc_time <- function( if (format == "timeless") { return() } else if (format == "date") { - return_time <- as.Date( + return_time <- as.POSIXlt( time, - format = "%Y%m%d" + format = "%Y%m%d", + tz = "UTC" ) } else if (format == "hour") { - return_time <- ISOdatetime( - year = substr(time[1], 1, 4), - month = substr(time[1], 5, 6), - day = substr(time[1], 7, 8), - hour = substr(time[2], 1, 2), - min = substr(time[2], 3, 4), - sec = substr(time[2], 5, 6), - tz = "UTC" + return_time <- as.POSIXlt( + ISOdatetime( + year = substr(time[1], 1, 4), + month = substr(time[1], 5, 6), + day = substr(time[1], 7, 8), + hour = substr(time[2], 1, 2), + min = substr(time[2], 3, 4), + sec = substr(time[2], 5, 6), + tz = "UTC" + ) ) } else if (format %in% c("yearmonth", "year")) { return_time <- as.integer(time) @@ -287,7 +291,35 @@ calc_time <- function( return(return_time) } -#' Peform covariate extraction +#' Check time values +#' @description +#' Check the time values within calculated covariates `data.frame` +#' @param covar data.frame(1). Calculated covariates `data.frame`. +#' @param POSIXt logical(1). Should the time values in `covar` be of class +#' `POSIXt`? If `FALSE`, the time values will be checked for integer class +#' (year and year-month). +#' @return NULL +#' @keywords internal +#' @export +calc_check_time <- function( + covar, + POSIXt = TRUE +) { + stopifnot(methods::is(covar, "data.frame")) + if ("time" %in% names(covar)) { + if (POSIXt) { + stopifnot(methods::is(covar$time), "POSIXt") + } else { + stopifnot(methods::is(covar$time), "integer") + } + } else { + message( + "`$time` not detected in `data.frame` provided.\n" + ) + } +} + +#' Perform covariate extraction #' @description #' Extract covariate values from `SpatRaster` object passed from #' \code{process_*()}. diff --git a/README.md b/README.md index f89746c8..254f9d4e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To add or edit functionality for new data sources or datasets, open a [Pull requ Utilize [Issues](https://github.com/NIEHS/amadeus/issues) to notify the authors of bugs, questions, or recommendations. Identify each issue with the appropriate label to help ensure a timely response.
- +
## Download @@ -57,7 +57,7 @@ Utilize [Issues](https://github.com/NIEHS/amadeus/issues) to notify the authors | [USGS Global Multi-resolution Terrain Elevation Data (GMTED2010)](https://www.usgs.gov/coastal-changes-and-impacts/gmted2010) | ESRI ASCII Grid | Elevation | -See the "`download_data` and NASA EarthData Account" vignette for a detailed description of source-specific download functions. +See the "download_data and NASA EarthData Account" vignette for a detailed description of source-specific download functions. Example use of `download_data` using NOAA NCEP North American Regional Reanalysis's (NARR) "weasd" (Daily Accumulated Snow at Surface) variable. diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index edde6b56..836f426a 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -1602,3 +1602,17 @@ testthat::test_that("calc_covariates wrapper works", { ) } }) + +testthat::test_that("calc_check_time identifies missing `time` column.", { + testthat::expect_error( + # provide integer instead of data.frame to provoke error + calc_check_time(12, TRUE) + ) + testthat::expect_message( + # provide data.frame without time to provoke message + calc_check_time( + data.frame(x = 10, y = 20), + true + ) + ) +}) diff --git a/vignettes/download_functions.Rmd b/vignettes/download_functions.Rmd index 3bc44f93..b364bb0f 100644 --- a/vignettes/download_functions.Rmd +++ b/vignettes/download_functions.Rmd @@ -1,5 +1,5 @@ --- -title: "download_data() and NASA EarthData Account" +title: "download_data and NASA EarthData Account" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{download_data() and NASA EarthData Account} From 84c1155f4dbedb9890ccf4445df0e6e26877bad5 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 14:39:36 -0400 Subject: [PATCH 21/65] update documentation --- NAMESPACE | 43 ++++++++++--------- man/calc_check_time.Rd | 19 ++++++++ man/calc_ecoregion.Rd | 9 +++- man/calc_geos.Rd | 19 +++++++- man/calc_gmted.Rd | 25 ++++++++--- man/calc_gridmet.Rd | 19 +++++++- man/calc_koppen_geiger.Rd | 21 ++++++++- man/calc_lagged.Rd | 4 +- man/calc_merra2.Rd | 17 +++++++- man/calc_narr.Rd | 17 +++++++- man/calc_nlcd.Rd | 8 ++++ man/calc_prepare_locs.Rd | 5 ++- man/calc_sedac_groads.Rd | 14 +++++- man/calc_sedac_population.Rd | 8 ++++ man/calc_terraclimate.Rd | 19 +++++++- man/calc_time.Rd | 5 ++- man/calc_worker.Rd | 13 +++++- man/{download_aqs_data.Rd => download_aqs.Rd} | 17 +++----- ...ropscape_data.Rd => download_cropscape.Rd} | 8 ++-- man/download_data.Rd | 36 ++++++++-------- ...coregion_data.Rd => download_ecoregion.Rd} | 16 +++---- ...download_geos_data.Rd => download_geos.Rd} | 12 +++--- ...wnload_gmted_data.Rd => download_gmted.Rd} | 16 +++---- ...ad_gridmet_data.Rd => download_gridmet.Rd} | 8 ++-- man/{download_hms_data.Rd => download_hms.Rd} | 28 ++++++------ man/{download_huc_data.Rd => download_huc.Rd} | 6 +-- ...iger_data.Rd => download_koppen_geiger.Rd} | 22 +++++----- ...load_merra2_data.Rd => download_merra2.Rd} | 12 +++--- ...wnload_modis_data.Rd => download_modis.Rd} | 6 +-- ...vel_data.Rd => download_narr_monolevel.Rd} | 8 ++-- ...vels_data.Rd => download_narr_p_levels.Rd} | 8 ++-- man/{download_nei_data.Rd => download_nei.Rd} | 12 +++--- ...download_nlcd_data.Rd => download_nlcd.Rd} | 21 +++++---- man/{download_olm_data.Rd => download_olm.Rd} | 6 +-- ...wnload_prism_data.Rd => download_prism.Rd} | 8 ++-- ...roads_data.Rd => download_sedac_groads.Rd} | 20 ++++----- ...n_data.Rd => download_sedac_population.Rd} | 21 +++++---- man/download_setup_dir.Rd | 9 +++- ...imate_data.Rd => download_terraclimate.Rd} | 8 ++-- man/{download_tri_data.Rd => download_tri.Rd} | 8 ++-- man/process_gmted.Rd | 3 +- man/process_locs_vector.Rd | 3 +- man/process_olm.Rd | 2 +- man/process_sedac_groads.Rd | 6 ++- 44 files changed, 389 insertions(+), 206 deletions(-) create mode 100644 man/calc_check_time.Rd rename man/{download_aqs_data.Rd => download_aqs.Rd} (78%) rename man/{download_cropscape_data.Rd => download_cropscape.Rd} (94%) rename man/{download_ecoregion_data.Rd => download_ecoregion.Rd} (75%) rename man/{download_geos_data.Rd => download_geos.Rd} (87%) rename man/{download_gmted_data.Rd => download_gmted.Rd} (84%) rename man/{download_gridmet_data.Rd => download_gridmet.Rd} (82%) rename man/{download_hms_data.Rd => download_hms.Rd} (77%) rename man/{download_huc_data.Rd => download_huc.Rd} (96%) rename man/{download_koppen_geiger_data.Rd => download_koppen_geiger.Rd} (75%) rename man/{download_merra2_data.Rd => download_merra2.Rd} (90%) rename man/{download_modis_data.Rd => download_modis.Rd} (97%) rename man/{download_narr_monolevel_data.Rd => download_narr_monolevel.Rd} (70%) rename man/{download_narr_p_levels_data.Rd => download_narr_p_levels.Rd} (74%) rename man/{download_nei_data.Rd => download_nei.Rd} (79%) rename man/{download_nlcd_data.Rd => download_nlcd.Rd} (75%) rename man/{download_olm_data.Rd => download_olm.Rd} (98%) rename man/{download_prism_data.Rd => download_prism.Rd} (97%) rename man/{download_sedac_groads_data.Rd => download_sedac_groads.Rd} (76%) rename man/{download_sedac_population_data.Rd => download_sedac_population.Rd} (76%) rename man/{download_terraclimate_data.Rd => download_terraclimate.Rd} (81%) rename man/{download_tri_data.Rd => download_tri.Rd} (77%) diff --git a/NAMESPACE b/NAMESPACE index 87eb3a09..2c0ae96a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export(as_mysftime) +export(calc_check_time) export(calc_covariates) export(calc_ecoregion) export(calc_geos) @@ -31,36 +32,36 @@ export(check_mysf) export(check_mysftime) export(check_url_status) export(check_urls) -export(download_aqs_data) -export(download_cropscape_data) +export(download_aqs) +export(download_cropscape) export(download_data) -export(download_ecoregion_data) +export(download_ecoregion) export(download_epa_certificate) -export(download_geos_data) -export(download_gmted_data) -export(download_gridmet_data) -export(download_hms_data) -export(download_huc_data) -export(download_koppen_geiger_data) -export(download_merra2_data) -export(download_modis_data) -export(download_narr_monolevel_data) -export(download_narr_p_levels_data) -export(download_nei_data) -export(download_nlcd_data) -export(download_olm_data) +export(download_geos) +export(download_gmted) +export(download_gridmet) +export(download_hms) +export(download_huc) +export(download_koppen_geiger) +export(download_merra2) +export(download_modis) +export(download_narr_monolevel) +export(download_narr_p_levels) +export(download_nei) +export(download_nlcd) +export(download_olm) export(download_permit) -export(download_prism_data) +export(download_prism) export(download_remove_command) export(download_remove_zips) export(download_run) export(download_sanitize_path) -export(download_sedac_groads_data) -export(download_sedac_population_data) +export(download_sedac_groads) +export(download_sedac_population) export(download_setup_dir) export(download_sink) -export(download_terraclimate_data) -export(download_tri_data) +export(download_terraclimate) +export(download_tri) export(download_unzip) export(dt_as_mysftime) export(extract_urls) diff --git a/man/calc_check_time.Rd b/man/calc_check_time.Rd new file mode 100644 index 00000000..9099defb --- /dev/null +++ b/man/calc_check_time.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/calculate_covariates_auxiliary.R +\name{calc_check_time} +\alias{calc_check_time} +\title{Check time values} +\usage{ +calc_check_time(covar, POSIXt = TRUE) +} +\arguments{ +\item{covar}{data.frame(1). Calculated covariates \code{data.frame}.} + +\item{POSIXt}{logical(1). Should the time values in \code{covar} be of class +\code{POSIXt}? If \code{FALSE}, the time values will be checked for integer class +(year and year-month).} +} +\description{ +Check the time values within calculated covariates \code{data.frame} +} +\keyword{internal} diff --git a/man/calc_ecoregion.Rd b/man/calc_ecoregion.Rd index 1ee1d969..21d14855 100644 --- a/man/calc_ecoregion.Rd +++ b/man/calc_ecoregion.Rd @@ -4,7 +4,7 @@ \alias{calc_ecoregion} \title{Calculate ecoregions covariates} \usage{ -calc_ecoregion(from = NULL, locs, locs_id = "site_id", ...) +calc_ecoregion(from = NULL, locs, locs_id = "site_id", geom = FALSE, ...) } \arguments{ \item{from}{SpatVector(1). Output of \code{\link{process_ecoregion}}.} @@ -14,6 +14,13 @@ a unique identifier field named \code{locs_id}} \item{locs_id}{character(1). Name of unique identifier.} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders.} } \value{ diff --git a/man/calc_geos.Rd b/man/calc_geos.Rd index c64bb16d..804fc856 100644 --- a/man/calc_geos.Rd +++ b/man/calc_geos.Rd @@ -4,7 +4,15 @@ \alias{calc_geos} \title{Calculate atmospheric composition covariates} \usage{ -calc_geos(from, locs, locs_id = NULL, radius = 0, fun = "mean", ...) +calc_geos( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output of \code{process_geos()}.} @@ -20,7 +28,14 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} -\item{...}{Placeholders} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + +\item{...}{Placeholders.} } \value{ a data.frame object diff --git a/man/calc_gmted.Rd b/man/calc_gmted.Rd index 83246d0f..58c19e32 100644 --- a/man/calc_gmted.Rd +++ b/man/calc_gmted.Rd @@ -4,7 +4,15 @@ \alias{calc_gmted} \title{Calculate elevation covariates} \usage{ -calc_gmted(from, locs, locs_id = NULL, radius = 0, fun = "mean", ...) +calc_gmted( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output from \code{process_gmted()}.} @@ -20,6 +28,13 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders} } \value{ @@ -27,10 +42,10 @@ a data.frame object } \description{ Extract elevation values at point locations. Returns a \code{data.frame} -object containing \code{locs_id} and elevation variable. Elevation variable -column name reflects the elevation statistic, spatial resolution of -\code{from}, and circular buffer radius (ie. Breakline Emphasis at 7.5 -arc-second resolution with 0 meter buffer: breakline_emphasis_r75_0). +object containing \code{locs_id}, year of release, and elevation variable. +Elevation variable column name reflects the elevation statistic, spatial +resolution of \code{from}, and circular buffer radius (ie. Breakline Emphasis +at 7.5 arc-second resolution with 0 meter buffer: breakline_emphasis_r75_0). } \seealso{ \code{\link[=process_gmted]{process_gmted()}} diff --git a/man/calc_gridmet.Rd b/man/calc_gridmet.Rd index eaf9219c..2a28b10a 100644 --- a/man/calc_gridmet.Rd +++ b/man/calc_gridmet.Rd @@ -4,7 +4,15 @@ \alias{calc_gridmet} \title{Calculate gridMET covariates} \usage{ -calc_gridmet(from, locs, locs_id = NULL, radius = 0, fun = "mean") +calc_gridmet( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output from \code{process_gridmet()}.} @@ -19,6 +27,15 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} + +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + +\item{...}{Placeholders.} } \value{ a data.frame object diff --git a/man/calc_koppen_geiger.Rd b/man/calc_koppen_geiger.Rd index 9c68e241..23b08083 100644 --- a/man/calc_koppen_geiger.Rd +++ b/man/calc_koppen_geiger.Rd @@ -4,7 +4,13 @@ \alias{calc_koppen_geiger} \title{Calculate climate classification covariates} \usage{ -calc_koppen_geiger(from = NULL, locs = NULL, locs_id = "site_id", ...) +calc_koppen_geiger( + from = NULL, + locs = NULL, + locs_id = "site_id", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatVector(1). Output of \code{process_koppen_geiger()}.} @@ -14,6 +20,13 @@ a unique identifier field named \code{locs_id}} \item{locs_id}{character(1). Name of unique identifier.} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders.} } \value{ @@ -25,6 +38,12 @@ Extract climate classification values at point locations. Returns a binary (0 = point not in climate region; 1 = point in climate region) variables for each climate classification region. } +\note{ +The returned \code{data.frame} object contains a +\verb{$description} column to represent the temporal range covered by the +dataset. For more information, see +\url{https://www.nature.com/articles/sdata2018214}. +} \seealso{ \code{\link{process_koppen_geiger}} } diff --git a/man/calc_lagged.Rd b/man/calc_lagged.Rd index ed05fbfa..b067859b 100644 --- a/man/calc_lagged.Rd +++ b/man/calc_lagged.Rd @@ -4,7 +4,7 @@ \alias{calc_lagged} \title{Calculate temporally lagged covariates} \usage{ -calc_lagged(from, date, lag, locs_id, time_id) +calc_lagged(from, date, lag, locs_id, time_id = "time") } \arguments{ \item{from}{data.frame(1). A \code{data.frame} containing calculated covariates @@ -31,6 +31,8 @@ In order to calculate temporally lagged covariates, \code{from} must contain at least the number of lag days before the desired start date. For example, if \verb{date = c("2024-01-01", "2024-01-31)} and \code{lag = 1}, \code{from} must contain data starting at 2023-12-31. +If \code{from} contains geometry features, \code{calc_lagged} will return a column +with geometry features of the same name. \code{calc_lagged()} assumes that all columns other than \code{time_id}, \code{locs_id}, and fixed columns of "lat" and "lon", follow the genre, variable, lag, buffer radius format adopted in \code{calc_setcolumns()}. diff --git a/man/calc_merra2.Rd b/man/calc_merra2.Rd index 50e871ed..9a8e86ff 100644 --- a/man/calc_merra2.Rd +++ b/man/calc_merra2.Rd @@ -4,7 +4,15 @@ \alias{calc_merra2} \title{Calculate meteorological and atmospheric covariates} \usage{ -calc_merra2(from, locs, locs_id = NULL, radius = 0, fun = "mean", ...) +calc_merra2( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output of \code{process_merra2()}.} @@ -20,6 +28,13 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders} } \value{ diff --git a/man/calc_narr.Rd b/man/calc_narr.Rd index c00f832c..ae6701b6 100644 --- a/man/calc_narr.Rd +++ b/man/calc_narr.Rd @@ -4,7 +4,15 @@ \alias{calc_narr} \title{Calculate meteorological covariates} \usage{ -calc_narr(from, locs, locs_id = NULL, radius = 0, fun = "mean", ...) +calc_narr( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = FALSE, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output of \code{process_narr()}.} @@ -20,6 +28,13 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders} } \value{ diff --git a/man/calc_nlcd.Rd b/man/calc_nlcd.Rd index 97874474..37fa30a5 100644 --- a/man/calc_nlcd.Rd +++ b/man/calc_nlcd.Rd @@ -10,6 +10,7 @@ calc_nlcd( locs_id = "site_id", radius = 1000, max_cells = 1e+08, + geom = FALSE, ... ) } @@ -28,6 +29,13 @@ Higher values will expedite processing, but will increase memory usage. Maximum possible value is \code{2^31 - 1}. See \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract}} for details.} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders.} } \value{ diff --git a/man/calc_prepare_locs.Rd b/man/calc_prepare_locs.Rd index ead49266..427e4da3 100644 --- a/man/calc_prepare_locs.Rd +++ b/man/calc_prepare_locs.Rd @@ -4,7 +4,7 @@ \alias{calc_prepare_locs} \title{Prepare extraction locations} \usage{ -calc_prepare_locs(from, locs, locs_id, radius) +calc_prepare_locs(from, locs, locs_id, radius, geom = FALSE) } \arguments{ \item{from}{SpatRaster(1) or SpatVector(1). Output from @@ -19,6 +19,9 @@ Passed from \code{calc_\*()}.} \item{radius}{integer(1). Circular buffer distance around site locations. (Default = 0). Passed from \code{calc_\*()}.} + +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}.} } \value{ A \code{list} containing \code{SpatVector} and \code{data.frame} objects diff --git a/man/calc_sedac_groads.Rd b/man/calc_sedac_groads.Rd index 9286602d..2d23b4f2 100644 --- a/man/calc_sedac_groads.Rd +++ b/man/calc_sedac_groads.Rd @@ -9,7 +9,8 @@ calc_sedac_groads( locs = NULL, locs_id = NULL, radius = 1000, - fun = sum, + fun = "sum", + geom = FALSE, ... ) } @@ -27,6 +28,13 @@ containing identifier for each unique coordinate location.} \item{fun}{function(1). Function used to summarize the length of roads within sites location buffer (Default is \code{sum}).} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders.} } \value{ @@ -40,7 +48,9 @@ the total length from the area of the buffer. \code{terra::linearUnits()} is used to convert the unit of length to meters. } \note{ -Unit is km / sq km. +Unit is km / sq km. The returned \code{data.frame} object contains a +\verb{$time} column to represent the temporal range covered by the +dataset. For more information, see \url{https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/metadata}. } \seealso{ \code{\link{process_sedac_groads}} diff --git a/man/calc_sedac_population.Rd b/man/calc_sedac_population.Rd index 801b25d2..764b42a3 100644 --- a/man/calc_sedac_population.Rd +++ b/man/calc_sedac_population.Rd @@ -10,6 +10,7 @@ calc_sedac_population( locs_id = NULL, radius = 0, fun = "mean", + geom = FALSE, ... ) } @@ -27,6 +28,13 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + \item{...}{Placeholders} } \value{ diff --git a/man/calc_terraclimate.Rd b/man/calc_terraclimate.Rd index 720e5e8c..6c98b872 100644 --- a/man/calc_terraclimate.Rd +++ b/man/calc_terraclimate.Rd @@ -4,7 +4,15 @@ \alias{calc_terraclimate} \title{Calculate TerraClimate covariates} \usage{ -calc_terraclimate(from, locs, locs_id = NULL, radius = 0, fun = "mean") +calc_terraclimate( + from, + locs, + locs_id = NULL, + radius = 0, + fun = "mean", + geom = geom, + ... +) } \arguments{ \item{from}{SpatRaster(1). Output from \code{process_terraclimate()}.} @@ -19,6 +27,15 @@ containing identifier for each unique coordinate location.} \item{fun}{character(1). Function used to summarize multiple raster cells within sites location buffer (Default = \code{mean}).} + +\item{geom}{logical(1). Should the geometry of \code{locs} be returned in the +\code{data.frame}? Default is \code{FALSE}. If \code{geom = TRUE} and \code{locs} contain +polygon geometries, the \verb{$geometry} column in the returned data frame may +make the \code{data.frame} difficult to read due to long geometry strings. The +coordinate reference system of the \verb{$geometry} is the coordinate +reference system of \code{from}.} + +\item{...}{Placeholders.} } \value{ a data.frame object diff --git a/man/calc_time.Rd b/man/calc_time.Rd index 247395cd..43ebb99a 100644 --- a/man/calc_time.Rd +++ b/man/calc_time.Rd @@ -10,8 +10,9 @@ calc_time(time, format) \item{time}{Time value} \item{format}{Type of time to return in the \verb{$time} column. Can be -"timeless" (ie. GMTED data), "date" (ie. NARR data), "hour", (ie. GEOS data), -"year" (ie. SEDAC population data), or "yearmonth" (ie. TerraClimate data).} +"timeless" (ie. Ecoregions data), "date" (ie. NARR data), "hour" +(ie. GEOS data), "year" (ie. SEDAC population data), or "yearmonth" +(ie. TerraClimate data).} } \value{ a \code{Date}, \code{POSIXt}, or \code{integer} object based on \verb{format =} diff --git a/man/calc_worker.Rd b/man/calc_worker.Rd index ea4a1150..6749c869 100644 --- a/man/calc_worker.Rd +++ b/man/calc_worker.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/calculate_covariates_auxiliary.R \name{calc_worker} \alias{calc_worker} -\title{Peform covariate extraction} +\title{Perform covariate extraction} \usage{ calc_worker( dataset, @@ -14,7 +14,9 @@ calc_worker( time, time_type = c("date", "hour", "year", "yearmonth", "timeless"), radius, - level = NULL + level = NULL, + max_cells = 1e+08, + ... ) } \arguments{ @@ -44,6 +46,13 @@ value(s).} \item{level}{integer. Position within the layer name containing the vertical pressure level value (if applicable). Default = \code{NULL}.} + +\item{max_cells}{integer(1). Maximum number of cells to be read at once. +Higher values will expedite processing, but will increase memory usage. +Maximum possible value is \code{2^31 - 1}. +See \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract}} for details.} + +\item{...}{Placeholders.} } \value{ a \code{data.frame} object diff --git a/man/download_aqs_data.Rd b/man/download_aqs.Rd similarity index 78% rename from man/download_aqs_data.Rd rename to man/download_aqs.Rd index 620b50d0..3b302bfb 100644 --- a/man/download_aqs_data.Rd +++ b/man/download_aqs.Rd @@ -1,16 +1,15 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_aqs_data} -\alias{download_aqs_data} +\name{download_aqs} +\alias{download_aqs} \title{Download air quality data} \usage{ -download_aqs_data( +download_aqs( parameter_code = 88101, resolution_temporal = "daily", year_start = 2018, year_end = 2022, url_aqs_download = "https://aqs.epa.gov/aqsweb/airdata/", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -37,11 +36,9 @@ End year for downloading data.} \item{url_aqs_download}{character(1). URL to the AQS pre-generated datasets.} -\item{directory_to_download}{character(1). -Directory to download zip files from AQS data mart.} - -\item{directory_to_save}{character(1). -Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped data files ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -66,7 +63,7 @@ monitors and the daily representative values will be stored in \code{directory_to_save}. } \description{ -The \code{download_aqs_data()} function accesses and downloads Air Quality System (AQS) data from the \href{https://aqs.epa.gov/aqsweb/airdata/download_files.html}{U.S. Environmental Protection Agency's (EPA) Pre-Generated Data Files}. +The \code{download_aqs()} function accesses and downloads Air Quality System (AQS) data from the \href{https://aqs.epa.gov/aqsweb/airdata/download_files.html}{U.S. Environmental Protection Agency's (EPA) Pre-Generated Data Files}. } \author{ Mariana Kassien, Insang Song, Mitchell Manware diff --git a/man/download_cropscape_data.Rd b/man/download_cropscape.Rd similarity index 94% rename from man/download_cropscape_data.Rd rename to man/download_cropscape.Rd index 01956576..82b6e0fa 100644 --- a/man/download_cropscape_data.Rd +++ b/man/download_cropscape.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_cropscape_data} -\alias{download_cropscape_data} +\name{download_cropscape} +\alias{download_cropscape} \title{Download CropScape data} \usage{ -download_cropscape_data( +download_cropscape( year = seq(1997, 2023), source = c("USDA", "GMU"), directory_to_save = NULL, @@ -55,7 +55,7 @@ JSON files should be found at STAC catalog of OpenLandMap } \examples{ \dontrun{ -download_cropscape_data( +download_cropscape( 2020, "~/data", acknowledgement = TRUE, download = TRUE, diff --git a/man/download_data.Rd b/man/download_data.Rd index 021c1e58..8d1b9706 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -32,30 +32,30 @@ The \code{download_data()} function accesses and downloads atmospheric, meteorol } \note{ \itemize{ -\item All download function names are in \code{download_*_data} formats +\item All download function names are in \code{download_*} formats } } \seealso{ For details of each download function per dataset, Please refer to: \itemize{ -\item \link{download_aqs_data}: "aqs", "AQS" -\item \link{download_ecoregion_data}: "ecoregion" -\item \link{download_geos_data}: "geos" -\item \link{download_gmted_data}: "gmted", "GMTED" -\item \link{download_koppen_geiger_data}: "koppen", "koppengeiger" -\item \link{download_merra2_data}: "merra2", "merra", "MERRA", "MERRA2" -\item \link{download_narr_monolevel_data}: "narr_monolevel", "monolevel" -\item \link{download_narr_p_levels_data}: "narr_p_levels", "p_levels", "plevels" -\item \link{download_nlcd_data}: "nlcd", "NLCD" -\item \link{download_hms_data}: "noaa", "smoke", "hms" -\item \link{download_sedac_groads_data}: "sedac_groads", "groads" -\item \link{download_sedac_population_data}: "sedac_population", "population" -\item \link{download_modis_data}: "modis", "MODIS" -\item \link{download_tri_data}: "tri", "TRI" -\item \link{download_nei_data}: "nei", "NEI" -\item \link{download_gridmet_data}: "gridMET", "gridmet" -\item \link{download_terraclimate_data}: "TerraClimate", "terraclimate" +\item \link{download_aqs}: "aqs", "AQS" +\item \link{download_ecoregion}: "ecoregion" +\item \link{download_geos}: "geos" +\item \link{download_gmted}: "gmted", "GMTED" +\item \link{download_koppen_geiger}: "koppen", "koppengeiger" +\item \link{download_merra2}: "merra2", "merra", "MERRA", "MERRA2" +\item \link{download_narr_monolevel}: "narr_monolevel", "monolevel" +\item \link{download_narr_p_levels}: "narr_p_levels", "p_levels", "plevels" +\item \link{download_nlcd}: "nlcd", "NLCD" +\item \link{download_hms}: "noaa", "smoke", "hms" +\item \link{download_sedac_groads}: "sedac_groads", "groads" +\item \link{download_sedac_population}: "sedac_population", "population" +\item \link{download_modis}: "modis", "MODIS" +\item \link{download_tri}: "tri", "TRI" +\item \link{download_nei}: "nei", "NEI" +\item \link{download_gridmet}: "gridMET", "gridmet" +\item \link{download_terraclimate}: "TerraClimate", "terraclimate" } } \author{ diff --git a/man/download_ecoregion_data.Rd b/man/download_ecoregion.Rd similarity index 75% rename from man/download_ecoregion_data.Rd rename to man/download_ecoregion.Rd index 53071a3a..5479def8 100644 --- a/man/download_ecoregion_data.Rd +++ b/man/download_ecoregion.Rd @@ -1,14 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_ecoregion_data} -\alias{download_ecoregion_data} +\name{download_ecoregion} +\alias{download_ecoregion} \title{Download ecoregion data} \usage{ -download_ecoregion_data( +download_ecoregion( epa_certificate_path = system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus"), certificate_url = "http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -25,10 +24,9 @@ for EPA DataCommons. Default is \item{certificate_url}{character(1). URL to certificate file. See notes for details.} -\item{directory_to_download}{character(1). Directory to download zip file -of Ecoregion level 3 shapefiles} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped data files ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -51,7 +49,7 @@ Default \code{FALSE}.} NULL; } \description{ -The \code{download_ecoregion_data()} function accesses and downloads United States Ecoregions data from the \href{https://www.epa.gov/eco-research/ecoregions}{U.S. Environmental Protection Agency's (EPA) Ecorgions}. Level 3 data, where all pieces of information in the higher levels are included, are downloaded. +The \code{download_ecoregion()} function accesses and downloads United States Ecoregions data from the \href{https://www.epa.gov/eco-research/ecoregions}{U.S. Environmental Protection Agency's (EPA) Ecorgions}. Level 3 data, where all pieces of information in the higher levels are included, are downloaded. } \note{ For EPA Data Commons certificate errors, follow the steps below: diff --git a/man/download_geos_data.Rd b/man/download_geos.Rd similarity index 87% rename from man/download_geos_data.Rd rename to man/download_geos.Rd index 20277c5d..ab72fa62 100644 --- a/man/download_geos_data.Rd +++ b/man/download_geos.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_geos_data} -\alias{download_geos_data} +\name{download_geos} +\alias{download_geos} \title{Download atmospheric composition data} \usage{ -download_geos_data( +download_geos( collection = c("aqc_tavg_1hr_g1440x721_v1", "chm_tavg_1hr_g1440x721_v1", "met_tavg_1hr_g1440x721_x1", "xgc_tavg_1hr_g1440x721_x1", "chm_inst_1hr_g1440x721_p23", "met_inst_1hr_g1440x721_p23"), @@ -40,11 +40,11 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Hourly netCDF (.nc4) files will be stored in -\code{directory_to_save}. +NULL; Hourly netCDF (.nc4) files will be stored in a +collection-specific folder within \code{directory_to_save}. } \description{ -The \code{download_geos_data()} function accesses and downloads various +The \code{download_geos()} function accesses and downloads various atmospheric composition collections from \href{https://gmao.gsfc.nasa.gov/GEOS_systems/}{NASA's Global Earth Observing System (GEOS) model}. } \author{ diff --git a/man/download_gmted_data.Rd b/man/download_gmted.Rd similarity index 84% rename from man/download_gmted_data.Rd rename to man/download_gmted.Rd index abc7e55d..f29c901b 100644 --- a/man/download_gmted_data.Rd +++ b/man/download_gmted.Rd @@ -1,15 +1,14 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_gmted_data} -\alias{download_gmted_data} +\name{download_gmted} +\alias{download_gmted} \title{Download elevation data} \usage{ -download_gmted_data( +download_gmted( statistic = c("Breakline Emphasis", "Systematic Subsample", "Median Statistic", "Minimum Statistic", "Mean Statistic", "Maximum Statistic", "Standard Deviation Statistic"), resolution = c("7.5 arc-seconds", "15 arc-seconds", "30 arc-seconds"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -25,10 +24,9 @@ download_gmted_data( \item{resolution}{character(1). Available resolutions include \code{"7.5 arc-seconds"}, \code{"15 arc-seconds"}, and \code{"30 arc-seconds"}.} -\item{directory_to_download}{character(1). Directory to download zip files -from Global Multi-resolution Terrain Elevation Data (GMTED2010).} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped data files ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -53,7 +51,7 @@ NULL; Statistic and resolution-specific zip files will be stored in will be stored in \code{directory_to_save}. } \description{ -The \code{download_gmted_data()} function accesses and downloads Global +The \code{download_gmted()} function accesses and downloads Global Multi-resolution Terrain Elevation Data (GMTED2010) from \href{https://www.usgs.gov/coastal-changes-and-impacts/gmted2010}{U.S. Geological Survey and National Geospatial-Intelligence Agency}. } diff --git a/man/download_gridmet_data.Rd b/man/download_gridmet.Rd similarity index 82% rename from man/download_gridmet_data.Rd rename to man/download_gridmet.Rd index 2c2fe91e..52745adc 100644 --- a/man/download_gridmet_data.Rd +++ b/man/download_gridmet.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_gridmet_data} -\alias{download_gridmet_data} +\name{download_gridmet} +\alias{download_gridmet} \title{Download gridMET data} \usage{ -download_gridmet_data( +download_gridmet( variables = NULL, year_start = 2022, year_end = 2022, @@ -45,7 +45,7 @@ NULL; Yearly netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ -The \code{download_gridmet_data} function accesses and downloads gridded surface meteorological data from the \href{https://www.climatologylab.org/gridmet.html}{University of California Merced Climatology Lab's gridMET dataset}. +The \code{download_gridmet} function accesses and downloads gridded surface meteorological data from the \href{https://www.climatologylab.org/gridmet.html}{University of California Merced Climatology Lab's gridMET dataset}. } \author{ Mitchell Manware diff --git a/man/download_hms_data.Rd b/man/download_hms.Rd similarity index 77% rename from man/download_hms_data.Rd rename to man/download_hms.Rd index 02ef3a36..662abc36 100644 --- a/man/download_hms_data.Rd +++ b/man/download_hms.Rd @@ -1,14 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_hms_data} -\alias{download_hms_data} +\name{download_hms} +\alias{download_hms} \title{Download wildfire smoke data} \usage{ -download_hms_data( +download_hms( data_format = "Shapefile", date_start = "2023-09-01", date_end = "2023-09-01", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -26,12 +25,11 @@ data. Format YYYY-MM-DD (ex. September 1, 2023 is \code{"2023-09-01"}).} \item{date_end}{character(1). length of 10. End date for downloading data. Format YYYY-MM-DD (ex. September 10, 2023 is \code{"2023-09-10"}).} -\item{directory_to_download}{character(1). Directory to download zip files -from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if -\code{data_format = "KML"}.)} - -\item{directory_to_save}{character(1). Directory to save unzipped shapefiles -and KML files.} +\item{directory_to_save}{character(1). Directory to save data. If +\code{data_format = "Shapefile"}, two sub-directories will be created for the +downloaded zip files ("/zip_files") and the unzipped shapefiles +("/data_files"). If \code{data_format = "KML"}, a single sub-directory +("/data_files") will be created.} \item{acknowledgement}{logical(1). By setting \code{TRUE} the @@ -52,14 +50,18 @@ if \code{data_format = "KML"}.)} \item{remove_zip}{logical(1). Remove zip files from directory_to_download. Default is \code{FALSE}. (Ignored if \code{data_format = "KML"}.)} + +\item{directory_to_download}{character(1). Directory to download zip files +from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if +\code{data_format = "KML"}.)} } \value{ -NULL; Zip file will be stored in \code{directory_to_download}, and -Shapefiles (.shp) or KML files (.kml) will be stored in +NULL; Zip files and unzipped data files will be stored in +"/zip_files" and "/data_files" sub-directories, respectively, within the \code{directory_to_save}. } \description{ -The \code{download_hms_data()} function accesses and downloads +The \code{download_hms()} function accesses and downloads wildfire smoke plume coverage data from \href{https://www.ospo.noaa.gov/Products/land/hms.html#0}{NOAA's Hazard Mapping System Fire and Smoke Product}. } \author{ diff --git a/man/download_huc_data.Rd b/man/download_huc.Rd similarity index 96% rename from man/download_huc_data.Rd rename to man/download_huc.Rd index ff2477d0..2beffda5 100644 --- a/man/download_huc_data.Rd +++ b/man/download_huc.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_huc_data} -\alias{download_huc_data} +\name{download_huc} +\alias{download_huc} \title{Download National Hydrography Dataset (NHD) data} \usage{ -download_huc_data( +download_huc( region = c("Lower48", "Islands"), type = c("Seamless", "OceanCatchment"), directory_to_save = NULL, diff --git a/man/download_koppen_geiger_data.Rd b/man/download_koppen_geiger.Rd similarity index 75% rename from man/download_koppen_geiger_data.Rd rename to man/download_koppen_geiger.Rd index f38ad4be..b472b60c 100644 --- a/man/download_koppen_geiger_data.Rd +++ b/man/download_koppen_geiger.Rd @@ -1,13 +1,12 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_koppen_geiger_data} -\alias{download_koppen_geiger_data} +\name{download_koppen_geiger} +\alias{download_koppen_geiger} \title{Download climate classification data} \usage{ -download_koppen_geiger_data( +download_koppen_geiger( data_resolution = c("0.0083", "0.083", "0.5"), time_period = c("Present", "Future"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -25,11 +24,9 @@ degrees (approx. 1 km), \code{"0.083"} degrees (approx. 10 km), and and \code{"Future"} (2071-2100). ("Future" classifications are based on scenario RCP8.5).} -\item{directory_to_download}{character(1). Directory to download zip files -from Present and future Köppen-Geiger climate classification maps at 1-km -resolution.} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped shapefiles ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -49,11 +46,12 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip file will be stored in \code{directory_to_download}, and -selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +NULL; Zip files and unzipped data files will be stored in +"/zip_files" and "/data_files" sub-directories, respectively, within the +\code{directory_to_save}. } \description{ -The \code{download_koppen_geiger_data()} function accesses and downloads +The \code{download_koppen_geiger()} function accesses and downloads climate classification data from the \emph{Present and future Köppen-Geiger climate classification maps at 1-km resolution}(\href{https://www.nature.com/articles/sdata2018214}{link for article}; \href{https://figshare.com/articles/dataset/Present_and_future_K_ppen-Geiger_climate_classification_maps_at_1-km_resolution/6396959/2}{link for data}). diff --git a/man/download_merra2_data.Rd b/man/download_merra2.Rd similarity index 90% rename from man/download_merra2_data.Rd rename to man/download_merra2.Rd index 1225703b..02b202ab 100644 --- a/man/download_merra2_data.Rd +++ b/man/download_merra2.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_merra2_data} -\alias{download_merra2_data} +\name{download_merra2} +\alias{download_merra2} \title{Download meteorological and atmospheric data} \usage{ -download_merra2_data( +download_merra2( collection = c("inst1_2d_asm_Nx", "inst1_2d_int_Nx", "inst1_2d_lfo_Nx", "inst3_3d_asm_Np", "inst3_3d_aer_Nv", "inst3_3d_asm_Nv", "inst3_3d_chm_Nv", "inst3_3d_gas_Nv", "inst3_2d_gas_Nx", "inst6_3d_ana_Np", "inst6_3d_ana_Nv", @@ -48,11 +48,11 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Daily netCDF (.nc4) files will be stored in -\code{directory_to_save}. +NULL; Daily netCDF (.nc4) files will be stored in a +collection-specific folder within \code{directory_to_save}. } \description{ -The \code{download_merra2_data()} function accesses and downloads various +The \code{download_merra2()} function accesses and downloads various meteorological and atmospheric collections from \href{https://gmao.gsfc.nasa.gov/reanalysis/MERRA-2/}{NASA's Modern-Era Retrospective analysis for Research and Applications, Version 2 (MERRA-2) model}. } \author{ diff --git a/man/download_modis_data.Rd b/man/download_modis.Rd similarity index 97% rename from man/download_modis_data.Rd rename to man/download_modis.Rd index cb46d19c..f4196b40 100644 --- a/man/download_modis_data.Rd +++ b/man/download_modis.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_modis_data} -\alias{download_modis_data} +\name{download_modis} +\alias{download_modis} \title{Download MODIS product files} \usage{ -download_modis_data( +download_modis( product = c("MOD09GA", "MOD11A1", "MOD06_L2", "MCD19A2", "MOD13A2", "VNP46A2"), version = "61", horizontal_tiles = c(7, 13), diff --git a/man/download_narr_monolevel_data.Rd b/man/download_narr_monolevel.Rd similarity index 70% rename from man/download_narr_monolevel_data.Rd rename to man/download_narr_monolevel.Rd index f52d9bac..05493322 100644 --- a/man/download_narr_monolevel_data.Rd +++ b/man/download_narr_monolevel.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_narr_monolevel_data} -\alias{download_narr_monolevel_data} +\name{download_narr_monolevel} +\alias{download_narr_monolevel} \title{Download meteorological data (monolevel)} \usage{ -download_narr_monolevel_data( +download_narr_monolevel( variables = NULL, year_start = 2022, year_end = 2022, @@ -44,7 +44,7 @@ NULL; Yearly netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ -The \code{download_narr_monolevel_data} function accesses and downloads monolevel meteorological data from \href{https://psl.noaa.gov/data/gridded/data.narr.html}{NOAA's North American Regional Reanalysis (NARR) model}. "Monolevel" variables contain a single value for the entire atmospheric column (ie. Variable: Convective cloud cover; Level: Entire atmosphere considered as a single layer), or represent a specific altitude associated with the variable (ie. Variable: Air temperature; Level: 2 m). +The \code{download_narr_monolevel} function accesses and downloads monolevel meteorological data from \href{https://psl.noaa.gov/data/gridded/data.narr.html}{NOAA's North American Regional Reanalysis (NARR) model}. "Monolevel" variables contain a single value for the entire atmospheric column (ie. Variable: Convective cloud cover; Level: Entire atmosphere considered as a single layer), or represent a specific altitude associated with the variable (ie. Variable: Air temperature; Level: 2 m). } \author{ Mitchell Manware, Insang Song diff --git a/man/download_narr_p_levels_data.Rd b/man/download_narr_p_levels.Rd similarity index 74% rename from man/download_narr_p_levels_data.Rd rename to man/download_narr_p_levels.Rd index ac87c519..4f69d00e 100644 --- a/man/download_narr_p_levels_data.Rd +++ b/man/download_narr_p_levels.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_narr_p_levels_data} -\alias{download_narr_p_levels_data} +\name{download_narr_p_levels} +\alias{download_narr_p_levels} \title{Download meteorological data (pressure levels)} \usage{ -download_narr_p_levels_data( +download_narr_p_levels( variables = NULL, year_start = 2022, year_end = 2022, @@ -44,7 +44,7 @@ NULL; Monthly netCDF (.nc) files will be stored in \code{directory_to_save}. } \description{ -The \code{download_narr_p_levels_data} function accesses and downloads pressure levels meteorological data from \href{https://psl.noaa.gov/data/gridded/data.narr.html}{NOAA's North American Regional Reanalysis (NARR) model}. "Pressure levels" variables contain variable values at 29 atmospheric levels, ranging from 1000 hPa to 100 hPa. All pressure levels data will be downloaded for each variable. +The \code{download_narr_p_levels} function accesses and downloads pressure levels meteorological data from \href{https://psl.noaa.gov/data/gridded/data.narr.html}{NOAA's North American Regional Reanalysis (NARR) model}. "Pressure levels" variables contain variable values at 29 atmospheric levels, ranging from 1000 hPa to 100 hPa. All pressure levels data will be downloaded for each variable. } \author{ Mitchell Manware, Insang Song diff --git a/man/download_nei_data.Rd b/man/download_nei.Rd similarity index 79% rename from man/download_nei_data.Rd rename to man/download_nei.Rd index 4b76ad06..0abd7150 100644 --- a/man/download_nei_data.Rd +++ b/man/download_nei.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_nei_data} -\alias{download_nei_data} +\name{download_nei} +\alias{download_nei} \title{Download road emissions data} \usage{ -download_nei_data( +download_nei( epa_certificate_path = system.file("extdata/cacert_gaftp_epa.pem", package = "amadeus"), certificate_url = "http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt", @@ -27,7 +27,9 @@ details.} \item{year_target}{Available years of NEI data. Default is \code{c(2017L, 2020L)}.} -\item{directory_to_save}{character(1). Directory to download files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped data files ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -49,7 +51,7 @@ NULL; Yearly comma-separated value (CSV) files will be stored in \code{directory_to_save}. } \description{ -The \code{download_nei_data()} function accesses and downloads road emissions data from the \href{https://www.epa.gov/air-emissions-inventories/national-emissions-inventory-nei}{U.S Environmental Protection Agency's (EPA) National Emissions Inventory (NEI)}. +The \code{download_nei()} function accesses and downloads road emissions data from the \href{https://www.epa.gov/air-emissions-inventories/national-emissions-inventory-nei}{U.S Environmental Protection Agency's (EPA) National Emissions Inventory (NEI)}. } \note{ For EPA Data Commons certificate errors, follow the steps below: diff --git a/man/download_nlcd_data.Rd b/man/download_nlcd.Rd similarity index 75% rename from man/download_nlcd_data.Rd rename to man/download_nlcd.Rd index ac1ca866..686d164a 100644 --- a/man/download_nlcd_data.Rd +++ b/man/download_nlcd.Rd @@ -1,13 +1,12 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_nlcd_data} -\alias{download_nlcd_data} +\name{download_nlcd} +\alias{download_nlcd} \title{Download land cover data} \usage{ -download_nlcd_data( +download_nlcd( collection = "Coterminous United States", year = 2021, - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -24,10 +23,9 @@ include \code{2001}, \code{2004}, \code{2006}, \code{2008}, \code{2011}, \code{2 \code{2019}, and \code{2021}. Available years for Alaska include \code{2001}, \code{2011}, and \code{2016}.} -\item{directory_to_download}{character(1). Directory to download zip files -from National Land Cover Database Science Research Products.} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped shapefiles ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -47,11 +45,12 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip file will be stored in \code{directory_to_download}, and -selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +NULL; Zip files and unzipped data files will be stored in +"/zip_files" and "/data_files" sub-directories, respectively, within the +\code{directory_to_save}. } \description{ -The \code{download_nlcd_data()} function accesses and downloads +The \code{download_nlcd()} function accesses and downloads land cover data from the \href{https://www.mrlc.gov/data}{Multi-Resolution Land Characteristics (MRLC) Consortium's National Land Cover Database (NLCD) products data base}. } diff --git a/man/download_olm_data.Rd b/man/download_olm.Rd similarity index 98% rename from man/download_olm_data.Rd rename to man/download_olm.Rd index 2c65cc74..e02d965d 100644 --- a/man/download_olm_data.Rd +++ b/man/download_olm.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_olm_data} -\alias{download_olm_data} +\name{download_olm} +\alias{download_olm} \title{Download OpenLandMap data} \usage{ -download_olm_data( +download_olm( product = NULL, format = "tif", directory_to_save = NULL, diff --git a/man/download_prism_data.Rd b/man/download_prism.Rd similarity index 97% rename from man/download_prism_data.Rd rename to man/download_prism.Rd index a59a3639..a44ff6b8 100644 --- a/man/download_prism_data.Rd +++ b/man/download_prism.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_prism_data} -\alias{download_prism_data} +\name{download_prism} +\alias{download_prism} \title{Download PRISM data} \usage{ -download_prism_data( +download_prism( time, element = c("ppt", "tmin", "tmax", "tmean", "tdmean", "vpdmin", "vpdmax", "solslope", "soltotal", "solclear", "soltrans"), @@ -72,7 +72,7 @@ PRISM data from the PRISM Climate Group Web Service } \examples{ \dontrun{ -download_prism_data( +download_prism( time = "202104", element = "ppt", data_type = "ts", diff --git a/man/download_sedac_groads_data.Rd b/man/download_sedac_groads.Rd similarity index 76% rename from man/download_sedac_groads_data.Rd rename to man/download_sedac_groads.Rd index c070b0bf..8c0d0582 100644 --- a/man/download_sedac_groads_data.Rd +++ b/man/download_sedac_groads.Rd @@ -1,14 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_sedac_groads_data} -\alias{download_sedac_groads_data} +\name{download_sedac_groads} +\alias{download_sedac_groads} \title{Download roads data} \usage{ -download_sedac_groads_data( +download_sedac_groads( data_region = c("Americas", "Global", "Africa", "Asia", "Europe", "Oceania East", "Oceania West"), data_format = c("Shapefile", "Geodatabase"), - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -24,10 +23,9 @@ download_sedac_groads_data( \item{data_format}{character(1). Data can be downloaded as \code{"Shapefile"} or \code{"Geodatabase"}. (Only \code{"Geodatabase"} available for \code{"Global"} region).} -\item{directory_to_download}{character(1). Directory to download zip files -from NASA Global Roads Open Access Data Set.} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped shapefiles ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -47,12 +45,12 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip file will be stored in \code{directory_to_download}, and -selected Shapefile (.shp) or Geodatabase (.gdb) files will be stored in +NULL; Zip files and unzipped data files will be stored in +"/zip_files" and "/data_files" sub-directories, respectively, within the \code{directory_to_save}. } \description{ -The \code{download_sedac_groads_data()} function accesses and downloads +The \code{download_sedac_groads()} function accesses and downloads roads data from \href{https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/data-download}{NASA's Global Roads Open Access Data Set (gROADS), v1 (1980-2010)}. } \author{ diff --git a/man/download_sedac_population_data.Rd b/man/download_sedac_population.Rd similarity index 76% rename from man/download_sedac_population_data.Rd rename to man/download_sedac_population.Rd index 18b28ae6..2537bc8c 100644 --- a/man/download_sedac_population_data.Rd +++ b/man/download_sedac_population.Rd @@ -1,14 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_sedac_population_data} -\alias{download_sedac_population_data} +\name{download_sedac_population} +\alias{download_sedac_population} \title{Download population density data} \usage{ -download_sedac_population_data( +download_sedac_population( data_resolution = "60 minute", data_format = c("GeoTIFF", "ASCII", "netCDF"), year = "2020", - directory_to_download = NULL, directory_to_save = NULL, acknowledgement = FALSE, download = FALSE, @@ -28,10 +27,9 @@ download_sedac_population_data( \item{year}{character(1). Available years are \code{2000}, \code{2005}, \code{2010}, \code{2015}, and \code{2020}, or \code{"all"} for all years.} -\item{directory_to_download}{character(1). Directory to download zip files -from NASA UN WPP-Adjusted Population Density, v4.11.} - -\item{directory_to_save}{character(1). Directory to decompress zip files.} +\item{directory_to_save}{character(1). Directory to save data. Two +sub-directories will be created for the downloaded zip files ("/zip_files") +and the unzipped shapefiles ("/data_files").} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -51,11 +49,12 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip file will be stored in \code{directory_to_download}, and -selected GeoTIFF (.tif) files will be stored in \code{directory_to_save}. +NULL; Zip files and unzipped data files will be stored in +"/zip_files" and "/data_files" sub-directories, respectively, within the +\code{directory_to_save}. } \description{ -The \code{download_sedac_population_data()} function accesses and downloads +The \code{download_sedac_population()} function accesses and downloads population density data from \href{https://sedac.ciesin.columbia.edu/data/set/gpw-v4-population-density-adjusted-to-2015-unwpp-country-totals-rev11}{NASA's UN WPP-Adjusted Population Density, v4.11}. } \author{ diff --git a/man/download_setup_dir.Rd b/man/download_setup_dir.Rd index 9a3f833d..6f0c8653 100644 --- a/man/download_setup_dir.Rd +++ b/man/download_setup_dir.Rd @@ -4,10 +4,17 @@ \alias{download_setup_dir} \title{Setup directory} \usage{ -download_setup_dir(directory) +download_setup_dir(directory, zip = FALSE) } \arguments{ \item{directory}{character(1) directory path} + +\item{zip}{logical(1). Should sub-directories be created for zip files and +data files? If \code{TRUE}, a vector of sub-directoy names will be returned.} +} +\value{ +NULL; if \code{zip = TRUE} a vector of directories for zip files and +data files } \description{ Create \code{directory} if it does not already exist. diff --git a/man/download_terraclimate_data.Rd b/man/download_terraclimate.Rd similarity index 81% rename from man/download_terraclimate_data.Rd rename to man/download_terraclimate.Rd index 0b523ce0..3a42bb57 100644 --- a/man/download_terraclimate_data.Rd +++ b/man/download_terraclimate.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_terraclimate_data} -\alias{download_terraclimate_data} +\name{download_terraclimate} +\alias{download_terraclimate} \title{Download TerraClimate data} \usage{ -download_terraclimate_data( +download_terraclimate( variables = NULL, year_start = 2022, year_end = 2022, @@ -44,7 +44,7 @@ NULL; Yearly netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ -The \code{download_terraclimate_data} function accesses and downloads climate and water balance data from the \href{https://www.climatologylab.org/terraclimate.html}{University of California Merced Climatology Lab's TerraClimate dataset}. +The \code{download_terraclimate} function accesses and downloads climate and water balance data from the \href{https://www.climatologylab.org/terraclimate.html}{University of California Merced Climatology Lab's TerraClimate dataset}. } \author{ Mitchell Manware, Insang Song diff --git a/man/download_tri_data.Rd b/man/download_tri.Rd similarity index 77% rename from man/download_tri_data.Rd rename to man/download_tri.Rd index b5f31132..4f883175 100644 --- a/man/download_tri_data.Rd +++ b/man/download_tri.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/download.R -\name{download_tri_data} -\alias{download_tri_data} +\name{download_tri} +\alias{download_tri} \title{Download toxic release data} \usage{ -download_tri_data( +download_tri( year_start = 2018L, year_end = 2022L, directory_to_save = NULL, @@ -36,7 +36,7 @@ NULL; Yearly comma-separated value (CSV) files will be stored in \code{directory_to_save}. } \description{ -The \code{download_tri_data()} function accesses and downloads toxic release data from the \href{https://www.epa.gov/toxics-release-inventory-tri-program/find-understand-and-use-tri}{U.S. Environmental Protection Agency's (EPA) Toxic Release Inventory (TRI) Program}. +The \code{download_tri()} function accesses and downloads toxic release data from the \href{https://www.epa.gov/toxics-release-inventory-tri-program/find-understand-and-use-tri}{U.S. Environmental Protection Agency's (EPA) Toxic Release Inventory (TRI) Program}. } \author{ Mariana Kassien, Insang Song diff --git a/man/process_gmted.Rd b/man/process_gmted.Rd index 4069bdd0..abcb8513 100644 --- a/man/process_gmted.Rd +++ b/man/process_gmted.Rd @@ -24,7 +24,8 @@ The \code{process_gmted()} function imports and cleans raw elevation data, returning a single \code{SpatRaster} object. } \note{ -\code{SpatRaster} layer name indicates selected variable and resolution. +\code{SpatRaster} layer name indicates selected variable and resolution, and year +of release (2010). } \author{ Mitchell Manware diff --git a/man/process_locs_vector.Rd b/man/process_locs_vector.Rd index c1291d88..e80697dd 100644 --- a/man/process_locs_vector.Rd +++ b/man/process_locs_vector.Rd @@ -20,7 +20,8 @@ be named "lat" and "lon", respectively.} a \code{SpatVector} object } \description{ -Convert locations from class \code{data.frame} or \code{data.table} to +Detect \code{SpatVector} object, or convert locations from class \code{sf}, +\code{data.frame} or \code{data.table} to \code{SpatVector} object, project to coordinate reference system, and apply circular buffer. } diff --git a/man/process_olm.Rd b/man/process_olm.Rd index 17ebfa68..73980c52 100644 --- a/man/process_olm.Rd +++ b/man/process_olm.Rd @@ -12,7 +12,7 @@ process_olm(path = NULL, ...) \item{...}{Placeholders.} } \value{ -SpatRaster +a \code{SpatRaster} object } \description{ Process OpenLandMap data diff --git a/man/process_sedac_groads.Rd b/man/process_sedac_groads.Rd index 2ed4ec8d..cefa9410 100644 --- a/man/process_sedac_groads.Rd +++ b/man/process_sedac_groads.Rd @@ -12,14 +12,16 @@ process_sedac_groads(path = NULL, ...) \item{...}{Placeholders.} } \value{ -a \code{SpatVector} boject +a \code{SpatVector} object } \description{ The \code{process_sedac_groads()} function imports and cleans raw road data, returning a single \code{SpatVector} object. } \note{ -U.S. context. +U.S. context. The returned \code{SpatVector} object contains a +\verb{$time} column to represent the temporal range covered by the +dataset. For more information, see \url{https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/metadata}. } \author{ Insang Song From 7c9299771f5fa0a960f7920cf49a5270c4a418e6 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Thu, 2 May 2024 14:53:08 -0400 Subject: [PATCH 22/65] download_data roxygen --- R/download.R | 75 ++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/R/download.R b/R/download.R index 91ca62da..8289a0c1 100644 --- a/R/download.R +++ b/R/download.R @@ -139,9 +139,8 @@ download_data <- #' @param remove_zip logical(1). Remove zip file from directory_to_download. #' Default \code{FALSE}. #' @author Mariana Kassien, Insang Song, Mitchell Manware -#' @returns NULL; Separate comma-separated value (CSV) files of -#' monitors and the daily representative values -#' will be stored in \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' \code{directory_to_save}. #' @export download_aqs <- function( @@ -158,10 +157,7 @@ download_aqs <- remove_zip = FALSE ) { #### 1. check for data download acknowledgement - download_permit( - acknowledgement = - acknowledgement - ) + download_permit(acknowledgement = acknowledgement) #### 2. check for null parameters check_for_null_parameters(mget(ls())) #### 3. directory setup @@ -298,10 +294,11 @@ download_aqs <- #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @param unzip logical(1). Unzip zip files. Default \code{TRUE}. -#' @param remove_zip logical(1). Remove zip file from directory_to_download. -#' Default \code{FALSE}. +#' @param remove_zip logical(1). Remove zip file from +#' \code{directory_to_download}. Default \code{FALSE}. #' @author Insang Song -#' @returns NULL; +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' \code{directory_to_save}. #' @importFrom utils download.file #' @export download_ecoregion <- function( @@ -421,6 +418,8 @@ download_ecoregion <- function( #' @param date_end character(1). length of 10. End date for downloading data. #' Format YYYY-MM-DD (ex. September 1, 2023 = `"2023-09-01"`). #' @param directory_to_save character(1). Directory to save data. +#' Sub-directories will be created within \code{directory_to_save} for each +#' GEOS-CF collection. #' @param acknowledgement logical(1). By setting \code{TRUE} the #' user acknowledges that the data downloaded using this function may be very #' large and use lots of machine storage and memory. @@ -431,7 +430,7 @@ download_ecoregion <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Hourly netCDF (.nc4) files will be stored in a +#' @return NULL; netCDF (.nc4) files will be stored in a #' collection-specific folder within \code{directory_to_save}. #' @export # nolint start: cyclocomp @@ -591,9 +590,8 @@ download_geos <- function( #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song # nolint end -#' @return NULL; Statistic and resolution-specific zip files will be stored in -#' \code{directory_to_download}, and directories containing raw ASCII Grid data -#'will be stored in \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' \code{directory_to_save}. #' @export download_gmted <- function( statistic = c( @@ -735,7 +733,7 @@ download_gmted <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Daily netCDF (.nc4) files will be stored in a +#' @return NULL; netCDF (.nc4) files will be stored in a #' collection-specific folder within \code{directory_to_save}. #' @export # nolint end @@ -1040,7 +1038,7 @@ download_merra2 <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +#' @return NULL; netCDF (.nc) files will be stored in a variable-specific #' folder within \code{directory_to_save}. #' @export # nolint end @@ -1166,7 +1164,7 @@ download_narr_monolevel <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Monthly netCDF (.nc) files will be stored in +#' @return NULL; netCDF (.nc) files will be stored in #' \code{directory_to_save}. #' @export # nolint end @@ -1308,9 +1306,8 @@ download_narr_p_levels <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @return NULL; Zip files and unzipped data files will be stored in -#' "/zip_files" and "/data_files" sub-directories, respectively, within the -#' \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export download_nlcd <- function( collection = "Coterminous United States", @@ -1456,9 +1453,8 @@ download_nlcd <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @return NULL; Zip files and unzipped data files will be stored in -#' "/zip_files" and "/data_files" sub-directories, respectively, within the -#' \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export download_sedac_groads <- function( data_region = c("Americas", "Global", "Africa", "Asia", "Europe", "Oceania East", "Oceania West"), @@ -1604,9 +1600,8 @@ download_sedac_groads <- function( #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song # nolint end -#' @return NULL; Zip files and unzipped data files will be stored in -#' "/zip_files" and "/data_files" sub-directories, respectively, within the -#' \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export download_sedac_population <- function( data_resolution = "60 minute", @@ -1793,9 +1788,8 @@ download_sedac_population <- function( #' @importFrom utils head #' @importFrom utils tail #' @author Mitchell Manware, Insang Song -#' @return NULL; Zip files and unzipped data files will be stored in -#' "/zip_files" and "/data_files" sub-directories, respectively, within the -#' \code{directory_to_save}. +##' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export # nolint start: cyclocomp download_hms <- function( @@ -1969,9 +1963,8 @@ download_hms <- function( #' @param remove_zip logical(1). Remove zip files from directory_to_download. #' Default is \code{FALSE}. #' @author Mitchell Manware, Insang Song -#' @return NULL; Zip files and unzipped data files will be stored in -#' "/zip_files" and "/data_files" sub-directories, respectively, within the -#' \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export download_koppen_geiger <- function( data_resolution = c("0.0083", "0.083", "0.5"), @@ -2131,7 +2124,7 @@ download_koppen_geiger <- function( #' the text file containing download commands. #' @author Mitchell Manware, Insang Song #' @import rvest -#' @return NULL; Raw HDF (.hdf) files will be stored in +#' @return NULL; HDF (.hdf) files will be stored in #' \code{directory_to_save}. #' @export download_modis <- function( @@ -2462,7 +2455,7 @@ download_modis <- function( #' @param remove_command logical(1). Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mariana Kassien, Insang Song -#' @returns NULL; Yearly comma-separated value (CSV) files will be stored in +#' @returns NULL; Comma-separated value (CSV) files will be stored in #' \code{directory_to_save}. #' @export download_tri <- function( @@ -2572,8 +2565,8 @@ download_tri <- function( #' Currently we bundle the pre-downloaded crt and its PEM (which is accepted #' in wget command) file in ./inst/extdata. The instruction above is for #' certificate updates in the future. -#' @returns NULL; Yearly comma-separated value (CSV) files will be stored in -#' \code{directory_to_save}. +#' @returns NULL; Zip and/or data files will be downloaded and stored in +#' respective sub-directories within \code{directory_to_save}. #' @export download_nei <- function( epa_certificate_path = @@ -2869,7 +2862,7 @@ download_olm <- function( #' the text file containing download commands. #' @param unzip logical(1). Unzip the downloaded compressed files. #' Default is \code{FALSE}. Not working for this function since HUC data is in 7z format. -#' @returns None. Downloaded files will be stored in \code{directory_to_save}. +#' @returns NULL. Downloaded files will be stored in \code{directory_to_save}. #' @author Insang Song #' @examples #' \dontrun{ @@ -3157,8 +3150,8 @@ download_cropscape <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Insang Song -#' @returns NULL; .bil (normals) or single grid files depending on the format choice. -#' \code{directory_to_save}. +#' @returns NULL; .bil (normals) or single grid files depending on the format +#' choice will be stored in \code{directory_to_save}. #' @examples #' \dontrun{ #' download_prism( @@ -3293,7 +3286,7 @@ download_prism <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware -#' @return NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +#' @return NULL; netCDF (.nc) files will be stored in a variable-specific #' folder within \code{directory_to_save}. #' @export # nolint end @@ -3422,7 +3415,7 @@ download_gridmet <- function( #' Remove (\code{TRUE}) or keep (\code{FALSE}) #' the text file containing download commands. #' @author Mitchell Manware, Insang Song -#' @return NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +#' @return NULL; netCDF (.nc) files will be stored in a variable-specific #' folder within \code{directory_to_save}. #' @export # nolint end From d5cb4de8a6e52bb5f51aada2b9556498b243af26 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Fri, 3 May 2024 14:27:11 -0400 Subject: [PATCH 23/65] process_aqs update - Now process_aqs supports three options - "full": locations * time regardless of attributes are available - "sparse": location-time pairs with available attributes - "location" unique locations existing the full input data - calc_worker gets match_arg() for time_type argument - Reflowing calc_nlcd --- NAMESPACE | 3 +- R/calculate_covariates.R | 51 ++++++++++++------- R/calculate_covariates_auxiliary.R | 5 +- R/process.R | 72 +++++++++++++++++++------- man/process_aqs.Rd | 23 +++++++-- tests/testthat/test-process.R | 81 +++++++++++++++++++++++++++--- 6 files changed, 184 insertions(+), 51 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 87eb3a09..e969cf34 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -173,6 +173,7 @@ importFrom(terra,buffer) importFrom(terra,coltab) importFrom(terra,crop) importFrom(terra,crs) +importFrom(terra,deepcopy) importFrom(terra,describe) importFrom(terra,distance) importFrom(terra,expanse) @@ -190,7 +191,7 @@ importFrom(terra,nlyr) importFrom(terra,perim) importFrom(terra,project) importFrom(terra,rast) -importFrom(terra,same.crs) +importFrom(terra,set.crs) importFrom(terra,snap) importFrom(terra,sprc) importFrom(terra,subset) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 315f68c4..20c23a6d 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -233,7 +233,8 @@ calc_koppen_geiger <- #' @importFrom terra project #' @importFrom terra vect #' @importFrom terra crs -#' @importFrom terra same.crs +#' @importFrom terra deepcopy +#' @importFrom terra set.crs #' @importFrom terra buffer #' @importFrom sf st_union #' @importFrom sf st_geometry @@ -256,9 +257,21 @@ calc_nlcd <- function(from, } if (!methods::is(locs, "SpatVector")) { message("locs is not a terra::SpatVector.") - locs <- tryCatch(terra::vect(locs), error = function(e) { + locs <- tryCatch({ + if (data.table::is.data.table(locs)) { + locs <- as.data.frame(locs) + } + locsa <- terra::deepcopy(terra::vect(locs)) + locs_crs <- terra::crs(locsa) + if (locs_crs == "" || is.na(locs_crs)) { + terra::crs(locsa) <- "EPSG:4326" + } + locsa + }, + error = function(e) { stop("Failed to locs to a terra::SpatVector.") - }) + } + ) } if (!methods::is(from, "SpatRaster")) { stop("from is not a SpatRaster.") @@ -272,20 +285,21 @@ calc_nlcd <- function(from, terra::project(y = terra::crs(locs)) data_vect_b <- locs |> terra::intersect(x = us_main) - if (!terra::same.crs(data_vect_b, from)) { - data_vect_b <- terra::project(data_vect_b, terra::crs(from)) - } + data_vect_b <- terra::project(data_vect_b, terra::crs(from)) # create circle buffers with buf_radius bufs_pol <- terra::buffer(data_vect_b, width = radius) |> - sf::st_as_sf() + sf::st_as_sf() |> + sf::st_geometry() # ratio of each nlcd class per buffer - nlcd_at_bufs <- exactextractr::exact_extract(from, - sf::st_geometry(bufs_pol), - fun = "frac", - stack_apply = TRUE, - force_df = TRUE, - progress = FALSE, - max_cells_in_memory = max_cells) + nlcd_at_bufs <- + exactextractr::exact_extract( + from, + bufs_pol, + fun = "frac", + #stack_apply = TRUE, + force_df = TRUE, + progress = FALSE, + max_cells_in_memory = max_cells) # select only the columns of interest cfpath <- system.file("extdata", "nlcd_classes.csv", package = "amadeus") nlcd_classes <- utils::read.csv(cfpath) @@ -311,11 +325,12 @@ calc_nlcd <- function(from, # merge data_vect with nlcd class fractions (and reproject) new_data_vect <- cbind(data_vect_b, nlcd_at_bufs) new_data_vect <- terra::project(new_data_vect, terra::crs(locs)) - new_data_vect$nlcd_year <- as.integer(year) + new_data_vect$time <- as.integer(year) return(new_data_vect) } + #' Calculate ecoregions covariates #' @description #' Extract ecoregions covariates (U.S. EPA Ecoregions Level 2/3) at point @@ -1033,7 +1048,7 @@ calc_tri <- function( df_tri <- dplyr::left_join(as.data.frame(locs), df_tri) } # read attr - df_tri$tri_year <- attr(from, "tri_year") + df_tri$time <- attr(from, "tri_year") return(df_tri) } @@ -1067,7 +1082,7 @@ calc_nei <- function( # spatial join locs_re <- terra::project(locs, terra::crs(from)) locs_re <- terra::intersect(locs_re, from) - + locs_re$time <- unique(from$nei_year) return(locs_re) } @@ -1551,7 +1566,7 @@ calc_sedac_population <- function( fun = fun, variable = 3, time = 4, - time_type = "sedac_population_year" + time_type = "year" ) #### return data.frame return(data.frame(sites_extracted)) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index dfe8f3ed..387a8176 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -299,6 +299,7 @@ calc_worker <- function( level = NULL) { #### empty location data.frame sites_extracted <- NULL + time_type <- match.arg(time_type) for (l in seq_len(terra::nlyr(from))) { #### select data layer data_layer <- from[[l]] @@ -317,10 +318,8 @@ calc_worker <- function( ) } #### extract level (if applicable) - if (!(is.null(level))) { + if (!is.null(level)) { data_level <- data_split[level] - } else { - data_level <- NULL } #### message calc_message( diff --git a/R/process.R b/R/process.R index e1dd85d4..f7ef1e5c 100644 --- a/R/process.R +++ b/R/process.R @@ -905,8 +905,16 @@ process_nei <- function( #' @param date character(2). Start and end date. #' Should be in `"YYYY-MM-DD"` format and sorted. If `NULL`, #' only unique locations are returned. -#' @param return_format character(1). `"terra"` or `"sf"`. +#' @param mode character(1). One of "full" (all dates * all locations) +#' or "sparse" (date-location pairs with available data) or +#' "location" (unique locations). +#' @param data_field character(1). Data field to extract. +#' @param return_format character(1). `"terra"` or `"sf"` or `"data.table"`. #' @param ... Placeholders. +#' @seealso +#' * [`download_aqs_data()`] +#' * [EPA, n.d., _AQS Parameter Codes_]( +#' https://aqs.epa.gov/aqsweb/documents/codetables/parameters.csv) #' @returns a `SpatVector` or sf object depending on the `return_format` #' @importFrom data.table as.data.table #' @importFrom utils read.csv @@ -916,16 +924,21 @@ process_nei <- function( #' @importFrom dplyr group_by #' @importFrom dplyr ungroup #' @importFrom dplyr filter -#' @note `date = NULL` will return a massive data.table -#' object. Please choose proper `date` values. +#' @note Choose `date` and `mode` values with caution. +#' The function may return a massive data.table, resulting in +#' a long processing time or even a crash. #' @export process_aqs <- function( path = NULL, date = c("2018-01-01", "2022-12-31"), - return_format = "terra", + mode = c("full", "sparse", "location"), + data_field = "Arithmetic.Mean", + return_format = c("terra", "sf", "data.table"), ... ) { + mode <- match.arg(mode) + return_format <- match.arg(return_format) if (!is.null(date)) { date <- try(as.Date(date)) if (inherits(date, "try-error")) { @@ -935,7 +948,6 @@ process_aqs <- stop("date should be a character vector of length 2.") } } - if (length(path) == 1 && dir.exists(path)) { path <- list.files( path = path, @@ -968,8 +980,18 @@ process_aqs <- dplyr::as_tibble() |> dplyr::group_by(site_id) |> dplyr::filter(POC == min(POC)) |> + dplyr::mutate(time = Date.Local) |> dplyr::ungroup() - sites_v <- unique(sites[, c("site_id", "Longitude", "Latitude", "Datum")]) + col_sel <- c("site_id", "Longitude", "Latitude", "Datum") + if (mode != "sparse") { + sites_v <- unique(sites[, col_sel]) + } else { + col_sel <- append(col_sel, "time") + col_sel <- append(col_sel, data_field) + sites_v <- sites |> + dplyr::select(dplyr::all_of(col_sel)) |> + dplyr::distinct() + } names(sites_v)[2:3] <- c("lon", "lat") sites_v <- data.table::as.data.table(sites_v) @@ -978,7 +1000,7 @@ process_aqs <- # NAD83 to WGS84 sites_v_nad <- - sites_v[Datum == "NAD83"] + sites_v[sites_v$Datum == "NAD83", ] sites_v_nad <- terra::vect( sites_v_nad, @@ -987,23 +1009,34 @@ process_aqs <- ) sites_v_nad <- terra::project(sites_v_nad, "EPSG:4326") # postprocessing: combine WGS84 and new WGS84 records - sites_v_nad <- sites_v_nad[, seq(1, 3)] sites_v_nad <- as.data.frame(sites_v_nad) - sites_v_wgs <- sites_v[Datum == "WGS84"][, -4] - final_sites <- rbind(sites_v_wgs, sites_v_nad) + sites_v_wgs <- sites_v[sites_v$Datum == "WGS84"] + final_sites <- data.table::rbindlist( + list(sites_v_wgs, sites_v_nad), fill = TRUE) + final_sites <- + final_sites[, grep("Datum", names(final_sites), invert = TRUE), with = FALSE] if (!is.null(date)) { date_start <- as.Date(date[1]) date_end <- as.Date(date[2]) date_sequence <- seq(date_start, date_end, "day") - final_sites <- - split(date_sequence, date_sequence) |> - lapply(function(x) { - fs_time <- final_sites - fs_time$time <- x - return(fs_time) - }) - final_sites <- Reduce(rbind, final_sites) + date_sequence <- as.character(date_sequence) + + if (mode == "full") { + final_sites <- + split(date_sequence, date_sequence) |> + lapply(function(x) { + fs_time <- final_sites + fs_time$time <- x + return(fs_time) + }) + final_sites <- data.table::rbindlist(final_sites, fill = TRUE) + } + if (mode == "sparse") { + final_sites <- + final_sites[final_sites$time %in% date_sequence, ] + final_sites <- unique(final_sites) + } } final_sites <- @@ -1022,7 +1055,8 @@ process_aqs <- dim = "XY", coords = c("lon", "lat"), crs = "EPSG:4326" - ) + ), + data.table = final_sites ) return(final_sites) diff --git a/man/process_aqs.Rd b/man/process_aqs.Rd index 0495909c..bd55ad23 100644 --- a/man/process_aqs.Rd +++ b/man/process_aqs.Rd @@ -7,7 +7,9 @@ process_aqs( path = NULL, date = c("2018-01-01", "2022-12-31"), - return_format = "terra", + mode = c("full", "sparse", "location"), + data_field = "Arithmetic.Mean", + return_format = c("terra", "sf", "data.table"), ... ) } @@ -18,7 +20,13 @@ process_aqs( Should be in \code{"YYYY-MM-DD"} format and sorted. If \code{NULL}, only unique locations are returned.} -\item{return_format}{character(1). \code{"terra"} or \code{"sf"}.} +\item{mode}{character(1). One of "full" (all dates * all locations) +or "sparse" (date-location pairs with available data) or +"location" (unique locations).} + +\item{data_field}{character(1). Data field to extract.} + +\item{return_format}{character(1). \code{"terra"} or \code{"sf"} or \code{"data.table"}.} \item{...}{Placeholders.} } @@ -30,6 +38,13 @@ The \code{process_aqs()} function cleans and imports raw air quality monitoring sites, returning a single \code{SpatVector} or sf object. } \note{ -\code{date = NULL} will return a massive data.table -object. Please choose proper \code{date} values. +Choose \code{date} and \code{mode} values with caution. +The function may return a massive data.table, resulting in +a long processing time or even a crash. +} +\seealso{ +\itemize{ +\item \code{\link[=download_aqs_data]{download_aqs_data()}} +\item \href{https://aqs.epa.gov/aqsweb/documents/codetables/parameters.csv}{EPA, n.d., \emph{AQS Parameter Codes}} +} } diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index b0106f78..2e8b23fd 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -961,30 +961,100 @@ testthat::test_that("process_aqs", { aqs <- process_aqs(path = aqssub, date = NULL) ) testthat::expect_no_error( - aqse <- process_aqs( + aqsft <- process_aqs( path = aqssub, - date = c("2022-02-04", "2022-02-28") + date = c("2022-02-04", "2022-02-28"), + mode = "full", + return_format = "terra" + ) + ) + testthat::expect_no_error( + aqsst <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "sparse", + return_format = "terra" + ) + ) + testthat::expect_no_error( + aqslt <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "location", + return_format = "terra" ) ) # expect testthat::expect_s4_class(aqs, "SpatVector") - testthat::expect_s4_class(aqse, "SpatVector") + testthat::expect_s4_class(aqsft, "SpatVector") + testthat::expect_s4_class(aqsst, "SpatVector") + testthat::expect_s4_class(aqslt, "SpatVector") testthat::expect_no_error( - aqssf <- process_aqs(path = aqssub, date = NULL, return_format = "sf") + aqsfs <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "full", + return_format = "sf" + ) + ) + testthat::expect_no_error( + aqsss <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "sparse", + return_format = "sf" + ) ) testthat::expect_no_error( - aqsesf <- process_aqs( + aqsls <- process_aqs( path = aqssub, date = c("2022-02-04", "2022-02-28"), + mode = "location", return_format = "sf" ) ) + testthat::expect_s3_class(aqsfs, "sf") + testthat::expect_s3_class(aqsss, "sf") + testthat::expect_s3_class(aqsls, "sf") + + testthat::expect_no_error( + aqsfd <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "full", + return_format = "data.table" + ) + ) + testthat::expect_no_error( + aqssd <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "sparse", + return_format = "data.table" + ) + ) + testthat::expect_no_error( + aqsld <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "location", + return_format = "data.table" + ) + ) + testthat::expect_s3_class(aqsfd, "data.table") + testthat::expect_s3_class(aqssd, "data.table") + testthat::expect_s3_class(aqsld, "data.table") + + testthat::expect_no_error( + aqssf <- process_aqs(path = aqssub, date = NULL, return_format = "sf") + ) testthat::expect_no_error( aqssf <- process_aqs( path = testd, date = c("2022-02-04", "2022-02-28"), + mode = "location", return_format = "sf" ) ) @@ -1000,7 +1070,6 @@ testthat::test_that("process_aqs", { # expect testthat::expect_s3_class(aqssf, "sf") - testthat::expect_s3_class(aqsesf, "sf") # error cases From 4a7f116552caf0c75f69e420af18b1e38708cc49 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 6 May 2024 17:34:59 -0400 Subject: [PATCH 24/65] 0.2.0dev - Significant changes - Due to bug fix of process_modis_swath, previous calculations are invalidated and should be rerun - Dropped doParallel and foreach dependency: future and future.apply in place - Bug fix - Tests on Date class is removed or replaced with POSIXt - HMS: empty/missing polygon error due to date sequence generator setting (i.e., sub_hyphen=TRUE) - calc_terraclimate: promise before evaluation error due to introducing geom argument - process_modis_swath: empty subdataset processing, clarified documentation on setting subdataset argument - process_modis_warp: first-phase mosaicking is done with stars::st_mosaic - Most calc_* functions for yearly updated raw data: explicit integer assignment complying to calc_check_time logic - Refactoring #75 - Etc. - Edited calculate_covariates tests - Edited calc_gmted for succinct field names - Edited process_gmted for the raw data compatibility - File name change of nei test data (...17.csv -> ...2017.csv) - Styling roxygen2 and elaborating documentation --- DESCRIPTION | 4 +- NAMESPACE | 5 +- R/calculate_covariates.R | 333 +++++++++--------- R/calculate_covariates_auxiliary.R | 19 +- R/process.R | 147 +++++--- R/process_auxiliary.R | 3 +- man/calc_modis_daily.Rd | 9 +- man/calc_modis_par.Rd | 33 +- man/calc_terraclimate.Rd | 6 +- man/download_aqs.Rd | 5 +- man/download_data.Rd | 35 +- man/download_ecoregion.Rd | 7 +- man/download_geos.Rd | 6 +- man/download_gmted.Rd | 5 +- man/download_gridmet.Rd | 2 +- man/download_hms.Rd | 5 +- man/download_huc.Rd | 2 +- man/download_koppen_geiger.Rd | 5 +- man/download_merra2.Rd | 2 +- man/download_modis.Rd | 2 +- man/download_narr_monolevel.Rd | 2 +- man/download_narr_p_levels.Rd | 2 +- man/download_nei.Rd | 4 +- man/download_nlcd.Rd | 5 +- man/download_prism.Rd | 4 +- man/download_sedac_groads.Rd | 5 +- man/download_sedac_population.Rd | 5 +- man/download_terraclimate.Rd | 2 +- man/download_tri.Rd | 2 +- man/process_modis_swath.Rd | 14 +- man/process_modis_warp.Rd | 2 +- man/process_sedac_groads.Rd | 2 +- .../nei/{onroadnc17.csv => onroadnc2017.csv} | 0 tests/testthat/test-calculate_covariates.R | 108 +++--- 34 files changed, 427 insertions(+), 365 deletions(-) rename tests/testdata/nei/{onroadnc17.csv => onroadnc2017.csv} (100%) diff --git a/DESCRIPTION b/DESCRIPTION index 636da352..f9f40753 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: amadeus Title: AMADEUS: A Machine for Data, Environments, and User Setup for common environmental and climate health datasets -Version: 0.1.8 +Version: 0.2.0 Authors@R: c( person("Kyle", "Messier", , "kyle.messier@nih.gov", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-9508-9623")), person("Mitchell", "Manware", role = c("aut", "ctb"), comment = c(ORCID = "0009-0003-6440-6106")), @@ -12,7 +12,7 @@ Authors@R: c( ) Description: A Mechanism/Machine for Data, Environments, and User Setup package for health and climate research. It is fully tested, versioned, and open source and open access. Depends: R (>= 4.1.0) -Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), doParallel, parallelly, stars, foreach, future, tidyr, rlang, rstac, nhdplusTools, archive +Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), parallelly, stars, future, tidyr, rlang, rstac, nhdplusTools, archive Suggests: covr, withr, knitr, rmarkdown, lwgeom, FNN, doRNG Encoding: UTF-8 VignetteBuilder: knitr, rmarkdown diff --git a/NAMESPACE b/NAMESPACE index a96b6b11..737ea86a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -129,7 +129,6 @@ importFrom(data.table,fread) importFrom(data.table,month) importFrom(data.table,rbindlist) importFrom(data.table,year) -importFrom(doParallel,registerDoParallel) importFrom(dplyr,across) importFrom(dplyr,all_of) importFrom(dplyr,as_tibble) @@ -143,10 +142,9 @@ importFrom(dplyr,mutate) importFrom(dplyr,summarize) importFrom(dplyr,ungroup) importFrom(exactextractr,exact_extract) -importFrom(foreach,"%dopar%") -importFrom(foreach,foreach) importFrom(future,cluster) importFrom(future,plan) +importFrom(future.apply,future_lapply) importFrom(httr,GET) importFrom(httr,HEAD) importFrom(methods,is) @@ -164,6 +162,7 @@ importFrom(sf,st_drop_geometry) importFrom(sf,st_geometry) importFrom(sf,st_union) importFrom(stars,read_stars) +importFrom(stars,st_mosaic) importFrom(stars,st_warp) importFrom(stats,aggregate) importFrom(stats,setNames) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 243889b3..9823e46c 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -178,9 +178,9 @@ calc_koppen_geiger <- class_kg = kg_class ) - locs_kg_extract[[locs_id]] <- locs_df[,1] + locs_kg_extract[[locs_id]] <- locs_df[, 1] if (geom) { - locs_kg_extract$geometry <- locs_df[,2] + locs_kg_extract$geometry <- locs_df[, 2] } colnames(locs_kg_extract)[2] <- "value" locs_kg_extract_e <- merge(locs_kg_extract, kg_colclass, by = "value") @@ -289,7 +289,7 @@ calc_nlcd <- function(from, if (!methods::is(from, "SpatRaster")) { stop("from is not a SpatRaster.") } - + # prepare locations locs_prepared <- calc_prepare_locs( from = from, @@ -300,7 +300,7 @@ calc_nlcd <- function(from, ) locs_vector <- locs_prepared[[1]] locs_df <- locs_prepared[[2]] - + year <- try(as.integer(terra::metags(from, name = "year"))) # select points within mainland US and reproject on nlcd crs if necessary us_main <- @@ -313,10 +313,10 @@ calc_nlcd <- function(from, terra::project(y = terra::crs(from)) # subset locs_df to those in us extent - locs_df <- locs_df[ + locs_dfs <- locs_df[ unlist(locs_df[[locs_id]]) %in% unlist(data_vect_b[[locs_id]]), - ] - + ] + # create circle buffers with buf_radius bufs_pol <- terra::buffer(data_vect_b, width = radius) |> sf::st_as_sf() |> @@ -330,7 +330,8 @@ calc_nlcd <- function(from, stack_apply = TRUE, force_df = TRUE, progress = FALSE, - max_cells_in_memory = max_cells) + max_cells_in_memory = max_cells + ) # select only the columns of interest cfpath <- system.file("extdata", "nlcd_classes.csv", package = "amadeus") @@ -345,7 +346,7 @@ calc_nlcd <- function(from, # change column names nlcd_names <- names(nlcd_at_bufs) nlcd_names <- sub(pattern = "frac_", replacement = "", x = nlcd_names) - nlcd_names <- as.numeric(nlcd_names) + nlcd_names <- sort(as.numeric(nlcd_names)) nlcd_names <- nlcd_classes[nlcd_classes$value %in% nlcd_names, c("class")] new_names <- sapply( nlcd_names, @@ -355,7 +356,7 @@ calc_nlcd <- function(from, ) names(nlcd_at_bufs) <- new_names # merge locs_df with nlcd class fractions - new_data_vect <- cbind(locs_df, as.integer(year), nlcd_at_bufs) + new_data_vect <- cbind(locs_dfs, as.integer(year), nlcd_at_bufs) if (geom) { names(new_data_vect)[1:3] <- c(locs_id, "geometry", "time") } else { @@ -415,7 +416,7 @@ calc_ecoregion <- ) locs <- locs_prepared[[1]] locs_df <- locs_prepared[[2]] - + locs_in <- terra::intersect(locs, from) locs_out <- locs[!unlist(locs[[locs_id]]) %in% unlist(locs_in[[locs_id]]), ] @@ -441,7 +442,6 @@ calc_ecoregion <- key3_num <- sprintf("DUM_E3%03d_0_00000", key3_num) key3_num_unique <- sort(unique(key3_num)) - df_lv2 <- split(key2_num_unique, key2_num_unique) |> lapply(function(x) { @@ -462,7 +462,8 @@ calc_ecoregion <- locs_ecoreg <- cbind( locs_df, paste0("1997 - ", data.table::year(Sys.Date())), - df_lv2, df_lv3) + df_lv2, df_lv3 + ) if (geom) { names(locs_ecoreg)[3] <- "description" } else { @@ -499,6 +500,10 @@ calc_ecoregion <- #' Please note that this function does not provide a function to filter #' swaths or tiles, so it is strongly recommended to check and pre-filter #' the file names at users' discretion. +#' @seealso +#' * Preprocessing: [process_modis_merge()], [process_modis_swath()], +#' [process_bluemarble()] +#' * Parallelization: [calc_modis_par()] #' @author Insang Song #' @returns A data.frame object. #' @importFrom terra extract @@ -518,7 +523,7 @@ calc_modis_daily <- function( date = NULL, name_extracted = NULL, fun_summary = "mean", - max_cells = 1e8, + max_cells = 3e7, ... ) { if (!any(methods::is(locs, "SpatVector"), @@ -534,11 +539,7 @@ calc_modis_daily <- function( locs_id) ) } - if (!"time" %in% names(locs)) { - locs$time <- as.POSIXlt(date) - } - stopifnot(methods::is(locs$time, "POSIXt")) - + extract_with_buffer <- function( points, surf, @@ -589,6 +590,7 @@ calc_modis_daily <- function( # multiple columns will get proper names name_range <- seq(ncol(extracted) - name_offset + 1, ncol(extracted), 1) colnames(extracted)[name_range] <- name_extracted + extracted$time <- as.POSIXlt(extracted$time) calc_check_time(covar = extracted, POSIXt = TRUE) return(extracted) } @@ -608,7 +610,8 @@ calc_modis_daily <- function( #' '{name_covariates}{zero-padded buffer radius in meters}', #' e.g., 'MOD_NDVIF_0_50000' where 50 km radius circular buffer #' was used to calculate mean NDVI value. -#' @param subdataset Index or search pattern of subdataset. +#' @param subdataset Indices, names, or search patterns for subdatasets. +#' Find detail usage of the argument in notes. #' @param fun_summary character or function. Function to summarize #' extracted raster values. #' @param nthreads integer(1). Number of threads to be used @@ -642,22 +645,28 @@ calc_modis_daily <- function( #' automatically detected and passed to the function. Please note that #' `locs` here and `path` in `preprocess` functions are assumed to have a #' standard naming convention of raw files from NASA. +#' The argument `subdataset` should be in a proper format +#' depending on `preprocess` function: +#' * `process_modis_merge()`: Regular expression pattern. +#' e.g., `"^LST_"` +#' * `process_modis_swath()`: Subdataset names. +#' e.g., `c("Cloud_Fraction_Day", "Cloud_Fraction_Night")` +#' * `process_bluemarble()`: Subdataset number. +#' e.g., for VNP46A2 product, 3L. #' @seealso See details for setting parallelization: -#' * [`foreach::foreach`] -#' * [`parallelly::makeClusterPSOCK`] -#' * [`parallelly::availableCores`] -#' * [`doParallel::registerDoParallel`] +#' * [`future::plan()`] +#' * [`future.apply::future_lapply()`] +#' * [`parallelly::makeClusterPSOCK()`] +#' * [`parallelly::availableCores()`] #' #' This function leverages the calculation of single-day MODIS #' covariates: -#' * [`calc_modis_daily`] +#' * [`calc_modis_daily()`] #' -#' Also, for preprocessing, see: -#' * [`process_modis_merge`] -#' * [`process_modis_swath`] -#' * [`process_bluemarble`] -#' @importFrom foreach foreach -#' @importFrom foreach %dopar% +#' Also, for preprocessing, please refer to: +#' * [`process_modis_merge()`] +#' * [`process_modis_swath()`] +#' * [`process_bluemarble()`] #' @importFrom methods is #' @importFrom sf st_as_sf #' @importFrom sf st_drop_geometry @@ -667,8 +676,8 @@ calc_modis_daily <- function( #' @importFrom rlang inject #' @importFrom future plan #' @importFrom future cluster +#' @importFrom future.apply future_lapply #' @importFrom parallelly availableWorkers -#' @importFrom doParallel registerDoParallel #' @export calc_modis_par <- function( @@ -683,7 +692,7 @@ calc_modis_par <- nthreads = floor(length(parallelly::availableWorkers()) / 2), package_list_add = NULL, export_list_add = NULL, - max_cells = 1e8, + max_cells = 3e7, ... ) { if (!is.function(preprocess)) { @@ -691,8 +700,9 @@ calc_modis_par <- process_modis_swath, or process_bluemarble.") } # read all arguments + # nolint start hdf_args <- c(as.list(environment()), list(...)) - + # nolint end dates_available <- regmatches(from, regexpr("A20\\d{2,2}[0-3]\\d{2,2}", from)) dates_available <- unique(dates_available) @@ -707,7 +717,7 @@ process_modis_swath, or process_bluemarble.") export_list <- c() package_list <- c("sf", "terra", "exactextractr", "foreach", "data.table", "stars", - "dplyr", "parallelly", "doParallel", "rlang") + "dplyr", "parallelly", "doParallel", "rlang", "amadeus") if (!is.null(export_list_add)) { export_list <- append(export_list, export_list_add) } @@ -716,85 +726,93 @@ process_modis_swath, or process_bluemarble.") } # make clusters - doParallel::registerDoParallel(cores = nthreads) - future::future(future::cluster, workers = nthreads) - - datei <- NULL + # doParallel::registerDoParallel(cores = nthreads) + if (nthreads == 1) { + future::plan(future::sequential) + } else { + future::plan(future::multisession, workers = nthreads) + } + # future::future(future::multicore, workers = nthreads) + idx_date_available <- seq_along(dates_available) + list_date_available <- + split(idx_date_available, idx_date_available) calc_results <- - foreach::foreach( - datei = seq_along(dates_available), - .packages = package_list, - .export = export_list, - .combine = dplyr::bind_rows, - .errorhandling = "pass", - .verbose = TRUE - ) %dopar% { - options(sf_use_s2 = FALSE) - # nolint start - day_to_pick <- dates_available[datei] - # nolint end - day_to_pick <- as.Date(day_to_pick, format = "%Y%j") - - radiusindex <- seq_along(radius) - radiuslist <- split(radiusindex, radiusindex) - - hdf_args <- append(hdf_args, values = list(date = day_to_pick)) - hdf_args <- append(hdf_args, values = list(path = hdf_args$from)) - # unified interface with rlang::inject - vrt_today <- - rlang::inject(preprocess(!!!hdf_args)) - - if (sum(terra::nlyr(vrt_today)) != length(name_covariates)) { - warning("The number of layers in the input raster do not match - the length of name_covariates.\n") - } - - res0 <- - lapply(radiuslist, - function(k) { - name_radius <- - sprintf("%s%05d", - name_covariates, - radius[k]) - - tryCatch({ - extracted <- - calc_modis_daily( - locs = locs_input, - from = vrt_today, - locs_id = locs_id, - date = as.character(day_to_pick), - fun_summary = fun_summary, - name_extracted = name_radius, - radius = radius[k], - max_cells = max_cells - ) - return(extracted) - }, error = function(e) { + future.apply::future_lapply( + list_date_available, + FUN = function (datei) { + options(sf_use_s2 = FALSE) + # nolint start + day_to_pick <- dates_available[datei] + # nolint end + day_to_pick <- as.Date(day_to_pick, format = "%Y%j") + + radiusindex <- seq_along(radius) + radiuslist <- split(radiusindex, radiusindex) + + hdf_args <- append(hdf_args, values = list(date = day_to_pick)) + hdf_args <- append(hdf_args, values = list(path = hdf_args$from)) + # unified interface with rlang::inject + vrt_today <- + rlang::inject(preprocess(!!!hdf_args)) + + if (sum(terra::nlyr(vrt_today)) != length(name_covariates)) { + warning("The number of layers in the input raster do not match + the length of name_covariates.\n") + } + + res0 <- + lapply(radiuslist, + function(k) { name_radius <- sprintf("%s%05d", name_covariates, radius[k]) - error_df <- sf::st_drop_geometry(locs_input) - # coerce to avoid errors - error_df <- as.data.frame(error_df) - error_df <- error_df[, c(locs_id, "time")] - error_df[[name_radius]] <- -99999 - attr(error_df, "error_message") <- e - return(error_df) + + tryCatch({ + extracted <- + calc_modis_daily( + locs = locs_input, + from = vrt_today, + locs_id = locs_id, + date = as.character(day_to_pick), + fun_summary = fun_summary, + name_extracted = name_radius, + radius = radius[k], + max_cells = max_cells + ) + return(extracted) + }, error = function(e) { + name_radius <- + sprintf("%s%05d", + name_covariates, + radius[k]) + error_df <- sf::st_drop_geometry(locs_input) + # coerce to avoid errors + error_df <- as.data.frame(error_df) + error_df <- error_df[, c(locs_id, "time")] + error_df[[name_radius]] <- -99999 + attr(error_df, "error_message") <- e + return(error_df) + } + ) } - ) - } - ) - res <- - Reduce(function(x, y) { - dplyr::left_join(x, y, - by = c(locs_id, "time") ) - }, - res0) - return(res) - } + res <- + Reduce(function(x, y) { + dplyr::left_join(x, y, + by = c(locs_id, "time") + ) + }, + res0) + return(res) + + }, + future.packages = package_list, + future.globals = TRUE, + future.seed = TRUE + ) + calc_results <- do.call(dplyr::bind_rows, calc_results) + Sys.sleep(1L) return(calc_results) } @@ -844,6 +862,7 @@ calc_temporal_dummies <- return(dt_dum) } + calc_check_time(covar = locs, POSIXt = TRUE) # year vec_year <- data.table::year(locs$time) dt_year_dum <- dummify(vec_year, year) @@ -875,7 +894,6 @@ calc_temporal_dummies <- dt_month_dum, dt_wday_dum ) - calc_check_time(covar = locs_dums, POSIXt = TRUE) return(locs_dums) } @@ -946,8 +964,6 @@ calc_sedc <- sedc_bandwidth = NULL, target_fields = NULL ) { - # define sources, set SEDC exponential decay range - if (!methods::is(locs, "SpatVector")) { locs <- try(terra::vect(locs)) } @@ -984,7 +1000,6 @@ The result may not be accurate.\n", # near features with distance argument: only returns integer indices # threshold is set to the twice of sedc_bandwidth - # lines 895-900 may overlap with distance arg in 912-913 res_nearby <- terra::nearby(locs, from_in, distance = sedc_bandwidth * 2) # attaching actual distance @@ -1006,7 +1021,7 @@ The result may not be accurate.\n", dplyr::left_join(dist_nearby_df) |> # per the definition in # https://mserre.sph.unc.edu/BMElab_web/SEDCtutorial/index.html - # exp(-3) is about 0.05 + # exp(-3) is about 0.05 * (value at origin) dplyr::mutate(w_sedc = exp((-3 * dist) / sedc_bandwidth)) |> dplyr::group_by(!!rlang::sym(locs_id)) |> dplyr::summarize( @@ -1022,7 +1037,7 @@ The result may not be accurate.\n", attr(res_sedc, "sedc_bandwidth") <- sedc_bandwidth attr(res_sedc, "sedc_threshold") <- sedc_bandwidth * 2 - calc_check_time(covar = extracted, POSIXt = TRUE) + calc_check_time(covar = res_sedc, POSIXt = TRUE) return(res_sedc) } @@ -1086,8 +1101,7 @@ calc_tri <- function( # inner lapply list_radius <- split(radius, radius) list_locs_tri <- - lapply( - list_radius, + Map( function(x) { locs_tri_s <- calc_sedc( @@ -1098,7 +1112,8 @@ calc_tri <- function( target_fields = tri_cols ) return(locs_tri_s) - } + }, + list_radius ) # bind element data.frames into one df_tri <- Reduce(function(x, y) dplyr::full_join(x, y), list_locs_tri) @@ -1106,7 +1121,7 @@ calc_tri <- function( df_tri <- dplyr::left_join(as.data.frame(locs), df_tri) } # read attr - df_tri$time <- attr(from, "tri_year") + df_tri$time <- as.integer(attr(from, "tri_year")) calc_check_time(covar = df_tri, POSIXt = FALSE) return(df_tri) } @@ -1141,6 +1156,7 @@ calc_nei <- function( # spatial join locs_re <- terra::project(locs, terra::crs(from)) locs_re <- terra::intersect(locs_re, from) + locs_re <- as.data.frame(locs_re) calc_check_time(covar = locs_re, POSIXt = FALSE) return(locs_re) } @@ -1178,7 +1194,7 @@ calc_hms <- function( ...) { #### check for null parameters check_for_null_parameters(mget(ls())) - #### from == character indicates no wildfire smoke polumes are present + #### from == character indicates no wildfire smoke plumes are present #### return 0 for all locs and dates if ("character" %in% class(from)) { cat(paste0( @@ -1237,7 +1253,7 @@ calc_hms <- function( from$Date[nrow(from)], format = "%Y%m%d" ), - sub_hyphen = TRUE + sub_hyphen = FALSE ) #### empty location data.frame sites_extracted <- NULL @@ -1293,7 +1309,7 @@ calc_hms <- function( ) } #### check for missing dates (missing polygons) - if (!(identical(date_sequence, from$Date))) { + if (!(identical(date_sequence, sort(unique(from$Date))))) { cat(paste0( "Detected absent smoke plume polygons.\n" )) @@ -1325,8 +1341,12 @@ calc_hms <- function( } #### coerce binary to integer sites_extracted[, 3] <- as.integer(sites_extracted[, 3]) + #### date to POSIXlt + sites_extracted$time <- as.POSIXlt(sites_extracted$time) #### order by date - sites_extracted_ordered <- sites_extracted[order(sites_extracted$time), ] + sites_extracted_ordered <- as.data.frame( + sites_extracted[order(sites_extracted$time), ] + ) cat(paste0( "Returning ", layer_name, @@ -1334,7 +1354,7 @@ calc_hms <- function( )) calc_check_time(covar = sites_extracted_ordered, POSIXt = TRUE) #### return data.frame - return(data.frame(sites_extracted_ordered)) + return(sites_extracted_ordered) } #' Calculate elevation covariates @@ -1400,37 +1420,35 @@ calc_gmted <- function( time_type = "year" ) #### variable column name - variable_name <- paste0( - gsub( - " ", - "_", - tolower( - process_gmted_codes( - substr( - strsplit( - names(from), - "_" - )[[1]][2], - 1, - 2 - ), - statistic = TRUE, - invert = TRUE - ) - ) - ), - "r", - substr( - strsplit( - names(from), - "_" - )[[1]][2], - 3, - 4 - ), - "_", - radius + statistic_codes <- c("be", "ds", "md", "mi", "mn", "mx", "sd") + statistic_to <- c( + "BRK", "SUB", "MED", "MEA", "MIN", "MAX", "STD" + ) + name_from <- names(from) + code_unique <- + regmatches( + name_from, + regexpr( + paste0("(", + paste(statistic_codes, collapse = "|"), + ")[0-9]{2,2}"), + name_from + ) + ) + statistic <- substr(code_unique, 1, 2) + resolution <- substr(code_unique, 3, 4) + statistic_to <- + sprintf( + "%s%s", + statistic_to[match(statistic, statistic_codes)], + resolution ) + + variable_name <- paste0( + statistic_to, + "_", + sprintf("%05d", as.integer(radius)) + ) if (geom) { #### convert integer to numeric sites_extracted[, 4] <- as.numeric(sites_extracted[, 4]) @@ -1439,13 +1457,14 @@ calc_gmted <- function( #### convert integer to numeric sites_extracted[, 3] <- as.numeric(sites_extracted[, 3]) names(sites_extracted) <- c(locs_id, "time", variable_name) - } calc_check_time(covar = sites_extracted, POSIXt = FALSE) #### return data.frame return(data.frame(sites_extracted)) } + + #' Calculate meteorological covariates #' @description #' Extract meteorological values at point locations. Returns a \code{data.frame} @@ -1965,12 +1984,12 @@ calc_gridmet <- function( #' @importFrom terra crs #' @export calc_terraclimate <- function( - from, - locs, + from = NULL, + locs = NULL, locs_id = NULL, radius = 0, fun = "mean", - geom = geom, + geom = FALSE, ...) { #### prepare locations list sites_list <- calc_prepare_locs( @@ -2084,6 +2103,6 @@ calc_lagged <- function( #### merge with other locations variables_merge <- rbind(variables_merge, variables_return_date) } - calc_check_time(covar = sites_extracted, POSIXt = TRUE) + calc_check_time(covar = variables_merge, POSIXt = TRUE) return(variables_merge) } diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 2e346721..2c211695 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -75,7 +75,7 @@ calc_setcolumns <- function( cov_index <- which( !(c(names_from %in% c( locs_id, "geometry", "time", "lat", "lon", "level", "description" - )) + )) ) ) for (c in seq_along(cov_index)) { @@ -119,7 +119,7 @@ calc_setcolumns <- function( } #### check for unique names stopifnot(length(names_return) == length(unique(names_return))) - colnames(from) <- names_return + colnames(from) <- toupper(names_return) return(from) } @@ -241,7 +241,7 @@ calc_prepare_locs <- function( select = c(locs_id, "geometry") ) } else { - #### site identifiers only + #### site identifiers only sites_id <- subset( terra::as.data.frame(sites_e), select = locs_id @@ -302,15 +302,15 @@ calc_time <- function( #' @keywords internal #' @export calc_check_time <- function( - covar, - POSIXt = TRUE + covar, + POSIXt = TRUE ) { stopifnot(methods::is(covar, "data.frame")) if ("time" %in% names(covar)) { if (POSIXt) { - stopifnot(methods::is(covar$time), "POSIXt") + stopifnot(all(sapply(covar$time, methods::is, "POSIXt"))) } else { - stopifnot(methods::is(covar$time), "integer") + stopifnot(all(sapply(covar$time, methods::is, "integer"))) } } else { message( @@ -388,6 +388,8 @@ calc_worker <- function( #### extract level (if applicable) if (!is.null(level)) { data_level <- data_split[level] + } else { + data_level <- "" } #### message calc_message( @@ -406,8 +408,7 @@ calc_worker <- function( progress = FALSE, force_df = TRUE, fun = fun, - max_cells_in_memory = max_cells, - ... + max_cells_in_memory = max_cells ) } else if (terra::geomtype(locs_vector) == "points") { #### apply terra::extract for points diff --git a/R/process.R b/R/process.R index 2789da15..2afa7781 100644 --- a/R/process.R +++ b/R/process.R @@ -492,7 +492,7 @@ process_modis_warp <- function( path = NULL, cellsize = 0.1, - threshold = cellsize * 2, + threshold = cellsize * 4, crs = 4326, ... ) { @@ -502,14 +502,13 @@ process_modis_warp <- stars::st_warp( ras, crs = crs, + segments = 500, cellsize = cellsize, threshold = threshold ) return(rtd) } - - # nolint start #' Mosaic MODIS swaths #' @description This function will return a `SpatRaster` object with @@ -523,16 +522,20 @@ process_modis_warp <- #' @param path character. Full paths of hdf files. #' @param date character(1). Date to query. #' @param subdataset character. Subdatasets to process. +#' __Unlike other preprocessing functions, this argument should specify +#' the exact subdataset name.__ For example, when using MOD06_L2 product, +#' one may specify `c("Cloud_Fraction", "Cloud_Optical_Thickness")`, +#' etc. The subdataset names can be found in `terra::describe()` output. #' @param suffix character(1). Should be formatted `:{product}:`, #' e.g., `:mod06:` #' @param resolution numeric(1). Resolution of output raster. -#' Unit is degree. +#' Unit is degree (decimal degree in WGS84). #' @param ... For internal use. #' @seealso -#' * [`process_modis_warp`] +#' * [`process_modis_warp()`], [`stars::read_stars()`], [`stars::st_warp()`] #' * [GDAL HDF4 driver documentation](https://gdal.org/drivers/raster/hdf4.html) -#' * [`terra::describe`]: to list the full subdataset list with `sds = TRUE` -#' * [`terra::sprc`], [`terra::rast`] +#' * [`terra::describe()`]: to list the full subdataset list with `sds = TRUE` +#' * [`terra::sprc()`], [`terra::rast()`] #' @returns #' * a `SpatRaster` object (crs = `"EPSG:4326"`): if `path` is a single file with #' full specification of subdataset. @@ -540,8 +543,9 @@ process_modis_warp <- #' @author Insang Song #' @importFrom terra rast #' @importFrom terra crop +#' @importFrom terra ext #' @importFrom terra mosaic -#' @importFrom terra varnames +#' @importFrom stars st_mosaic #' @importFrom terra values #' @importFrom terra sprc #' @export @@ -561,51 +565,71 @@ process_modis_swath <- header <- "HDF4_EOS:EOS_SWATH:" ras_mod06 <- vector("list", length = length(subdataset)) datejul <- strftime(date, format = "%Y%j") + ## FIXME: this part may result in underperformance. + ## Find a way to optimize this part. paths_today <- grep(sprintf("A%s", datejul), path, value = TRUE) # if two or more paths are put in, # these are read into a list then mosaicked - if (length(path) > 1) { - for (element in seq_along(subdataset)) { - target_text <- - sprintf("%s%s%s%s", header, paths_today, suffix, subdataset[element]) - # rectified stars objects to SpatRaster - mod06_element <- split(target_text, target_text) |> - lapply(process_modis_warp, cellsize = resolution) |> - lapply(terra::rast) - # Remove all NA layers to avoid erroneous values - mod06_element_nas <- - sapply( - mod06_element, - function(x) { - all(is.na(terra::values(x))) - } - ) - mod06_element <- mod06_element[!mod06_element_nas] + for (element in seq_along(subdataset)) { + target_text <- + sprintf("%s%s%s%s", header, paths_today, suffix, subdataset[element]) + # rectified stars objects to SpatRaster + mod06_element <- split(target_text, target_text) |> + lapply(process_modis_warp, cellsize = resolution) + # Remove all NA layers to avoid erroneous values + mod06_element_nas <- + sapply( + mod06_element, + function(x) { + xvals <- x[[subdataset[element]]] + all(is.na(xvals)) || all(is.nan(xvals)) + } + ) + mod06_element <- + mod06_element[!mod06_element_nas & !is.null(mod06_element_nas)] + + # prepare a fail-safe alternative return + # It will be used again later. + alt <- terra::rast( + xmin = -128, + xmax = -64, + ymin = 20, + ymax = 52, + resolution = resolution + ) + # initialize values with NA + alt[] <- NA + + if (is.null(mod06_element) || length(mod06_element) == 0) { + message("All layers are NA or NaN.") + mod06_element_mosaic <- alt + } else { # mosaick the warped SpatRasters into one - mod06_element_mosaic <- Reduce(f = terra::mosaic, x = mod06_element) - ras_mod06[[element]] <- mod06_element_mosaic + mod06_element_mosaic <- + do.call(stars::st_mosaic, mod06_element) |> + terra::rast() # assigning variable name - names(ras_mod06)[element] <- subdataset[element] + mod06_element_mosaic <- + terra::crop(mod06_element_mosaic, terra::ext(alt)) } - # SpatRasterCollection can accommodate multiple SpatRasters - # with different extents (most flexible kind) - mod06_sprc <- terra::sprc(ras_mod06) - # post-hoc: stack multiple layers with different extent - # into one SpatRaster - # 1. mosaic all layers into one - mod06_mosaic <- terra::mosaic(mod06_sprc, fun = "first") - # 2. Assign NAs to prepare "etching" - terra::values(mod06_mosaic) <- NA - # 3. Looping main "etching"; each element is put first - mod06_etched <- - sapply(mod06_sprc, terra::mosaic, y = mod06_mosaic, fun = "first") - # 4. stack - mod06_return <- do.call(c, mod06_etched) - } else { - mod06_return <- - terra::rast(process_modis_warp(path, cellsize = resolution)) + names(mod06_element_mosaic) <- subdataset[element] + ras_mod06[[element]] <- mod06_element_mosaic } + # SpatRasterCollection can accommodate multiple SpatRasters + # with different extents (most flexible kind) + mod06_sprc <- terra::sprc(ras_mod06) + # post-hoc: stack multiple layers with different extent + # into one SpatRaster + # 1. mosaic all layers into one + mod06_mosaic <- terra::mosaic(mod06_sprc, fun = "median") + # 2. Assign NAs to prepare "etching"; NA will result in NaNs + terra::values(mod06_mosaic) <- NA + # 3. Looping main "etching"; each element is put first + mod06_etched <- + sapply(mod06_sprc, terra::mosaic, y = mod06_mosaic, fun = "first") + # 4. stack + mod06_return <- do.call(c, mod06_etched) return(mod06_return) } @@ -869,11 +893,19 @@ process_nei <- function( stop("year should be one of 2017 or 2020.\n") } # Concatenate NEI csv files - csvs_nei <- list.files(path = path, pattern = "*.csv$", recursive = TRUE, full.names = TRUE) + csvs_nei <- + list.files( + path = path, + pattern = "*.csv$", + recursive = TRUE, + full.names = TRUE + ) csvs_nei <- grep(year, csvs_nei, value = TRUE) + if (is.null(csvs_nei) || length(csvs_nei) == 0) { + stop("No files found for the year. The file names should include the year") + } csvs_nei <- lapply(csvs_nei, data.table::fread) csvs_nei <- data.table::rbindlist(csvs_nei) - # column name readjustment target_nm <- c("fips code", "total emissions", "emissions uom") # not grep-ping at once for flexibility @@ -898,14 +930,14 @@ process_nei <- function( TRF_NEINP_0_00000 = sum(emissions_total_ton, na.rm = TRUE) ), by = geoid] - csvs_nei$nei_year <- year + csvs_nei$time <- as.integer(year) # read county vector cnty_geoid_guess <- grep("GEOID", names(county)) names(county)[cnty_geoid_guess] <- "geoid" county$geoid <- sprintf("%05d", as.integer(county$geoid)) - cnty_vect <- merge(county, csvs_nei, by = "geoid") - cnty_vect <- cnty_vect[, c("geoid", "nei_year", "TRF_NEINP_0_00000")] + cnty_vect <- merge(county, as.data.frame(csvs_nei), by = "geoid") + cnty_vect <- cnty_vect[, c("geoid", "time", "TRF_NEINP_0_00000")] return(cnty_vect) } @@ -1148,7 +1180,7 @@ process_sedac_population <- function( #' @param path character(1). Path to geodatabase or shapefiles. #' @param ... Placeholders. #' @note U.S. context. The returned `SpatVector` object contains a -#' `$time` column to represent the temporal range covered by the +#' `$description` column to represent the temporal range covered by the #' dataset. For more information, see . #' @author Insang Song #' @returns a `SpatVector` object @@ -1166,7 +1198,7 @@ process_sedac_groads <- function( #### import data data <- terra::vect(path) #### time period - data$time <- "1980 - 2010" + data$description <- "1980 - 2010" return(data) } @@ -1253,7 +1285,7 @@ process_hms <- function( #### subset to density of interest data_density <- data_date_p[ tolower(data_date_p$Density) == tolower(variable) - ] + ] #### absent polygons (ie. December 31, 2018) if (nrow(data_density) == 0) { cat(paste0( @@ -1417,8 +1449,10 @@ process_gmted <- function( #### identify file path paths <- list.files( path, - full.names = TRUE + full.names = TRUE, + recursive = TRUE ) + #### select only the folder containing data data_paths <- unique( grep( @@ -1432,7 +1466,7 @@ process_gmted <- function( value = TRUE ) ) - data_path <- data_paths[endsWith(data_paths, "_grd")] + data_path <- data_paths[grep("(_grd$|w001001.adf)", data_paths)] #### import data data <- terra::rast(data_path) #### layer name @@ -1454,7 +1488,8 @@ process_gmted <- function( ")" ) #### year - terra::metags(data) <- c(year = 2010) + terra::metags(data) <- + c(year = 2010L) #### set coordinate reference system return(data) } diff --git a/R/process_auxiliary.R b/R/process_auxiliary.R index ed513b85..38551e12 100644 --- a/R/process_auxiliary.R +++ b/R/process_auxiliary.R @@ -311,7 +311,8 @@ process_locs_radius <- } else if (radius > 0) { sites_buffer <- terra::buffer( locs, - radius + radius, + quadsegs = 180L ) return(sites_buffer) } diff --git a/man/calc_modis_daily.Rd b/man/calc_modis_daily.Rd index b4357d5f..d92e1532 100644 --- a/man/calc_modis_daily.Rd +++ b/man/calc_modis_daily.Rd @@ -12,7 +12,7 @@ calc_modis_daily( date = NULL, name_extracted = NULL, fun_summary = "mean", - max_cells = 1e+08, + max_cells = 3e+07, ... ) } @@ -55,6 +55,13 @@ Please note that this function does not provide a function to filter swaths or tiles, so it is strongly recommended to check and pre-filter the file names at users' discretion. } +\seealso{ +\itemize{ +\item Preprocessing: \code{\link[=process_modis_merge]{process_modis_merge()}}, \code{\link[=process_modis_swath]{process_modis_swath()}}, +\code{\link[=process_bluemarble]{process_bluemarble()}} +\item Parallelization: \code{\link[=calc_modis_par]{calc_modis_par()}} +} +} \author{ Insang Song } diff --git a/man/calc_modis_par.Rd b/man/calc_modis_par.Rd index f17fc22f..bd2cfb3a 100644 --- a/man/calc_modis_par.Rd +++ b/man/calc_modis_par.Rd @@ -16,7 +16,7 @@ calc_modis_par( nthreads = floor(length(parallelly::availableWorkers())/2), package_list_add = NULL, export_list_add = NULL, - max_cells = 1e+08, + max_cells = 3e+07, ... ) } @@ -40,7 +40,8 @@ The calculated covariate names will have a form of e.g., 'MOD_NDVIF_0_50000' where 50 km radius circular buffer was used to calculate mean NDVI value.} -\item{subdataset}{Index or search pattern of subdataset.} +\item{subdataset}{Indices, names, or search patterns for subdatasets. +Find detail usage of the argument in notes.} \item{fun_summary}{character or function. Function to summarize extracted raster values.} @@ -84,26 +85,36 @@ Common arguments in \code{preprocess} functions such as \code{date} and \code{pa automatically detected and passed to the function. Please note that \code{locs} here and \code{path} in \code{preprocess} functions are assumed to have a standard naming convention of raw files from NASA. +The argument \code{subdataset} should be in a proper format +depending on \code{preprocess} function: +\itemize{ +\item \code{process_modis_merge()}: Regular expression pattern. +e.g., \code{"^LST_"} +\item \code{process_modis_swath()}: Subdataset names. +e.g., \code{c("Cloud_Fraction_Day", "Cloud_Fraction_Night")} +\item \code{process_bluemarble()}: Subdataset number. +e.g., for VNP46A2 product, 3L. +} } \seealso{ See details for setting parallelization: \itemize{ -\item \code{\link[foreach:foreach]{foreach::foreach}} -\item \code{\link[parallelly:makeClusterPSOCK]{parallelly::makeClusterPSOCK}} -\item \code{\link[parallelly:availableCores]{parallelly::availableCores}} -\item \code{\link[doParallel:registerDoParallel]{doParallel::registerDoParallel}} +\item \code{\link[future:plan]{future::plan()}} +\item \code{\link[future.apply:future_lapply]{future.apply::future_lapply()}} +\item \code{\link[parallelly:makeClusterPSOCK]{parallelly::makeClusterPSOCK()}} +\item \code{\link[parallelly:availableCores]{parallelly::availableCores()}} } This function leverages the calculation of single-day MODIS covariates: \itemize{ -\item \code{\link{calc_modis_daily}} +\item \code{\link[=calc_modis_daily]{calc_modis_daily()}} } -Also, for preprocessing, see: +Also, for preprocessing, please refer to: \itemize{ -\item \code{\link{process_modis_merge}} -\item \code{\link{process_modis_swath}} -\item \code{\link{process_bluemarble}} +\item \code{\link[=process_modis_merge]{process_modis_merge()}} +\item \code{\link[=process_modis_swath]{process_modis_swath()}} +\item \code{\link[=process_bluemarble]{process_bluemarble()}} } } diff --git a/man/calc_terraclimate.Rd b/man/calc_terraclimate.Rd index 6c98b872..4f97714d 100644 --- a/man/calc_terraclimate.Rd +++ b/man/calc_terraclimate.Rd @@ -5,12 +5,12 @@ \title{Calculate TerraClimate covariates} \usage{ calc_terraclimate( - from, - locs, + from = NULL, + locs = NULL, locs_id = NULL, radius = 0, fun = "mean", - geom = geom, + geom = FALSE, ... ) } diff --git a/man/download_aqs.Rd b/man/download_aqs.Rd index 3b302bfb..22500b67 100644 --- a/man/download_aqs.Rd +++ b/man/download_aqs.Rd @@ -58,9 +58,8 @@ the text file containing download commands. Default is FALSE.} Default \code{FALSE}.} } \value{ -NULL; Separate comma-separated value (CSV) files of -monitors and the daily representative values -will be stored in \code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +\code{directory_to_save}. } \description{ The \code{download_aqs()} function accesses and downloads Air Quality System (AQS) data from the \href{https://aqs.epa.gov/aqsweb/airdata/download_files.html}{U.S. Environmental Protection Agency's (EPA) Pre-Generated Data Files}. diff --git a/man/download_data.Rd b/man/download_data.Rd index c8db1e4f..d61c038c 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -38,23 +38,24 @@ The \code{download_data()} function accesses and downloads atmospheric, meteorol \seealso{ For details of each download function per dataset, Please refer to: -\item \link{`download_aqs`}: \code{"aqs"}, \code{"AQS"} -\item \link{`download_ecoregion`}: \code{"ecoregions"}, \code{"ecoregion"} -\item \link{`download_geos`}: \code{"geos"} -\item \link{`download_gmted`}: \code{"gmted"}, \code{"GMTED"} -\item \link{`download_koppen_geiger`}: \code{"koppen"}, \code{"koppengeiger"} -\item \link{`download_merra2`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} -\item \link{`download_narr_monolevel`}: \code{"narr_monolevel"}, \code{"monolevel"} -\item \link{`download_narr_p_levels`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} -\item \link{`download_nlcd`}: \code{"nlcd"}, \code{"NLCD"} -\item \link{`download_hms`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} -\item \link{`download_sedac_groads`}: \code{"sedac_groads"}, \code{"groads"} -\item \link{`download_sedac_population`}: \code{"sedac_population"}, \code{"population"} -\item \link{`download_modis`}: \code{"modis"}, \code{"MODIS"} -\item \link{`download_tri`}: \code{"tri"}, \code{"TRI"} -\item \link{`download_nei`}: \code{"nei"}, \code{"NEI"} -\item \link{`download_gridmet`}: \code{"gridMET"}, \code{"gridmet"} -\item \link{`download_terraclimate`}: \code{"TerraClimate"}, \code{"terraclimate"} +\itemize{ +\item \link{`download_aqs_data`}: \code{"aqs"}, \code{"AQS"} +\item \link{`download_ecoregion_data`}: \code{"ecoregions"}, \code{"ecoregion"} +\item \link{`download_geos_data`}: \code{"geos"} +\item \link{`download_gmted_data`}: \code{"gmted"}, \code{"GMTED"} +\item \link{`download_koppen_geiger_data`}: \code{"koppen"}, \code{"koppengeiger"} +\item \link{`download_merra2_data`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} +\item \link{`download_narr_monolevel_data`}: \code{"narr_monolevel"}, \code{"monolevel"} +\item \link{`download_narr_p_levels_data`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} +\item \link{`download_nlcd_data`}: \code{"nlcd"}, \code{"NLCD"} +\item \link{`download_hms_data`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} +\item \link{`download_sedac_groads_data`}: \code{"sedac_groads"}, \code{"groads"} +\item \link{`download_sedac_population_data`}: \code{"sedac_population"}, \code{"population"} +\item \link{`download_modis_data`}: \code{"modis"}, \code{"MODIS"} +\item \link{`download_tri_data`}: \code{"tri"}, \code{"TRI"} +\item \link{`download_nei_data`}: \code{"nei"}, \code{"NEI"} +\item \link{`download_gridmet_data`}: \code{"gridMET"}, \code{"gridmet"} +\item \link{`download_terraclimate_data`}: \code{"TerraClimate"}, \code{"terraclimate"} } } \author{ diff --git a/man/download_ecoregion.Rd b/man/download_ecoregion.Rd index 5479def8..bd7d71d0 100644 --- a/man/download_ecoregion.Rd +++ b/man/download_ecoregion.Rd @@ -42,11 +42,12 @@ the text file containing download commands.} \item{unzip}{logical(1). Unzip zip files. Default \code{TRUE}.} -\item{remove_zip}{logical(1). Remove zip file from directory_to_download. -Default \code{FALSE}.} +\item{remove_zip}{logical(1). Remove zip file from +\code{directory_to_download}. Default \code{FALSE}.} } \value{ -NULL; +NULL; Zip and/or data files will be downloaded and stored in +\code{directory_to_save}. } \description{ The \code{download_ecoregion()} function accesses and downloads United States Ecoregions data from the \href{https://www.epa.gov/eco-research/ecoregions}{U.S. Environmental Protection Agency's (EPA) Ecorgions}. Level 3 data, where all pieces of information in the higher levels are included, are downloaded. diff --git a/man/download_geos.Rd b/man/download_geos.Rd index ab72fa62..766cfc67 100644 --- a/man/download_geos.Rd +++ b/man/download_geos.Rd @@ -25,7 +25,9 @@ data. Format YYYY-MM-DD (ex. September 1, 2023 = \code{"2023-09-01"}).} \item{date_end}{character(1). length of 10. End date for downloading data. Format YYYY-MM-DD (ex. September 1, 2023 = \code{"2023-09-01"}).} -\item{directory_to_save}{character(1). Directory to save data.} +\item{directory_to_save}{character(1). Directory to save data. +Sub-directories will be created within \code{directory_to_save} for each +GEOS-CF collection.} \item{acknowledgement}{logical(1). By setting \code{TRUE} the user acknowledges that the data downloaded using this function may be very @@ -40,7 +42,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Hourly netCDF (.nc4) files will be stored in a +NULL; netCDF (.nc4) files will be stored in a collection-specific folder within \code{directory_to_save}. } \description{ diff --git a/man/download_gmted.Rd b/man/download_gmted.Rd index f29c901b..ce6440e6 100644 --- a/man/download_gmted.Rd +++ b/man/download_gmted.Rd @@ -46,9 +46,8 @@ the text file containing download commands. Default is FALSE.} Default is \code{FALSE}.} } \value{ -NULL; Statistic and resolution-specific zip files will be stored in -\code{directory_to_download}, and directories containing raw ASCII Grid data -will be stored in \code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +\code{directory_to_save}. } \description{ The \code{download_gmted()} function accesses and downloads Global diff --git a/man/download_gridmet.Rd b/man/download_gridmet.Rd index 52745adc..4ef5c8cc 100644 --- a/man/download_gridmet.Rd +++ b/man/download_gridmet.Rd @@ -41,7 +41,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +NULL; netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ diff --git a/man/download_hms.Rd b/man/download_hms.Rd index 662abc36..403df0dc 100644 --- a/man/download_hms.Rd +++ b/man/download_hms.Rd @@ -56,9 +56,8 @@ from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if \code{data_format = "KML"}.)} } \value{ -NULL; Zip files and unzipped data files will be stored in -"/zip_files" and "/data_files" sub-directories, respectively, within the -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_hms()} function accesses and downloads diff --git a/man/download_huc.Rd b/man/download_huc.Rd index 2beffda5..affbfcbf 100644 --- a/man/download_huc.Rd +++ b/man/download_huc.Rd @@ -38,7 +38,7 @@ the text file containing download commands.} Default is \code{FALSE}. Not working for this function since HUC data is in 7z format.} } \value{ -None. Downloaded files will be stored in \code{directory_to_save}. +NULL. Downloaded files will be stored in \code{directory_to_save}. } \description{ NHDPlus data provides the most comprehensive and high-resolution diff --git a/man/download_koppen_geiger.Rd b/man/download_koppen_geiger.Rd index b472b60c..926cd4f5 100644 --- a/man/download_koppen_geiger.Rd +++ b/man/download_koppen_geiger.Rd @@ -46,9 +46,8 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip files and unzipped data files will be stored in -"/zip_files" and "/data_files" sub-directories, respectively, within the -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_koppen_geiger()} function accesses and downloads diff --git a/man/download_merra2.Rd b/man/download_merra2.Rd index 02b202ab..070dc701 100644 --- a/man/download_merra2.Rd +++ b/man/download_merra2.Rd @@ -48,7 +48,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Daily netCDF (.nc4) files will be stored in a +NULL; netCDF (.nc4) files will be stored in a collection-specific folder within \code{directory_to_save}. } \description{ diff --git a/man/download_modis.Rd b/man/download_modis.Rd index f4196b40..85e56764 100644 --- a/man/download_modis.Rd +++ b/man/download_modis.Rd @@ -56,7 +56,7 @@ large and use lots of machine storage and memory.} the text file containing download commands.} } \value{ -NULL; Raw HDF (.hdf) files will be stored in +NULL; HDF (.hdf) files will be stored in \code{directory_to_save}. } \description{ diff --git a/man/download_narr_monolevel.Rd b/man/download_narr_monolevel.Rd index 05493322..7d4814f8 100644 --- a/man/download_narr_monolevel.Rd +++ b/man/download_narr_monolevel.Rd @@ -40,7 +40,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +NULL; netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ diff --git a/man/download_narr_p_levels.Rd b/man/download_narr_p_levels.Rd index 4f69d00e..ecb28eda 100644 --- a/man/download_narr_p_levels.Rd +++ b/man/download_narr_p_levels.Rd @@ -40,7 +40,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Monthly netCDF (.nc) files will be stored in +NULL; netCDF (.nc) files will be stored in \code{directory_to_save}. } \description{ diff --git a/man/download_nei.Rd b/man/download_nei.Rd index 0abd7150..5b842b01 100644 --- a/man/download_nei.Rd +++ b/man/download_nei.Rd @@ -47,8 +47,8 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Yearly comma-separated value (CSV) files will be stored in -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_nei()} function accesses and downloads road emissions data from the \href{https://www.epa.gov/air-emissions-inventories/national-emissions-inventory-nei}{U.S Environmental Protection Agency's (EPA) National Emissions Inventory (NEI)}. diff --git a/man/download_nlcd.Rd b/man/download_nlcd.Rd index 686d164a..521b1ad0 100644 --- a/man/download_nlcd.Rd +++ b/man/download_nlcd.Rd @@ -45,9 +45,8 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip files and unzipped data files will be stored in -"/zip_files" and "/data_files" sub-directories, respectively, within the -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_nlcd()} function accesses and downloads diff --git a/man/download_prism.Rd b/man/download_prism.Rd index a44ff6b8..3dadecf5 100644 --- a/man/download_prism.Rd +++ b/man/download_prism.Rd @@ -63,8 +63,8 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; .bil (normals) or single grid files depending on the format choice. -\code{directory_to_save}. +NULL; .bil (normals) or single grid files depending on the format +choice will be stored in \code{directory_to_save}. } \description{ Accesses and downloads Oregon State University's diff --git a/man/download_sedac_groads.Rd b/man/download_sedac_groads.Rd index 8c0d0582..cda88773 100644 --- a/man/download_sedac_groads.Rd +++ b/man/download_sedac_groads.Rd @@ -45,9 +45,8 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip files and unzipped data files will be stored in -"/zip_files" and "/data_files" sub-directories, respectively, within the -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_sedac_groads()} function accesses and downloads diff --git a/man/download_sedac_population.Rd b/man/download_sedac_population.Rd index 2537bc8c..73840a15 100644 --- a/man/download_sedac_population.Rd +++ b/man/download_sedac_population.Rd @@ -49,9 +49,8 @@ the text file containing download commands.} Default is \code{FALSE}.} } \value{ -NULL; Zip files and unzipped data files will be stored in -"/zip_files" and "/data_files" sub-directories, respectively, within the -\code{directory_to_save}. +NULL; Zip and/or data files will be downloaded and stored in +respective sub-directories within \code{directory_to_save}. } \description{ The \code{download_sedac_population()} function accesses and downloads diff --git a/man/download_terraclimate.Rd b/man/download_terraclimate.Rd index 3a42bb57..1f1f35de 100644 --- a/man/download_terraclimate.Rd +++ b/man/download_terraclimate.Rd @@ -40,7 +40,7 @@ Remove (\code{TRUE}) or keep (\code{FALSE}) the text file containing download commands.} } \value{ -NULL; Yearly netCDF (.nc) files will be stored in a variable-specific +NULL; netCDF (.nc) files will be stored in a variable-specific folder within \code{directory_to_save}. } \description{ diff --git a/man/download_tri.Rd b/man/download_tri.Rd index 4f883175..56ea0435 100644 --- a/man/download_tri.Rd +++ b/man/download_tri.Rd @@ -32,7 +32,7 @@ will download all of the requested data files.} the text file containing download commands.} } \value{ -NULL; Yearly comma-separated value (CSV) files will be stored in +NULL; Comma-separated value (CSV) files will be stored in \code{directory_to_save}. } \description{ diff --git a/man/process_modis_swath.Rd b/man/process_modis_swath.Rd index 0ab6b99f..a04244c6 100644 --- a/man/process_modis_swath.Rd +++ b/man/process_modis_swath.Rd @@ -18,13 +18,17 @@ process_modis_swath( \item{date}{character(1). Date to query.} -\item{subdataset}{character. Subdatasets to process.} +\item{subdataset}{character. Subdatasets to process. +\strong{Unlike other preprocessing functions, this argument should specify +the exact subdataset name.} For example, when using MOD06_L2 product, +one may specify \code{c("Cloud_Fraction", "Cloud_Optical_Thickness")}, +etc. The subdataset names can be found in \code{terra::describe()} output.} \item{suffix}{character(1). Should be formatted \verb{:\{product\}:}, e.g., \verb{:mod06:}} \item{resolution}{numeric(1). Resolution of output raster. -Unit is degree.} +Unit is degree (decimal degree in WGS84).} \item{...}{For internal use.} } @@ -47,10 +51,10 @@ the full path to the hdf file. } \seealso{ \itemize{ -\item \code{\link{process_modis_warp}} +\item \code{\link[=process_modis_warp]{process_modis_warp()}}, \code{\link[stars:read_stars]{stars::read_stars()}}, \code{\link[stars:st_warp]{stars::st_warp()}} \item \href{https://gdal.org/drivers/raster/hdf4.html}{GDAL HDF4 driver documentation} -\item \code{\link[terra:describe]{terra::describe}}: to list the full subdataset list with \code{sds = TRUE} -\item \code{\link[terra:sprc]{terra::sprc}}, \code{\link[terra:rast]{terra::rast}} +\item \code{\link[terra:describe]{terra::describe()}}: to list the full subdataset list with \code{sds = TRUE} +\item \code{\link[terra:sprc]{terra::sprc()}}, \code{\link[terra:rast]{terra::rast()}} } } \author{ diff --git a/man/process_modis_warp.Rd b/man/process_modis_warp.Rd index b707504b..abf290d5 100644 --- a/man/process_modis_warp.Rd +++ b/man/process_modis_warp.Rd @@ -7,7 +7,7 @@ process_modis_warp( path = NULL, cellsize = 0.1, - threshold = cellsize * 2, + threshold = cellsize * 4, crs = 4326, ... ) diff --git a/man/process_sedac_groads.Rd b/man/process_sedac_groads.Rd index cefa9410..38c03e8d 100644 --- a/man/process_sedac_groads.Rd +++ b/man/process_sedac_groads.Rd @@ -20,7 +20,7 @@ returning a single \code{SpatVector} object. } \note{ U.S. context. The returned \code{SpatVector} object contains a -\verb{$time} column to represent the temporal range covered by the +\verb{$description} column to represent the temporal range covered by the dataset. For more information, see \url{https://sedac.ciesin.columbia.edu/data/set/groads-global-roads-open-access-v1/metadata}. } \author{ diff --git a/tests/testdata/nei/onroadnc17.csv b/tests/testdata/nei/onroadnc2017.csv similarity index 100% rename from tests/testdata/nei/onroadnc17.csv rename to tests/testdata/nei/onroadnc2017.csv diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 09590116..17697301 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -1,5 +1,5 @@ ## test for calculating covariates - +## 1. Koppen-Geiger #### testthat::test_that("calc_koppen_geiger works well", { withr::local_package("terra") withr::local_package("sf") @@ -50,6 +50,7 @@ testthat::test_that("calc_koppen_geiger works well", { testthat::expect_true("geometry" %in% names(kg_geom)) }) +## 2. Temporal Dummies #### testthat::test_that("calc_dummies works well", { site_faux <- @@ -57,7 +58,7 @@ testthat::test_that("calc_dummies works well", { site_id = "37031000188101", lon = -78.90, lat = 35.97, - time = "2022-01-01" + time = as.POSIXlt("2022-01-01") ) testthat::expect_no_error( @@ -107,6 +108,7 @@ testthat::test_that("calc_temporal_dummies errors.", { ) }) +## 3. Ecoregions #### testthat::test_that("calc_ecoregion works well", { withr::local_package("terra") withr::local_package("sf") @@ -157,7 +159,7 @@ testthat::test_that("calc_ecoregion works well", { testthat::expect_equal( sum(unlist(ecor_res[, dum_cn])), 2L ) - + testthat::expect_no_error( ecor_geom <- calc_ecoregion( from = erras, @@ -174,7 +176,7 @@ testthat::test_that("calc_ecoregion works well", { ) }) - +## 4. MODIS-VIIRS #### testthat::test_that("calc_modis works well.", { withr::local_package("sf") withr::local_package("terra") @@ -198,7 +200,7 @@ testthat::test_that("calc_modis works well.", { terra::vect( site_faux, geom = c("lon", "lat"), - keepgeom = TRUE, + keepgeom = FALSE, crs = "EPSG:4326") # case 1: standard mod11a1 @@ -366,7 +368,7 @@ testthat::test_that("calc_modis works well.", { ) ) site_faux2 <- site_faux - site_faux2[, 4] <- NULL + #site_faux2[, 4] <- NULL path_mcd19 <- testthat::test_path( @@ -429,11 +431,11 @@ testthat::test_that("calc_modis works well.", { ) ) testthat::expect_s3_class(flushed, "data.frame") - testthat::expect_true(any(unlist(flushed) == -99999)) + testthat::expect_true(unlist(flushed[, 3]) == -99999) }) - +## 5. NLCD #### testthat::test_that("Check calc_nlcd works", { withr::local_package("terra") withr::local_package("exactextractr") @@ -489,15 +491,10 @@ testthat::test_that("Check calc_nlcd works", { year = 1789), "NLCD data not available for this year." ) - # -- data_vect is a SpatVector - testthat::expect_message( - calc_nlcd(locs = sf::st_as_sf(eg_data), - from = nlcdras) - ) testthat::expect_error( calc_nlcd(locs = 12, - from = nlcdras), - "`locs` is not a `SpatVector`, `sf`, or `data.frame` object." + locs_id = "id", + from = nlcdras) ) testthat::expect_error( calc_nlcd(locs = eg_data, @@ -541,6 +538,7 @@ testthat::test_that("Check calc_nlcd works", { # -- initial names are still in the output data.frame testthat::expect_true(all(names(eg_data) %in% names(output))) # -- check the value of some of the points in the US + # the value has changed. What affected this behavior? testthat::expect_equal( output$LDU_TEFOR_0_03000[2], 0.7940682, tolerance = 1e-7 ) @@ -554,8 +552,8 @@ testthat::test_that("Check calc_nlcd works", { tolerance = 1e-7 ) # without geometry will have 11 columns - expect_equal( - ncol(output), 11 + testthat::expect_equal( + ncol(output), 15 ) output_geom <- calc_nlcd( locs = eg_data, @@ -565,15 +563,15 @@ testthat::test_that("Check calc_nlcd works", { geom = TRUE ) # with geometry will have 12 columns - expect_equal( - ncol(output_geom), 12 + testthat::expect_equal( + ncol(output_geom), 16 ) - expect_true( + testthat::expect_true( "geometry" %in% names(output_geom) ) }) - +## 6. NEI #### testthat::test_that("NEI calculation", { withr::local_package("terra") withr::local_package("sf") @@ -604,17 +602,19 @@ testthat::test_that("NEI calculation", { ) ) # inspecting calculated results - testthat::expect_s4_class(neiras, "SpatVector") + testthat::expect_true(inherits(neiras, "SpatVector")) + testthat::expect_true(nrow(neiras) == 3) # sf case testthat::expect_no_error( - process_nei( + neires <- process_nei( path = neipath, county = sf::st_as_sf(nc), year = 2017 ) ) - + testthat::expect_true(inherits(neires, "SpatVector")) + testthat::expect_true(nrow(neires) == 3) # error cases testthat::expect_error( @@ -630,7 +630,7 @@ testthat::test_that("NEI calculation", { # calc_nei ncp <- data.frame(lon = -78.8277, lat = 35.95013) ncp$site_id <- "3799900018810101" - ncp$time <- 2018 + ncp$time <- 2018L ncp <- terra::vect(ncp, keepgeom = TRUE, crs = "EPSG:4326") nc <- terra::project(nc, "EPSG:4326") @@ -653,7 +653,7 @@ testthat::test_that("NEI calculation", { }) - +## 7. TRI #### testthat::test_that("TRI calculation", { withr::local_package("terra") withr::local_package("sf") @@ -664,11 +664,11 @@ testthat::test_that("TRI calculation", { ncp <- data.frame(lon = -78.8277, lat = 35.95013) ncp$site_id <- "3799900018810101" - ncp$time <- 2018 + ncp$time <- 2018L ncpt <- terra::vect(ncp, geom = c("lon", "lat"), keepgeom = TRUE, crs = "EPSG:4326") - ncpt$time <- c(2018) + ncpt$time <- 2018L path_tri <- testthat::test_path("..", "testdata", "tri") testthat::expect_no_error( @@ -715,7 +715,7 @@ testthat::test_that("TRI calculation", { ) }) - +## 8. SEDC #### testthat::test_that("calc_sedc tests", { withr::local_package("terra") withr::local_package("sf") @@ -726,7 +726,7 @@ testthat::test_that("calc_sedc tests", { ncp <- data.frame(lon = -78.8277, lat = 35.95013) ncp$site_id <- "3799900018810101" - ncp$time <- 2018 + ncp$time <- 2018L ncpt <- terra::vect(ncp, geom = c("lon", "lat"), keepgeom = TRUE, crs = "EPSG:4326") @@ -775,6 +775,7 @@ testthat::test_that("calc_sedc tests", { }) +## 9. HMS #### testthat::test_that("calc_hms returns expected.", { withr::local_package("terra") densities <- c( @@ -836,10 +837,6 @@ testthat::test_that("calc_hms returns expected.", { expect_true( all(unique(hms_covariate[, 3]) %in% c(0, 1)) ) - # expect $time is class Date - expect_true( - "Date" %in% class(hms_covariate$time) - ) } } }) @@ -858,6 +855,9 @@ testthat::test_that("calc_hms returns expected with missing polygons.", { expect_true( is.function(calc_hms) ) + hms_dir <- testthat::test_path( + "..", "testdata", "hms" + ) for (d in seq_along(densities)) { density <- densities[d] for (r in seq_along(radii)) { @@ -865,11 +865,7 @@ testthat::test_that("calc_hms returns expected with missing polygons.", { process_hms( date = c("2022-06-10", "2022-06-13"), variable = density, - path = testthat::test_path( - "..", - "testdata", - "hms" - ) + path = hms_dir ) hms_covariate <- calc_hms( @@ -905,14 +901,11 @@ testthat::test_that("calc_hms returns expected with missing polygons.", { expect_true( all(unique(hms_covariate[, 3]) %in% c(0, 1)) ) - # expect $time is class Date - expect_true( - "Date" %in% class(hms_covariate$time) - ) } } }) +## 10. GMTED #### testthat::test_that("calc_gmted returns expected.", { withr::local_package("terra") statistics <- c( @@ -940,20 +933,7 @@ testthat::test_that("calc_gmted returns expected.", { testthat::test_path( "..", "testdata", - "gmted", - paste0( - process_gmted_codes( - statistic, - statistic = TRUE, - invert = FALSE - ), - process_gmted_codes( - resolution, - resolution = TRUE, - invert = FALSE - ), - "_grd" - ) + "gmted" ) ) gmted_covariate <- @@ -1084,6 +1064,7 @@ testthat::test_that("calc_narr returns expected.", { } }) +## 11. GEOS-CF #### testthat::test_that("calc_geos returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1148,6 +1129,7 @@ testthat::test_that("calc_geos returns as expected.", { } }) +## 12. SEDAC: Population #### testthat::test_that("calc_sedac_population returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1203,7 +1185,7 @@ testthat::test_that("calc_sedac_population returns as expected.", { } }) - +## 13. SEDAC: Global Roads #### testthat::test_that("groads calculation works", { withr::local_package("terra") withr::local_package("sf") @@ -1240,7 +1222,7 @@ testthat::test_that("groads calculation works", { # expect data.frame testthat::expect_s3_class(groads_res, "data.frame") - + # return with geometry testthat::expect_no_error( groads_geom <- calc_sedac_groads( @@ -1259,6 +1241,8 @@ testthat::test_that("groads calculation works", { ) }) + +## 14. MERRA2 #### testthat::test_that("calc_merra2 returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1343,6 +1327,7 @@ testthat::test_that("calc_merra2 returns as expected.", { } }) +## 15. GRIDMET #### testthat::test_that("calc_gridmet returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1395,11 +1380,12 @@ testthat::test_that("calc_gridmet returns as expected.", { ) # expect $time is class Date expect_true( - "Date" %in% class(gridmet_covariate$time) + "POSIXt" %in% class(gridmet_covariate$time) ) } }) +## 16. TerraClimate #### testthat::test_that("calc_terraclimate returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1457,6 +1443,7 @@ testthat::test_that("calc_terraclimate returns as expected.", { } }) +## 17. Lagged variables #### testthat::test_that("calc_lagged returns as expected.", { withr::local_package("terra") withr::local_package("data.table") @@ -1536,6 +1523,7 @@ testthat::test_that("calc_lagged returns as expected.", { }) +## 18. Wrapper #### testthat::test_that("calc_covariates wrapper works", { withr::local_package("rlang") From c0f78fc10b0c68b45fef15640a3cba87a9c53ea6 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 7 May 2024 17:36:15 -0400 Subject: [PATCH 25/65] MODIS functions bugfix - Revised swath function: all NaN layers get corner pixels to retain layers in multi-layer SpatRaster - Refactored calc_modis_par: try statement, error case treatment - calc_modis_daily: time field is assigned --- R/calculate_covariates.R | 104 +++++++++++++++++---------------------- R/process.R | 8 ++- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 9823e46c..848bc499 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -526,18 +526,15 @@ calc_modis_daily <- function( max_cells = 3e7, ... ) { - if (!any(methods::is(locs, "SpatVector"), - methods::is(locs, "sf"), - methods::is(locs, "sftime"))) { - stop("locs should be one of sf, sftime, or SpatVector.\n") - } if (!methods::is(locs, "SpatVector")) { - locs <- terra::vect(locs) + locs <- try(terra::vect(locs)) + if (inherits(locs, "try-error")) { + stop("locs should be a SpatVector or convertible object.") + } } if (!locs_id %in% names(locs)) { stop(sprintf("locs should include columns named %s.\n", - locs_id) - ) + locs_id)) } extract_with_buffer <- function( @@ -545,10 +542,11 @@ calc_modis_daily <- function( surf, radius, id, - time = "time", - func = "mean" + func = "mean", + maxcells = NULL ) { # generate buffers + if (radius == 0) radius <- 1e-6 # approximately 1 meter in degree bufs <- terra::buffer(points, width = radius, quadsegs = 180L) bufs <- terra::project(bufs, terra::crs(surf)) # extract raster values @@ -558,39 +556,33 @@ calc_modis_daily <- function( y = sf::st_as_sf(bufs), fun = func, force_df = TRUE, - append_cols = c(id, time), + append_cols = id, progress = FALSE, - max_cells_in_memory = max_cells + max_cells_in_memory = maxcells ) return(surf_at_bufs) } - ## NaN to zero - from[is.nan(from)] <- 0L + ## NaN to NA + from[is.nan(from)] <- NA # raster used to be vrt_today - if (any(grepl("00000", name_extracted))) { - locs_tr <- terra::project(locs, terra::crs(from)) - extracted <- terra::extract(x = from, y = locs_tr, ID = FALSE) - locs_blank <- as.data.frame(locs) - extracted <- cbind(locs_blank, extracted) - } else { - extracted <- - extract_with_buffer( - points = locs, - surf = from, - id = locs_id, - radius = radius, - func = fun_summary - ) - } + extracted <- + extract_with_buffer( + points = locs, + surf = from, + id = locs_id, + radius = radius, + func = fun_summary, + maxcells = max_cells + ) # cleaning names # assuming that extracted is a data.frame name_offset <- terra::nlyr(from) # multiple columns will get proper names name_range <- seq(ncol(extracted) - name_offset + 1, ncol(extracted), 1) colnames(extracted)[name_range] <- name_extracted - extracted$time <- as.POSIXlt(extracted$time) + extracted$time <- as.POSIXlt(date) calc_check_time(covar = extracted, POSIXt = TRUE) return(extracted) } @@ -716,8 +708,9 @@ process_modis_swath, or process_bluemarble.") export_list <- c() package_list <- - c("sf", "terra", "exactextractr", "foreach", "data.table", "stars", - "dplyr", "parallelly", "doParallel", "rlang", "amadeus") + c("sf", "terra", "exactextractr", "data.table", "stars", + "dplyr", "parallelly", "rlang", "amadeus", "future", + "future.apply") if (!is.null(export_list_add)) { export_list <- append(export_list, export_list_add) } @@ -730,16 +723,15 @@ process_modis_swath, or process_bluemarble.") if (nthreads == 1) { future::plan(future::sequential) } else { - future::plan(future::multisession, workers = nthreads) + future::plan(future::multicore, workers = nthreads) } - # future::future(future::multicore, workers = nthreads) idx_date_available <- seq_along(dates_available) list_date_available <- split(idx_date_available, idx_date_available) calc_results <- future.apply::future_lapply( list_date_available, - FUN = function (datei) { + FUN = function(datei) { options(sf_use_s2 = FALSE) # nolint start day_to_pick <- dates_available[datei] @@ -747,29 +739,28 @@ process_modis_swath, or process_bluemarble.") day_to_pick <- as.Date(day_to_pick, format = "%Y%j") radiusindex <- seq_along(radius) - radiuslist <- split(radiusindex, radiusindex) + radiusindexlist <- split(radiusindex, radiusindex) - hdf_args <- append(hdf_args, values = list(date = day_to_pick)) - hdf_args <- append(hdf_args, values = list(path = hdf_args$from)) + hdf_args <- c(hdf_args, list(date = day_to_pick)) + hdf_args <- c(hdf_args, list(path = hdf_args$from)) # unified interface with rlang::inject vrt_today <- rlang::inject(preprocess(!!!hdf_args)) if (sum(terra::nlyr(vrt_today)) != length(name_covariates)) { - warning("The number of layers in the input raster do not match + message("The number of layers in the input raster do not match the length of name_covariates.\n") } res0 <- - lapply(radiuslist, + lapply(radiusindexlist, function(k) { name_radius <- sprintf("%s%05d", name_covariates, radius[k]) - - tryCatch({ - extracted <- + extracted <- + try( calc_modis_daily( locs = locs_input, from = vrt_today, @@ -780,21 +771,20 @@ process_modis_swath, or process_bluemarble.") radius = radius[k], max_cells = max_cells ) - return(extracted) - }, error = function(e) { - name_radius <- - sprintf("%s%05d", - name_covariates, - radius[k]) - error_df <- sf::st_drop_geometry(locs_input) + ) + if (inherits(extracted, "try-error")) { # coerce to avoid errors - error_df <- as.data.frame(error_df) - error_df <- error_df[, c(locs_id, "time")] - error_df[[name_radius]] <- -99999 - attr(error_df, "error_message") <- e - return(error_df) + error_df <- data.frame( + matrix(-99999, + ncol = length(name_radius) + 1, + nrow = nrow(locs_input)) + ) + error_df <- stats::setNames(error_df, c(locs_id, name_radius)) + error_df[[locs_id]] <- unlist(locs_input[[locs_id]]) + error_df$time <- day_to_pick + extracted <- error_df } - ) + return(extracted) } ) res <- @@ -807,8 +797,6 @@ process_modis_swath, or process_bluemarble.") return(res) }, - future.packages = package_list, - future.globals = TRUE, future.seed = TRUE ) calc_results <- do.call(dplyr::bind_rows, calc_results) diff --git a/R/process.R b/R/process.R index 2afa7781..232cd33d 100644 --- a/R/process.R +++ b/R/process.R @@ -600,10 +600,16 @@ process_modis_swath <- ) # initialize values with NA alt[] <- NA + alt_dim <- dim(alt) + alt[1, 1] <- 0 + alt[1, alt_dim[2]] <- 0 + alt[alt_dim[1], 1] <- 0 + alt[alt_dim[1], alt_dim[2]] <- 0 + terra::crs(alt) <- "EPSG:4326" if (is.null(mod06_element) || length(mod06_element) == 0) { message("All layers are NA or NaN.") - mod06_element_mosaic <- alt + mod06_element_mosaic <- terra::deepcopy(alt) } else { # mosaick the warped SpatRasters into one mod06_element_mosaic <- From 3e5f4eecc0e1f938c3945cad3ba7b7f625081a3b Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 8 May 2024 16:33:09 -0400 Subject: [PATCH 26/65] calc_nlcd bug fix - us_main in calc_nlcd is removed --- R/calculate_covariates.R | 27 +++++++++------------------ R/process_auxiliary.R | 14 +++++++------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 848bc499..24a5e146 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -244,7 +244,7 @@ calc_koppen_geiger <- #' @param radius numeric (non-negative) giving the #' radius of buffer around points #' @param max_cells integer(1). Maximum number of cells to be read at once. -#' Higher values will expedite processing, but will increase memory usage. +#' Higher values may expedite processing, but will increase memory usage. #' Maximum possible value is `2^31 - 1`. #' See [`exactextractr::exact_extract`] for details. #' @param geom logical(1). Should the geometry of `locs` be returned in the @@ -254,6 +254,8 @@ calc_koppen_geiger <- #' coordinate reference system of the `$geometry` is the coordinate #' reference system of `from`. #' @param ... Placeholders. +#' @notes NLCD is available in U.S. only. Users should be cautious +#' the spatial extent of the data. #' @seealso [`process_nlcd`] #' @returns a data.frame object #' @importFrom utils read.csv @@ -275,7 +277,7 @@ calc_nlcd <- function(from, locs, locs_id = "site_id", radius = 1000, - max_cells = 1e8, + max_cells = 5e7, geom = FALSE, ...) { # check inputs @@ -303,19 +305,8 @@ calc_nlcd <- function(from, year <- try(as.integer(terra::metags(from, name = "year"))) # select points within mainland US and reproject on nlcd crs if necessary - us_main <- - terra::ext(c(xmin = -127, xmax = -65, ymin = 24, ymax = 51)) |> - terra::vect() |> - terra::set.crs("EPSG:4326") |> - terra::project(y = terra::crs(locs_vector)) - data_vect_b <- locs_vector |> - terra::intersect(x = us_main) |> - terra::project(y = terra::crs(from)) - - # subset locs_df to those in us extent - locs_dfs <- locs_df[ - unlist(locs_df[[locs_id]]) %in% unlist(data_vect_b[[locs_id]]), - ] + data_vect_b <- + terra::project(locs_vector, y = terra::crs(from)) # create circle buffers with buf_radius bufs_pol <- terra::buffer(data_vect_b, width = radius) |> @@ -356,7 +347,7 @@ calc_nlcd <- function(from, ) names(nlcd_at_bufs) <- new_names # merge locs_df with nlcd class fractions - new_data_vect <- cbind(locs_dfs, as.integer(year), nlcd_at_bufs) + new_data_vect <- cbind(locs_df, as.integer(year), nlcd_at_bufs) if (geom) { names(new_data_vect)[1:3] <- c(locs_id, "geometry", "time") } else { @@ -1329,8 +1320,8 @@ calc_hms <- function( } #### coerce binary to integer sites_extracted[, 3] <- as.integer(sites_extracted[, 3]) - #### date to POSIXlt - sites_extracted$time <- as.POSIXlt(sites_extracted$time) + #### date to POSIXct + sites_extracted$time <- as.POSIXct(sites_extracted$time) #### order by date sites_extracted_ordered <- as.data.frame( sites_extracted[order(sites_extracted$time), ] diff --git a/R/process_auxiliary.R b/R/process_auxiliary.R index 38551e12..851dd70e 100644 --- a/R/process_auxiliary.R +++ b/R/process_auxiliary.R @@ -338,10 +338,10 @@ process_locs_radius <- #' @export process_locs_vector <- function( - locs, - crs, - radius - ) { + locs, + crs, + radius + ) { #### detect SpatVector if (methods::is(locs, "SpatVector")) { cat( @@ -349,14 +349,14 @@ process_locs_vector <- "Detected `SpatVector` (", terra::geomtype(locs), ") extraction locations...\n" - ) + ) ) sites_v <- locs - #### detect sf object + #### detect sf object } else if (methods::is(locs, "sf")) { cat("Detected `sf` extraction locations...\n") sites_v <- terra::vect(locs) - ### detect data.frame object + ### detect data.frame object } else if (methods::is(locs, "data.frame")) { cat("Detected `data.frame` extraction locations...\n") #### columns From 5fb0b6fef0e6c0c4210e78760fddbc70b35ded4d Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 8 May 2024 16:34:17 -0400 Subject: [PATCH 27/65] calc_nlcd roxygen doc fix --- R/calculate_covariates.R | 2 +- man/calc_nlcd.Rd | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 24a5e146..609acc76 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -254,7 +254,7 @@ calc_koppen_geiger <- #' coordinate reference system of the `$geometry` is the coordinate #' reference system of `from`. #' @param ... Placeholders. -#' @notes NLCD is available in U.S. only. Users should be cautious +#' @note NLCD is available in U.S. only. Users should be cautious #' the spatial extent of the data. #' @seealso [`process_nlcd`] #' @returns a data.frame object diff --git a/man/calc_nlcd.Rd b/man/calc_nlcd.Rd index 37fa30a5..a8819959 100644 --- a/man/calc_nlcd.Rd +++ b/man/calc_nlcd.Rd @@ -9,7 +9,7 @@ calc_nlcd( locs, locs_id = "site_id", radius = 1000, - max_cells = 1e+08, + max_cells = 5e+07, geom = FALSE, ... ) @@ -25,7 +25,7 @@ calc_nlcd( radius of buffer around points} \item{max_cells}{integer(1). Maximum number of cells to be read at once. -Higher values will expedite processing, but will increase memory usage. +Higher values may expedite processing, but will increase memory usage. Maximum possible value is \code{2^31 - 1}. See \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract}} for details.} @@ -46,6 +46,10 @@ Compute ratio of land cover class in circle buffers around points. Returns a \code{data.frame} object containing \code{locs_id}, longitude, latitude, time (year), and computed ratio for each land cover class. } +\note{ +NLCD is available in U.S. only. Users should be cautious +the spatial extent of the data. +} \seealso{ \code{\link{process_nlcd}} } From 7b75408f31879fd23f75dd79984f15e051686b59 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 15 May 2024 17:23:13 -0400 Subject: [PATCH 28/65] calc_modis_par fix - Logic for available date filtering by number of files is fixed --- R/calculate_covariates.R | 43 ++++++++++++++++++++++++++++++++++++---- man/calc_modis_par.Rd | 13 ++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 609acc76..30f203e2 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -318,7 +318,6 @@ calc_nlcd <- function(from, from, bufs_pol, fun = "frac", - stack_apply = TRUE, force_df = TRUE, progress = FALSE, max_cells_in_memory = max_cells @@ -547,6 +546,7 @@ calc_modis_daily <- function( y = sf::st_as_sf(bufs), fun = func, force_df = TRUE, + stack_apply = TRUE, append_cols = id, progress = FALSE, max_cells_in_memory = maxcells @@ -575,6 +575,7 @@ calc_modis_daily <- function( colnames(extracted)[name_range] <- name_extracted extracted$time <- as.POSIXlt(date) calc_check_time(covar = extracted, POSIXt = TRUE) + gc() return(extracted) } @@ -636,6 +637,15 @@ calc_modis_daily <- function( #' e.g., `c("Cloud_Fraction_Day", "Cloud_Fraction_Night")` #' * `process_bluemarble()`: Subdataset number. #' e.g., for VNP46A2 product, 3L. +#' Dates with less than 80 percent of the expected number of tiles, +#' which are determined by the mode of the number of tiles, are removed. +#' Users will be informed of the dates with insufficient tiles. +#' The result data.frame will have an attribute with the dates with +#' insufficient tiles. +#' @returns A data.frame with an attribute: +#' * `attr(., "dates_dropped")`: Dates with insufficient tiles. +#' Note that the dates mean the dates with insufficient tiles, +#' not the dates without available tiles. #' @seealso See details for setting parallelization: #' * [`future::plan()`] #' * [`future.apply::future_lapply()`] @@ -686,11 +696,36 @@ process_modis_swath, or process_bluemarble.") # nolint start hdf_args <- c(as.list(environment()), list(...)) # nolint end - dates_available <- + dates_available_m <- regmatches(from, regexpr("A20\\d{2,2}[0-3]\\d{2,2}", from)) - dates_available <- unique(dates_available) + dates_available <- sort(unique(dates_available_m)) dates_available <- sub("A", "", dates_available) + # When multiple dates are concerned, + # the number of tiles are expected to be the same. + # Exceptions could exist, so here the number of tiles are checked. + summary_available <- table(dates_available_m) + summary_available_mode <- + sort(table(summary_available), decreasing = TRUE)[1] + summary_available_mode <- as.numeric(names(summary_available_mode)) + summary_available_insuf <- + which(summary_available < floor(summary_available_mode * 0.8)) + if (length(summary_available_insuf) > 0) { + dates_insuf <- + as.Date(dates_available[summary_available_insuf], "%Y%j") + message( + paste0( + "The number of tiles on the following dates are insufficient: ", + paste(dates_insuf, collapse = ", "), + ".\n" + ) + ) + # finally it removes the dates with insufficient tiles + dates_available <- dates_available[-summary_available_insuf] + } else { + dates_insuf <- NA + } + locs_input <- try(sf::st_as_sf(locs), silent = TRUE) if (inherits(locs_input, "try-error")) { stop("locs cannot be convertible to sf. @@ -791,7 +826,7 @@ process_modis_swath, or process_bluemarble.") future.seed = TRUE ) calc_results <- do.call(dplyr::bind_rows, calc_results) - + attr(calc_results, "dates_dropped") <- dates_insuf Sys.sleep(1L) return(calc_results) } diff --git a/man/calc_modis_par.Rd b/man/calc_modis_par.Rd index bd2cfb3a..1cf02d19 100644 --- a/man/calc_modis_par.Rd +++ b/man/calc_modis_par.Rd @@ -64,6 +64,14 @@ See \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract}} for \item{...}{Arguments passed to \code{preprocess}.} } +\value{ +A data.frame with an attribute: +\itemize{ +\item \code{attr(., "dates_dropped")}: Dates with insufficient tiles. +Note that the dates mean the dates with insufficient tiles, +not the dates without available tiles. +} +} \description{ \code{calc_modis_par} essentially runs \code{\link{calc_modis_daily}} function in each thread (subprocess). Based on daily resolution, each day's workload @@ -94,6 +102,11 @@ e.g., \code{"^LST_"} e.g., \code{c("Cloud_Fraction_Day", "Cloud_Fraction_Night")} \item \code{process_bluemarble()}: Subdataset number. e.g., for VNP46A2 product, 3L. +Dates with less than 80 percent of the expected number of tiles, +which are determined by the mode of the number of tiles, are removed. +Users will be informed of the dates with insufficient tiles. +The result data.frame will have an attribute with the dates with +insufficient tiles. } } \seealso{ From a1ab9d5441fefbeb4cba5e6298ef4f093f475814 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 21 May 2024 15:52:02 -0400 Subject: [PATCH 29/65] Small fix - Tests were revisited - calc_setcolumns logic fix: only calculated covariates will capitalized --- R/calculate_covariates_auxiliary.R | 3 +- tests/testthat/test-calculate_covariates.R | 18 +++---- tests/testthat/test-process.R | 58 ++++++++++++---------- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 2c211695..3205d965 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -115,11 +115,10 @@ calc_setcolumns <- function( side = "left" ) ) - names_return[cov_index[c]] <- name_new + names_return[cov_index[c]] <- toupper(name_new) } #### check for unique names stopifnot(length(names_return) == length(unique(names_return))) - colnames(from) <- toupper(names_return) return(from) } diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 17697301..504e5744 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -185,8 +185,7 @@ testthat::test_that("calc_modis works well.", { withr::local_package("foreach") withr::local_package("doParallel") withr::local_options( - list(sf_use_s2 = FALSE, - foreachDoparLocal = TRUE) + list(sf_use_s2 = FALSE) ) site_faux <- @@ -431,7 +430,7 @@ testthat::test_that("calc_modis works well.", { ) ) testthat::expect_s3_class(flushed, "data.frame") - testthat::expect_true(unlist(flushed[, 3]) == -99999) + testthat::expect_true(unlist(flushed[, 2]) == -99999) }) @@ -533,21 +532,21 @@ testthat::test_that("Check calc_nlcd works", { ) # -- returns a data.frame testthat::expect_equal(class(output)[1], "data.frame") - # -- out-of-mainland-US points removed (France and Alaska) - testthat::expect_equal(nrow(output), 2) + # nrow(output) == nrow(input) + testthat::expect_equal(nrow(output), 4) # -- initial names are still in the output data.frame testthat::expect_true(all(names(eg_data) %in% names(output))) # -- check the value of some of the points in the US # the value has changed. What affected this behavior? testthat::expect_equal( - output$LDU_TEFOR_0_03000[2], 0.7940682, tolerance = 1e-7 + output$LDU_TEFOR_0_03000[1], 0.8119843, tolerance = 1e-7 ) testthat::expect_equal( - output$LDU_TSHRB_0_03000[1], 0.9987249, tolerance = 1e-7 + output$LDU_TSHRB_0_03000[2], 0.9630467, tolerance = 1e-7 ) # -- class fraction rows should sum to 1 testthat::expect_equal( - rowSums(as.data.frame(output[, 3:(ncol(output))])), + unname(rowSums(output[1:2, 3:(ncol(output))])), rep(1, 2), tolerance = 1e-7 ) @@ -990,6 +989,7 @@ testthat::test_that("calc_gmted returns expected.", { ) }) +## 11. NARR #### testthat::test_that("calc_narr returns expected.", { withr::local_package("terra") variables <- c( @@ -1058,7 +1058,7 @@ testthat::test_that("calc_narr returns expected.", { } # expect $time is class Date expect_true( - "Date" %in% class(narr_covariate$time) + "POSIXct" %in% class(narr_covariate$time) ) } } diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index e2dbb1b2..61ddcdea 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -378,26 +378,30 @@ testthat::test_that("Other MODIS function errors", { testthat::expect_no_error( suppressWarnings( process_modis_swath( - path = path_mod06e, + path = path_mod06, + subdataset = "Cloud_Fraction_Night", date = "2021-08-15" ) ) ) testthat::expect_error( process_modis_swath( - path = path_mod06e, + path = path_mod06, + subdataset = "Cloud_Fraction_Night", date = "2021~08~15" ) ) testthat::expect_error( process_modis_swath( - path = path_mod06e, + path = path_mod06, + subdataset = "Cloud_Fraction_Night", date = "2021-13-15" ) ) testthat::expect_error( process_modis_swath( - path = path_mod06e, + path = path_mod06, + subdataset = "Cloud_Fraction_Night", date = "2021-12-45" ) ) @@ -483,7 +487,7 @@ testthat::test_that("process_nei tests", { path_cnty$GEOID <- path_cnty$FIPS testthat::expect_no_error( - neinc <- process_nei(path = path_nei, year = 2020, county = path_cnty) + neinc <- process_nei(path = path_nei, year = 2017, county = path_cnty) ) testthat::expect_s4_class(neinc, "SpatVector") @@ -608,7 +612,7 @@ testthat::test_that("process_hms returns expected.", { "Heavy" ) # expect function - expect_true( + testthat::expect_true( is.function(process_hms) ) for (d in seq_along(densities)) { @@ -623,30 +627,30 @@ testthat::test_that("process_hms returns expected.", { ) ) # expect output is a SpatVector or character - expect_true( + testthat::expect_true( class(hms)[1] %in% c("SpatVector", "character") ) if (class(hms)[1] == "SpatVector") { # expect non-null coordinate reference system - expect_false( + testthat::expect_false( is.null(terra::crs(hms)) ) # expect two columns - expect_true( + testthat::expect_true( ncol(hms) == 2 ) # expect density and date column - expect_true( + testthat::expect_true( all(c("Density", "Date") %in% names(hms)) ) } else if (class(hms)[1] == "character") { # expect first is density type - expect_true( + testthat::expect_true( hms[1] %in% c("Light", "Medium", "Heavy") ) - # expect other elements are 8 character dates - expect_true( - all(nchar(hms[2:length(hms)]) == 8) + # expect other elements are 10 character dates + testthat::expect_true( + all(nchar(hms[2:length(hms)]) == 10) ) } } @@ -1459,7 +1463,7 @@ testthat::test_that("gridmet and terraclimate auxiliary functions.", { # test PRISM #### -test_that("process_prism returns a SpatRaster object with correct metadata", { +testthat::test_that("process_prism returns a SpatRaster object with correct metadata", { # Set up test data withr::local_package("terra") path <- testthat::test_path( @@ -1472,16 +1476,16 @@ test_that("process_prism returns a SpatRaster object with correct metadata", { time <- "0228" # Call the function - expect_no_error(result <- process_prism(path, element, time)) - expect_no_error(result2 <- process_prism(path_dir, element, time)) + testthat::expect_no_error(result <- process_prism(path, element, time)) + testthat::expect_no_error(result2 <- process_prism(path_dir, element, time)) # Check the return type - expect_true(inherits(result, "SpatRaster")) - expect_true(inherits(result2, "SpatRaster")) + testthat::expect_true(inherits(result, "SpatRaster")) + testthat::expect_true(inherits(result2, "SpatRaster")) # Check the metadata - expect_equal(unname(terra::metags(result)["time"]), time) - expect_equal(unname(terra::metags(result)["element"]), element) + testthat::expect_equal(unname(terra::metags(result)["time"]), time) + testthat::expect_equal(unname(terra::metags(result)["element"]), element) # Set up test data path_bad <- "/path/to/nonexistent/folder" @@ -1489,10 +1493,14 @@ test_that("process_prism returns a SpatRaster object with correct metadata", { time_bad <- "invalid_time" # Call the function and expect an error - expect_error(process_prism(NULL, element, time)) - expect_error(process_prism(path_bad, element, time)) - expect_error(process_prism(path_dir, element_bad, time)) - expect_error(process_prism(path_dir, element, time_bad)) + testthat::expect_error(process_prism(NULL, element, time)) + testthat::expect_error( + testthat::expect_warning( + process_prism(path_bad, element, time) + ) + ) + testthat::expect_error(process_prism(path_dir, element_bad, time)) + testthat::expect_error(process_prism(path_dir, element, time_bad)) }) From 1965247f03a0a0a9f60aef74d50e732c37d7987d Mon Sep 17 00:00:00 2001 From: Insang Song Date: Fri, 24 May 2024 13:51:46 -0400 Subject: [PATCH 30/65] Hotfix: process_aqs - Sample durations 1-HOUR and 24-HOUR BLK AVG overlap; prioritizing 24-HOUR (due to QC) --- R/process.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/process.R b/R/process.R index 232cd33d..0bf411b2 100644 --- a/R/process.R +++ b/R/process.R @@ -1030,6 +1030,7 @@ process_aqs <- # select relevant fields only sites <- sites |> dplyr::as_tibble() |> + dplyr::filter(startsWith(Sample.Duration, "24")) |> dplyr::group_by(site_id) |> dplyr::filter(POC == min(POC)) |> dplyr::mutate(time = Date.Local) |> From 2701adb9ffd6ab8d5fb5d787170b4f94060da86c Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 28 May 2024 16:44:24 -0400 Subject: [PATCH 31/65] process_aqs --- R/process.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/process.R b/R/process.R index 0bf411b2..c47a1f03 100644 --- a/R/process.R +++ b/R/process.R @@ -1018,10 +1018,10 @@ process_aqs <- ## get unique sites sites$site_id <- sprintf("%02d%03d%04d%05d", - sites$State.Code, - sites$County.Code, - sites$Site.Num, - sites$Parameter.Code) + as.integer(sites$State.Code), + as.integer(sites$County.Code), + as.integer(sites$Site.Num), + as.integer(sites$Parameter.Code)) site_id <- NULL Datum <- NULL From 337cd75f636027360a95899d88b0e422b29bb53e Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 29 May 2024 15:06:18 -0400 Subject: [PATCH 32/65] calc_ecoregions bug fix - Introduced by 30af056 - Fixed the column reference based on grep, from the hard-coded integers --- R/calculate_covariates.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 30f203e2..a3bb6c22 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -418,14 +418,14 @@ calc_ecoregion <- # Generate field names from extracted ecoregion keys # TODO: if we keep all-zero fields, the initial reference # should be the ecoregion polygon, not the extracted data - key2_sorted <- unlist(extracted[, 3]) + key2_sorted <- unlist(extracted[, grep("L2", names(extracted))]) key2_num <- regmatches(key2_sorted, regexpr("\\d{1,2}\\.[1-9]", key2_sorted)) key2_num <- as.integer(10 * as.numeric(key2_num)) key2_num <- sprintf("DUM_E2%03d_0_00000", key2_num) key2_num_unique <- sort(unique(key2_num)) - key3_sorted <- unlist(extracted[, 2]) + key3_sorted <- unlist(extracted[, grep("L3", names(extracted))]) key3_num <- regmatches(key3_sorted, regexpr("\\d{1,3}", key3_sorted)) key3_num <- as.integer(as.numeric(key3_num)) From ee507124671a440ef04a27ed6aaa8835a0cac591 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 13:51:58 -0400 Subject: [PATCH 33/65] ecoregion function refactoring - process_ecoregion: Edit the geometry of Tukey's Bridge in Portland, ME - calc_ecoregion: removed exception processing, direct spatial join --- NAMESPACE | 6 +++++- R/calculate_covariates.R | 24 +++++++----------------- R/process.R | 18 +++++++++++++++++- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 737ea86a..5b03c707 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -158,8 +158,13 @@ importFrom(rstac,read_collections) importFrom(rstac,read_items) importFrom(rstac,read_stac) importFrom(sf,st_as_sf) +importFrom(sf,st_as_sfc) +importFrom(sf,st_crs) importFrom(sf,st_drop_geometry) importFrom(sf,st_geometry) +importFrom(sf,st_intersects) +importFrom(sf,st_read) +importFrom(sf,st_transform) importFrom(sf,st_union) importFrom(stars,read_stars) importFrom(stars,st_mosaic) @@ -192,7 +197,6 @@ importFrom(terra,perim) importFrom(terra,project) importFrom(terra,rast) importFrom(terra,set.crs) -importFrom(terra,snap) importFrom(terra,sprc) importFrom(terra,subset) importFrom(terra,tapp) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index a3bb6c22..a84aa727 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -380,13 +380,8 @@ calc_nlcd <- function(from, #' - `attr(., "ecoregion2_code")`: Ecoregion lv.2 code and key #' - `attr(., "ecoregion3_code")`: Ecoregion lv.3 code and key #' @author Insang Song -#' @importFrom methods is -#' @importFrom terra vect -#' @importFrom terra project -#' @importFrom terra intersect -#' @importFrom terra snap #' @importFrom terra extract -#' @importFrom terra crs +#' @importFrom data.table year #' @export calc_ecoregion <- function( @@ -404,28 +399,23 @@ calc_ecoregion <- radius = 0, geom = geom ) - locs <- locs_prepared[[1]] + # both objects will preserve the row order + locsp <- locs_prepared[[1]] locs_df <- locs_prepared[[2]] - locs_in <- terra::intersect(locs, from) - locs_out <- - locs[!unlist(locs[[locs_id]]) %in% unlist(locs_in[[locs_id]]), ] - - locs_snapped <- terra::snap(locs_out, from, tolerance = 50) - locs_fixed <- rbind(locs_in, locs_snapped) - extracted <- terra::extract(from, locs_fixed) - + extracted <- terra::intersect(locsp, from) + return(extracted) # Generate field names from extracted ecoregion keys # TODO: if we keep all-zero fields, the initial reference # should be the ecoregion polygon, not the extracted data - key2_sorted <- unlist(extracted[, grep("L2", names(extracted))]) + key2_sorted <- unlist(extracted[[grep("L2", names(extracted))]]) key2_num <- regmatches(key2_sorted, regexpr("\\d{1,2}\\.[1-9]", key2_sorted)) key2_num <- as.integer(10 * as.numeric(key2_num)) key2_num <- sprintf("DUM_E2%03d_0_00000", key2_num) key2_num_unique <- sort(unique(key2_num)) - key3_sorted <- unlist(extracted[, grep("L3", names(extracted))]) + key3_sorted <- unlist(extracted[[grep("L3", names(extracted))]]) key3_num <- regmatches(key3_sorted, regexpr("\\d{1,3}", key3_sorted)) key3_num <- as.integer(as.numeric(key3_num)) diff --git a/R/process.R b/R/process.R index c47a1f03..a32f19c8 100644 --- a/R/process.R +++ b/R/process.R @@ -732,17 +732,33 @@ process_nlcd <- #' @author Insang Song #' @returns a `SpatVector` object #' @importFrom terra vect +#' @importFrom sf st_read st_crs st_as_sfc st_transform st_intersects st_union +#' @importFrom data.table year #' @export process_ecoregion <- function( path = NULL, ... ) { - ecoreg <- terra::vect(path) + ecoreg <- sf::st_read(path) + # fix Tukey's bridge in Portland, ME + # nolint start + poly_tukey <- + "POLYGON ((-70.258 43.68, -70.2555 43.68, -70.255 43.6733, -70.2576 43.6732, -70.258 43.68))" + poly_tukey <- sf::st_as_sfc(poly_tukey, crs = "EPSG:4326") + poly_tukey <- sf::st_transform(poly_tukey, sf::st_crs(ecoreg)) + + # nolint end ecoreg <- ecoreg[, grepl("^(L2_KEY|L3_KEY)", names(ecoreg))] + ecoreg_edit_idx <- sf::st_intersects(ecoreg, poly_tukey, sparse = FALSE) + ecoreg_edit_idx <- vapply(ecoreg_edit_idx, function(x) any(x), logical(1)) + ecoreg_else <- ecoreg[!ecoreg_edit_idx, ] + ecoreg_edit <- sf::st_union(ecoreg[ecoreg_edit_idx, ], poly_tukey) + ecoreg <- rbind(ecoreg_else, ecoreg_edit) ecoreg$time <- paste0( "1997 - ", data.table::year(Sys.time()) ) + ecoreg <- terra::vect(ecoreg) return(ecoreg) } From 21658abcd4c1ba8cb6d477571d9d1a6b73e45ced Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 16:26:42 -0400 Subject: [PATCH 34/65] nlcd functions refactoring - Missing geometry is fixed in process_nlcd - calc_nlcd does not take exceptions into account - Missing class in inst/extdata/nlcd_classes.csv is added --- NAMESPACE | 1 - R/calculate_covariates.R | 101 +++++++++++++-------- R/process.R | 13 ++- inst/extdata/nlcd_classes.csv | 31 ++++--- man/calc_nlcd.Rd | 18 +++- man/process_nlcd.Rd | 5 +- tests/testthat/test-calculate_covariates.R | 32 +++++-- 7 files changed, 133 insertions(+), 68 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 5b03c707..41664e17 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -178,7 +178,6 @@ importFrom(terra,buffer) importFrom(terra,coltab) importFrom(terra,crop) importFrom(terra,crs) -importFrom(terra,deepcopy) importFrom(terra,describe) importFrom(terra,distance) importFrom(terra,expanse) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index a84aa727..5c0aea4c 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -241,11 +241,15 @@ calc_koppen_geiger <- #' @param from SpatRaster(1). Output of \code{process_nlcd()}. #' @param locs terra::SpatVector of points geometry #' @param locs_id character(1). Unique identifier of locations +#' @param mode character(1). One of `"exact"` +#' (using [`exactextractr::exact_extract()`]) +#' or `"terra"` (using [`terra::freq()`]). #' @param radius numeric (non-negative) giving the #' radius of buffer around points #' @param max_cells integer(1). Maximum number of cells to be read at once. #' Higher values may expedite processing, but will increase memory usage. -#' Maximum possible value is `2^31 - 1`. +#' Maximum possible value is `2^31 - 1`. Only valid when +#' `mode = "exact"`. #' See [`exactextractr::exact_extract`] for details. #' @param geom logical(1). Should the geometry of `locs` be returned in the #' `data.frame`? Default is `FALSE`. If `geom = TRUE` and `locs` contain @@ -254,8 +258,13 @@ calc_koppen_geiger <- #' coordinate reference system of the `$geometry` is the coordinate #' reference system of `from`. #' @param ... Placeholders. -#' @note NLCD is available in U.S. only. Users should be cautious -#' the spatial extent of the data. +#' @note NLCD is available in U.S. only. Users should be aware of +#' the spatial extent of the data. The results are different depending +#' on `mode` argument. The `"terra"` mode is less memory intensive +#' but less accurate because it counts the number of cells +#' intersecting with the buffer. The `"exact"` may be more accurate +#' but uses more memory as it will account for the partial overlap +#' with the buffer. #' @seealso [`process_nlcd`] #' @returns a data.frame object #' @importFrom utils read.csv @@ -264,7 +273,6 @@ calc_koppen_geiger <- #' @importFrom terra project #' @importFrom terra vect #' @importFrom terra crs -#' @importFrom terra deepcopy #' @importFrom terra set.crs #' @importFrom terra buffer #' @importFrom sf st_union @@ -276,11 +284,14 @@ calc_koppen_geiger <- calc_nlcd <- function(from, locs, locs_id = "site_id", + mode = c("exact", "terra"), radius = 1000, max_cells = 5e7, geom = FALSE, + nthreads = 1L, ...) { # check inputs + mode <- match.arg(mode) if (!is.numeric(radius)) { stop("radius is not a numeric.") } @@ -307,43 +318,61 @@ calc_nlcd <- function(from, # select points within mainland US and reproject on nlcd crs if necessary data_vect_b <- terra::project(locs_vector, y = terra::crs(from)) - # create circle buffers with buf_radius - bufs_pol <- terra::buffer(data_vect_b, width = radius) |> - sf::st_as_sf() |> - sf::st_geometry() - # ratio of each nlcd class per buffer - nlcd_at_bufs <- - exactextractr::exact_extract( - from, - bufs_pol, - fun = "frac", - force_df = TRUE, - progress = FALSE, - max_cells_in_memory = max_cells - ) - - # select only the columns of interest + bufs_pol <- terra::buffer(data_vect_b, width = radius) cfpath <- system.file("extdata", "nlcd_classes.csv", package = "amadeus") nlcd_classes <- utils::read.csv(cfpath) - nlcd_at_bufs <- - nlcd_at_bufs[ - sort(names(nlcd_at_bufs)[ - grepl(paste0("frac_(", paste(nlcd_classes$value, collapse = "|"), ")"), - names(nlcd_at_bufs)) - ]) - ] + + if (mode == "fast") { + # fast mode + class_query <- "names" + # extract land cover class in each buffer + nlcd_at_bufs <- + terra::freq( + from, + zones = bufs_pol, + wide = TRUE + ) + nlcd_at_bufs <- nlcd_at_bufs[, -seq(1, 2)] + nlcd_cellcnt <- nlcd_at_bufs[, seq(1, ncol(nlcd_at_bufs), 1)] + nlcd_cellcnt <- nlcd_cellcnt / rowSums(nlcd_cellcnt) + nlcd_at_bufs[, seq(1, ncol(nlcd_at_bufs), 1)] <- nlcd_cellcnt + } else { + class_query <- "value" + # ratio of each nlcd class per buffer + bufs_pol <- bufs_pol |> + sf::st_as_sf() |> + sf::st_geometry() + nlcd_at_bufs <- + exactextractr::exact_extract( + from, + bufs_pol, + fun = "frac", + force_df = TRUE, + progress = FALSE, + max_cells_in_memory = max_cells + ) + + # select only the columns of interest + nlcd_at_buf_names <- names(nlcd_at_bufs) + nlcd_val_cols <- + grep("^frac_", nlcd_at_buf_names) + nlcd_at_bufs <- nlcd_at_bufs[, nlcd_val_cols] + } + # change column names nlcd_names <- names(nlcd_at_bufs) nlcd_names <- sub(pattern = "frac_", replacement = "", x = nlcd_names) - nlcd_names <- sort(as.numeric(nlcd_names)) - nlcd_names <- nlcd_classes[nlcd_classes$value %in% nlcd_names, c("class")] - new_names <- sapply( - nlcd_names, - function(x) { - sprintf("LDU_%s_0_%05d", x, radius) - } - ) + nlcd_names <- + switch( + mode, + exact = sort(as.numeric(nlcd_names)), + fast = nlcd_names + ) + nlcd_names <- + nlcd_classes$class[match(nlcd_names, nlcd_classes[[class_query]])] + #nlcd_classes[nlcd_classes[[class_query]] %in% nlcd_names, c("class")] + new_names <- sprintf("LDU_%s_0_%05d", nlcd_names, radius) names(nlcd_at_bufs) <- new_names # merge locs_df with nlcd class fractions new_data_vect <- cbind(locs_df, as.integer(year), nlcd_at_bufs) @@ -404,7 +433,7 @@ calc_ecoregion <- locs_df <- locs_prepared[[2]] extracted <- terra::intersect(locsp, from) - return(extracted) + # Generate field names from extracted ecoregion keys # TODO: if we keep all-zero fields, the initial reference # should be the ecoregion polygon, not the extracted data diff --git a/R/process.R b/R/process.R index a32f19c8..b4afbe55 100644 --- a/R/process.R +++ b/R/process.R @@ -683,6 +683,8 @@ process_koppen_geiger <- #' returning a single `SpatRaster` object. #' @param path character giving nlcd data path #' @param year numeric giving the year of NLCD data used +#' @param extent numeric(4) or SpatExtent giving the extent of the raster +#' if `NULL` (default), the entire raster is loaded #' @param ... Placeholders. #' @description Reads NLCD file of selected `year`. #' @returns a `SpatRaster` object @@ -695,6 +697,7 @@ process_nlcd <- function( path = NULL, year = 2021, + extent = NULL, ... ) { # check inputs @@ -717,7 +720,7 @@ process_nlcd <- if (length(nlcd_file) == 0) { stop("NLCD data not available for this year.") } - nlcd <- terra::rast(nlcd_file) + nlcd <- terra::rast(nlcd_file, win = extent) terra::metags(nlcd) <- c(year = year) return(nlcd) } @@ -752,9 +755,11 @@ process_ecoregion <- ecoreg <- ecoreg[, grepl("^(L2_KEY|L3_KEY)", names(ecoreg))] ecoreg_edit_idx <- sf::st_intersects(ecoreg, poly_tukey, sparse = FALSE) ecoreg_edit_idx <- vapply(ecoreg_edit_idx, function(x) any(x), logical(1)) - ecoreg_else <- ecoreg[!ecoreg_edit_idx, ] - ecoreg_edit <- sf::st_union(ecoreg[ecoreg_edit_idx, ], poly_tukey) - ecoreg <- rbind(ecoreg_else, ecoreg_edit) + if (!all(ecoreg_edit_idx == 0)) { + ecoreg_else <- ecoreg[!ecoreg_edit_idx, ] + ecoreg_edit <- sf::st_union(ecoreg[ecoreg_edit_idx, ], poly_tukey) + ecoreg <- rbind(ecoreg_else, ecoreg_edit) + } ecoreg$time <- paste0( "1997 - ", data.table::year(Sys.time()) ) diff --git a/inst/extdata/nlcd_classes.csv b/inst/extdata/nlcd_classes.csv index 4e2f088e..4fd90305 100644 --- a/inst/extdata/nlcd_classes.csv +++ b/inst/extdata/nlcd_classes.csv @@ -1,17 +1,18 @@ "","value","class","names","col" -"1",0,"TUNCL","Unclassified","white" +"1",0,"TUNCL","Unclassified","#ffffff00" "2",11,"TWATR","Open Water","#476ba1" -"3",21,"TDVOS","Developed, Open Space","#decaca" -"4",22,"TDVLO","Developed, Low Intensity","#d99482" -"5",23,"TDVMI","Developed, Medium Intensity","#ee0000" -"6",24,"TDVHI","Developed, High Intensity","#ab0000" -"7",31,"TBARN","Barren Land","#b3aea3" -"8",41,"TDFOR","Deciduous Forest","#68ab63" -"9",42,"TEFOR","Evergreen Forest","#1c6330" -"10",43,"TMFOR","Mixed Forest","#b5ca8f" -"11",52,"TSHRB","Shrub/Scrub","#ccba7d" -"12",71,"THERB","Herbaceous","#e3e3c2" -"13",81,"TPAST","Hay/Pasture","#dcd93d" -"14",82,"TPLNT","Cultivated Crops","#ab7028" -"15",90,"TWDWT","Woody Wetlands","#bad9eb" -"16",95,"THWEM","Emergent Herbaceous Wetlands","#70a3ba" +"3",12,"TSNOW","Perennial Snow/Ice","#F5F5F5" +"4",21,"TDVOS","Developed, Open Space","#decaca" +"5",22,"TDVLO","Developed, Low Intensity","#d99482" +"6",23,"TDVMI","Developed, Medium Intensity","#ee0000" +"7",24,"TDVHI","Developed, High Intensity","#ab0000" +"8",31,"TBARN","Barren Land","#b3aea3" +"9",41,"TDFOR","Deciduous Forest","#68ab63" +"10",42,"TEFOR","Evergreen Forest","#1c6330" +"11",43,"TMFOR","Mixed Forest","#b5ca8f" +"12",52,"TSHRB","Shrub/Scrub","#ccba7d" +"13",71,"THERB","Herbaceous","#e3e3c2" +"14",81,"TPAST","Hay/Pasture","#dcd93d" +"15",82,"TPLNT","Cultivated Crops","#ab7028" +"16",90,"TWDWT","Woody Wetlands","#bad9eb" +"17",95,"THWEM","Emergent Herbaceous Wetlands","#70a3ba" diff --git a/man/calc_nlcd.Rd b/man/calc_nlcd.Rd index a8819959..ca0be2ac 100644 --- a/man/calc_nlcd.Rd +++ b/man/calc_nlcd.Rd @@ -8,9 +8,11 @@ calc_nlcd( from, locs, locs_id = "site_id", + mode = c("exact", "terra"), radius = 1000, max_cells = 5e+07, geom = FALSE, + nthreads = 1L, ... ) } @@ -21,12 +23,17 @@ calc_nlcd( \item{locs_id}{character(1). Unique identifier of locations} +\item{mode}{character(1). One of \code{"exact"} +(using \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract()}}) +or \code{"terra"} (using \code{\link[terra:freq]{terra::freq()}}).} + \item{radius}{numeric (non-negative) giving the radius of buffer around points} \item{max_cells}{integer(1). Maximum number of cells to be read at once. Higher values may expedite processing, but will increase memory usage. -Maximum possible value is \code{2^31 - 1}. +Maximum possible value is \code{2^31 - 1}. Only valid when +\code{mode = "exact"}. See \code{\link[exactextractr:exact_extract]{exactextractr::exact_extract}} for details.} \item{geom}{logical(1). Should the geometry of \code{locs} be returned in the @@ -47,8 +54,13 @@ a \code{data.frame} object containing \code{locs_id}, longitude, latitude, time (year), and computed ratio for each land cover class. } \note{ -NLCD is available in U.S. only. Users should be cautious -the spatial extent of the data. +NLCD is available in U.S. only. Users should be aware of +the spatial extent of the data. The results are different depending +on \code{mode} argument. The \code{"terra"} mode is less memory intensive +but less accurate because it counts the number of cells +intersecting with the buffer. The \code{"exact"} may be more accurate +but uses more memory as it will account for the partial overlap +with the buffer. } \seealso{ \code{\link{process_nlcd}} diff --git a/man/process_nlcd.Rd b/man/process_nlcd.Rd index c8d67341..472012f4 100644 --- a/man/process_nlcd.Rd +++ b/man/process_nlcd.Rd @@ -4,13 +4,16 @@ \alias{process_nlcd} \title{Process land cover data} \usage{ -process_nlcd(path = NULL, year = 2021, ...) +process_nlcd(path = NULL, year = 2021, extent = NULL, ...) } \arguments{ \item{path}{character giving nlcd data path} \item{year}{numeric giving the year of NLCD data used} +\item{extent}{numeric(4) or SpatExtent giving the extent of the raster +if \code{NULL} (default), the entire raster is loaded} + \item{...}{Placeholders.} } \value{ diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 504e5744..fc646ed3 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -441,10 +441,10 @@ testthat::test_that("Check calc_nlcd works", { withr::local_package("sf") withr::local_options(list(sf_use_s2 = FALSE)) - point_us1 <- cbind(lon = -114.7, lat = 38.9, id = 1) - point_us2 <- cbind(lon = -114, lat = 39, id = 2) - point_ak <- cbind(lon = -155.997, lat = 69.3884, id = 3) # alaska - point_fr <- cbind(lon = 2.957, lat = 43.976, id = 4) # france + point_us1 <- cbind(lon = -114.7, lat = 38.9, site_id = 1) + point_us2 <- cbind(lon = -114, lat = 39, site_id = 2) + point_ak <- cbind(lon = -155.997, lat = 69.3884, site_id = 3) # alaska + point_fr <- cbind(lon = 2.957, lat = 43.976, site_id = 4) # france eg_data <- rbind(point_us1, point_us2, point_ak, point_fr) |> as.data.frame() |> terra::vect(crs = "EPSG:4326") @@ -474,6 +474,22 @@ testthat::test_that("Check calc_nlcd works", { radius = -3), "radius has not a likely value." ) + + # -- buf_radius has likely value + testthat::expect_no_error( + calc_nlcd(locs = eg_data, + from = nlcdras, + mode = "exact", + radius = 300) + ) + testthat::expect_no_error( + calc_nlcd(locs = eg_data, + from = nlcdras, + mode = "terra", + radius = 300) + ) + + # -- year is numeric testthat::expect_error( process_nlcd(path = path_testdata, year = "2021"), @@ -492,7 +508,7 @@ testthat::test_that("Check calc_nlcd works", { ) testthat::expect_error( calc_nlcd(locs = 12, - locs_id = "id", + locs_id = "site_id", from = nlcdras) ) testthat::expect_error( @@ -519,14 +535,14 @@ testthat::test_that("Check calc_nlcd works", { testthat::expect_no_error( calc_nlcd( locs = eg_data, - locs_id = "id", + locs_id = "site_id", from = nlcdras, radius = buf_radius ) ) output <- calc_nlcd( locs = eg_data, - locs_id = "id", + locs_id = "site_id", radius = buf_radius, from = nlcdras ) @@ -556,7 +572,7 @@ testthat::test_that("Check calc_nlcd works", { ) output_geom <- calc_nlcd( locs = eg_data, - locs_id = "id", + locs_id = "site_id", radius = buf_radius, from = nlcdras, geom = TRUE From 1432d081fb9136176d6091d6389b9f41aef6d036 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 17:33:18 -0400 Subject: [PATCH 35/65] nlcd refactoring + lint - NLCD now supports multicore mode --- DESCRIPTION | 2 +- NAMESPACE | 4 ++ R/calculate_covariates.R | 65 ++++++++++++++-------- R/calculate_covariates_auxiliary.R | 2 + R/download.R | 43 +++++++------- R/process.R | 4 +- man/calc_nlcd.Rd | 2 + man/download_data.Rd | 34 +++++------ man/download_hms.Rd | 4 -- tests/testthat/test-calculate_covariates.R | 23 +++++++- 10 files changed, 113 insertions(+), 70 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f9f40753..e5cbcb36 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,7 +12,7 @@ Authors@R: c( ) Description: A Mechanism/Machine for Data, Environments, and User Setup package for health and climate research. It is fully tested, versioned, and open source and open access. Depends: R (>= 4.1.0) -Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), parallelly, stars, future, tidyr, rlang, rstac, nhdplusTools, archive +Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), parallelly, stars, future, future.apply, tidyr, rlang, rstac, nhdplusTools, archive, collapse Suggests: covr, withr, knitr, rmarkdown, lwgeom, FNN, doRNG Encoding: UTF-8 VignetteBuilder: knitr, rmarkdown diff --git a/NAMESPACE b/NAMESPACE index 41664e17..cb043a00 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -123,6 +123,7 @@ import(sf) import(sftime) import(stars) importFrom(archive,archive_extract) +importFrom(collapse,rowbind) importFrom(data.table,.SD) importFrom(data.table,as.data.table) importFrom(data.table,fread) @@ -143,7 +144,10 @@ importFrom(dplyr,summarize) importFrom(dplyr,ungroup) importFrom(exactextractr,exact_extract) importFrom(future,cluster) +importFrom(future,multicore) importFrom(future,plan) +importFrom(future,sequential) +importFrom(future.apply,future_Map) importFrom(future.apply,future_lapply) importFrom(httr,GET) importFrom(httr,HEAD) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 5c0aea4c..770bb3af 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -241,7 +241,7 @@ calc_koppen_geiger <- #' @param from SpatRaster(1). Output of \code{process_nlcd()}. #' @param locs terra::SpatVector of points geometry #' @param locs_id character(1). Unique identifier of locations -#' @param mode character(1). One of `"exact"` +#' @param mode character(1). One of `"exact"` #' (using [`exactextractr::exact_extract()`]) #' or `"terra"` (using [`terra::freq()`]). #' @param radius numeric (non-negative) giving the @@ -257,6 +257,7 @@ calc_koppen_geiger <- #' make the `data.frame` difficult to read due to long geometry strings. The #' coordinate reference system of the `$geometry` is the coordinate #' reference system of `from`. +#' @param nthreads integer(1). Number of threads to be used #' @param ... Placeholders. #' @note NLCD is available in U.S. only. Users should be aware of #' the spatial extent of the data. The results are different depending @@ -280,6 +281,9 @@ calc_koppen_geiger <- #' @importFrom terra intersect #' @importFrom terra metags #' @importFrom exactextractr exact_extract +#' @importFrom future plan multicore sequential +#' @importFrom future.apply future_Map +#' @importFrom collapse rowbind #' @export calc_nlcd <- function(from, locs, @@ -302,6 +306,12 @@ calc_nlcd <- function(from, if (!methods::is(from, "SpatRaster")) { stop("from is not a SpatRaster.") } + if (nthreads > 1L) { + stopifnot(Sys.info()["sysname"] != "Windows") + future::plan(future::multicore, workers = nthreads) + } else { + future::plan(future::sequential) + } # prepare locations locs_prepared <- calc_prepare_locs( @@ -323,19 +333,24 @@ calc_nlcd <- function(from, cfpath <- system.file("extdata", "nlcd_classes.csv", package = "amadeus") nlcd_classes <- utils::read.csv(cfpath) - if (mode == "fast") { - # fast mode + if (mode == "terra") { + # terra mode class_query <- "names" # extract land cover class in each buffer - nlcd_at_bufs <- - terra::freq( - from, - zones = bufs_pol, - wide = TRUE - ) + nlcd_at_bufs <- future.apply::future_Map( + function(i) { + terra::freq( + from, + zones = bufs_pol[i, ], + wide = TRUE + ) + }, seq_len(nrow(bufs_pol)), + future.seed = TRUE + ) |> + collapse::rowbind(fill = TRUE) nlcd_at_bufs <- nlcd_at_bufs[, -seq(1, 2)] nlcd_cellcnt <- nlcd_at_bufs[, seq(1, ncol(nlcd_at_bufs), 1)] - nlcd_cellcnt <- nlcd_cellcnt / rowSums(nlcd_cellcnt) + nlcd_cellcnt <- nlcd_cellcnt / rowSums(nlcd_cellcnt, na.rm = TRUE) nlcd_at_bufs[, seq(1, ncol(nlcd_at_bufs), 1)] <- nlcd_cellcnt } else { class_query <- "value" @@ -343,22 +358,28 @@ calc_nlcd <- function(from, bufs_pol <- bufs_pol |> sf::st_as_sf() |> sf::st_geometry() - nlcd_at_bufs <- - exactextractr::exact_extract( - from, - bufs_pol, - fun = "frac", - force_df = TRUE, - progress = FALSE, - max_cells_in_memory = max_cells - ) - + nlcd_at_bufs <- future.apply::future_Map( + function(i) { + exactextractr::exact_extract( + from, + bufs_pol[i, ], + fun = "frac", + force_df = TRUE, + progress = FALSE, + max_cells_in_memory = max_cells + ) + }, seq_len(length(bufs_pol)), + future.seed = TRUE + ) |> + collapse::rowbind(fill = TRUE) # select only the columns of interest nlcd_at_buf_names <- names(nlcd_at_bufs) nlcd_val_cols <- grep("^frac_", nlcd_at_buf_names) nlcd_at_bufs <- nlcd_at_bufs[, nlcd_val_cols] } + # fill NAs + nlcd_at_bufs[is.na(nlcd_at_bufs)] <- 0 # change column names nlcd_names <- names(nlcd_at_bufs) @@ -367,11 +388,10 @@ calc_nlcd <- function(from, switch( mode, exact = sort(as.numeric(nlcd_names)), - fast = nlcd_names + terra = nlcd_names ) nlcd_names <- nlcd_classes$class[match(nlcd_names, nlcd_classes[[class_query]])] - #nlcd_classes[nlcd_classes[[class_query]] %in% nlcd_names, c("class")] new_names <- sprintf("LDU_%s_0_%05d", nlcd_names, radius) names(nlcd_at_bufs) <- new_names # merge locs_df with nlcd class fractions @@ -382,6 +402,7 @@ calc_nlcd <- function(from, names(new_data_vect)[1:2] <- c(locs_id, "time") } calc_check_time(covar = new_data_vect, POSIXt = FALSE) + future::plan(future::sequential) return(new_data_vect) } diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 3205d965..8a808c2a 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -300,6 +300,7 @@ calc_time <- function( #' @return NULL #' @keywords internal #' @export +# nolint start calc_check_time <- function( covar, POSIXt = TRUE @@ -317,6 +318,7 @@ calc_check_time <- function( ) } } +# nolint end #' Perform covariate extraction #' @description diff --git a/R/download.R b/R/download.R index 3c94e902..ce874a90 100644 --- a/R/download.R +++ b/R/download.R @@ -17,28 +17,30 @@ #' @seealso #' For details of each download function per dataset, #' Please refer to: -#' * \link{`download_aqs_data`}: `"aqs"`, `"AQS"` -#' * \link{`download_ecoregion_data`}: `"ecoregions"`, `"ecoregion"` -#' * \link{`download_geos_data`}: `"geos"` -#' * \link{`download_gmted_data`}: `"gmted"`, `"GMTED"` -#' * \link{`download_koppen_geiger_data`}: `"koppen"`, `"koppengeiger"` -#' * \link{`download_merra2_data`}: "merra2", `"merra"`, `"MERRA"`, `"MERRA2"` -#' * \link{`download_narr_monolevel_data`}: `"narr_monolevel"`, `"monolevel"` -#' * \link{`download_narr_p_levels_data`}: `"narr_p_levels"`, `"p_levels"`, `"plevels"` -#' * \link{`download_nlcd_data`}: `"nlcd"`, `"NLCD"` -#' * \link{`download_hms_data`}: `"noaa"`, `"smoke"`, `"hms"` -#' * \link{`download_sedac_groads_data`}: `"sedac_groads"`, `"groads"` -#' * \link{`download_sedac_population_data`}: `"sedac_population"`, `"population"` -#' * \link{`download_modis_data`}: `"modis"`, `"MODIS"` -#' * \link{`download_tri_data`}: `"tri"`, `"TRI"` -#' * \link{`download_nei_data`}: `"nei"`, `"NEI"` -#' * \link{`download_gridmet_data`}: `"gridMET"`, `"gridmet"` -#' * \link{`download_terraclimate_data`}: `"TerraClimate"`, `"terraclimate"` +#' * \link{`download_aqs`}: `"aqs"`, `"AQS"` +#' * \link{`download_ecoregion`}: `"ecoregions"`, `"ecoregion"` +#' * \link{`download_geos`}: `"geos"` +#' * \link{`download_gmted`}: `"gmted"`, `"GMTED"` +#' * \link{`download_koppen_geiger`}: `"koppen"`, `"koppengeiger"` +#' * \link{`download_merra2`}: "merra2", `"merra"`, `"MERRA"`, `"MERRA2"` +#' * \link{`download_narr_monolevel`}: `"narr_monolevel"`, `"monolevel"` +#' * \link{`download_narr_p_levels`}: `"narr_p_levels"`, `"p_levels"`, +#' `"plevels"` +#' * \link{`download_nlcd`}: `"nlcd"`, `"NLCD"` +#' * \link{`download_hms`}: `"noaa"`, `"smoke"`, `"hms"` +#' * \link{`download_sedac_groads`}: `"sedac_groads"`, `"groads"` +#' * \link{`download_sedac_population`}: `"sedac_population"`, `"population"` +#' * \link{`download_modis`}: `"modis"`, `"MODIS"` +#' * \link{`download_tri`}: `"tri"`, `"TRI"` +#' * \link{`download_nei`}: `"nei"`, `"NEI"` +#' * \link{`download_gridmet`}: `"gridMET"`, `"gridmet"` +#' * \link{`download_terraclimate`}: `"TerraClimate"`, `"terraclimate"` #' @returns NULL #' @export download_data <- function( - dataset_name = c("aqs", "ecoregion", "ecoregions", "geos", "gmted", "koppen", + dataset_name = c("aqs", "ecoregion", "ecoregions", + "geos", "gmted", "koppen", "koppengeiger", "merra2", "merra", "narr_monolevel", "modis", "narr_p_levels", "nlcd", "noaa", "sedac_groads", "sedac_population", "groads", "population", "plevels", @@ -1763,9 +1765,6 @@ download_sedac_population <- function( #' data. Format YYYY-MM-DD (ex. September 1, 2023 is `"2023-09-01"`). #' @param date_end character(1). length of 10. End date for downloading data. #' Format YYYY-MM-DD (ex. September 10, 2023 is `"2023-09-10"`). -#' @param directory_to_download character(1). Directory to download zip files -#' from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if -#' \code{data_format = "KML"}.) #' @param directory_to_save character(1). Directory to save data. If #' `data_format = "Shapefile"`, two sub-directories will be created for the #' downloaded zip files ("/zip_files") and the unzipped shapefiles @@ -2472,8 +2471,6 @@ download_tri <- function( #### 2. directory setup download_setup_dir(directory_to_save) directory_to_save <- download_sanitize_path(directory_to_save) - - #### 3. define measurement data paths url_download <- "https://data.epa.gov/efservice/downloads/tri/mv_tri_basic_download/" diff --git a/R/process.R b/R/process.R index b4afbe55..d4c0f240 100644 --- a/R/process.R +++ b/R/process.R @@ -671,7 +671,7 @@ process_koppen_geiger <- if (period == "present") { terra::metags(kg_rast) <- c(year = "1980 - 2016") } else { - terra::meteags(kg_rast) <- c(year = "2071 - 2100") + terra::metags(kg_rast) <- c(year = "2071 - 2100") } return(kg_rast) } @@ -1047,6 +1047,8 @@ process_aqs <- site_id <- NULL Datum <- NULL POC <- NULL + Date.Local <- NULL + Sample.Duration <- NULL # select relevant fields only sites <- sites |> diff --git a/man/calc_nlcd.Rd b/man/calc_nlcd.Rd index ca0be2ac..237a6bc3 100644 --- a/man/calc_nlcd.Rd +++ b/man/calc_nlcd.Rd @@ -43,6 +43,8 @@ make the \code{data.frame} difficult to read due to long geometry strings. The coordinate reference system of the \verb{$geometry} is the coordinate reference system of \code{from}.} +\item{nthreads}{integer(1). Number of threads to be used} + \item{...}{Placeholders.} } \value{ diff --git a/man/download_data.Rd b/man/download_data.Rd index d61c038c..c455896a 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -39,23 +39,23 @@ The \code{download_data()} function accesses and downloads atmospheric, meteorol For details of each download function per dataset, Please refer to: \itemize{ -\item \link{`download_aqs_data`}: \code{"aqs"}, \code{"AQS"} -\item \link{`download_ecoregion_data`}: \code{"ecoregions"}, \code{"ecoregion"} -\item \link{`download_geos_data`}: \code{"geos"} -\item \link{`download_gmted_data`}: \code{"gmted"}, \code{"GMTED"} -\item \link{`download_koppen_geiger_data`}: \code{"koppen"}, \code{"koppengeiger"} -\item \link{`download_merra2_data`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} -\item \link{`download_narr_monolevel_data`}: \code{"narr_monolevel"}, \code{"monolevel"} -\item \link{`download_narr_p_levels_data`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} -\item \link{`download_nlcd_data`}: \code{"nlcd"}, \code{"NLCD"} -\item \link{`download_hms_data`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} -\item \link{`download_sedac_groads_data`}: \code{"sedac_groads"}, \code{"groads"} -\item \link{`download_sedac_population_data`}: \code{"sedac_population"}, \code{"population"} -\item \link{`download_modis_data`}: \code{"modis"}, \code{"MODIS"} -\item \link{`download_tri_data`}: \code{"tri"}, \code{"TRI"} -\item \link{`download_nei_data`}: \code{"nei"}, \code{"NEI"} -\item \link{`download_gridmet_data`}: \code{"gridMET"}, \code{"gridmet"} -\item \link{`download_terraclimate_data`}: \code{"TerraClimate"}, \code{"terraclimate"} +\item \link{`download_aqs`}: \code{"aqs"}, \code{"AQS"} +\item \link{`download_ecoregion`}: \code{"ecoregions"}, \code{"ecoregion"} +\item \link{`download_geos`}: \code{"geos"} +\item \link{`download_gmted`}: \code{"gmted"}, \code{"GMTED"} +\item \link{`download_koppen_geiger`}: \code{"koppen"}, \code{"koppengeiger"} +\item \link{`download_merra2`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} +\item \link{`download_narr_monolevel`}: \code{"narr_monolevel"}, \code{"monolevel"} +\item \link{`download_narr_p_levels`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} +\item \link{`download_nlcd`}: \code{"nlcd"}, \code{"NLCD"} +\item \link{`download_hms`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} +\item \link{`download_sedac_groads`}: \code{"sedac_groads"}, \code{"groads"} +\item \link{`download_sedac_population`}: \code{"sedac_population"}, \code{"population"} +\item \link{`download_modis`}: \code{"modis"}, \code{"MODIS"} +\item \link{`download_tri`}: \code{"tri"}, \code{"TRI"} +\item \link{`download_nei`}: \code{"nei"}, \code{"NEI"} +\item \link{`download_gridmet`}: \code{"gridMET"}, \code{"gridmet"} +\item \link{`download_terraclimate`}: \code{"TerraClimate"}, \code{"terraclimate"} } } \author{ diff --git a/man/download_hms.Rd b/man/download_hms.Rd index 403df0dc..91c8930c 100644 --- a/man/download_hms.Rd +++ b/man/download_hms.Rd @@ -50,10 +50,6 @@ if \code{data_format = "KML"}.)} \item{remove_zip}{logical(1). Remove zip files from directory_to_download. Default is \code{FALSE}. (Ignored if \code{data_format = "KML"}.)} - -\item{directory_to_download}{character(1). Directory to download zip files -from NOAA Hazard Mapping System Fire and Smoke Product. (Ignored if -\code{data_format = "KML"}.)} } \value{ NULL; Zip and/or data files will be downloaded and stored in diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index fc646ed3..8abed92a 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -439,7 +439,11 @@ testthat::test_that("Check calc_nlcd works", { withr::local_package("terra") withr::local_package("exactextractr") withr::local_package("sf") - withr::local_options(list(sf_use_s2 = FALSE)) + withr::local_package("future") + withr::local_package("future.apply") + withr::local_options( + list(sf_use_s2 = FALSE, future.resolve.recursive = 2L) + ) point_us1 <- cbind(lon = -114.7, lat = 38.9, site_id = 1) point_us2 <- cbind(lon = -114, lat = 39, site_id = 2) @@ -475,7 +479,7 @@ testthat::test_that("Check calc_nlcd works", { "radius has not a likely value." ) - # -- buf_radius has likely value + # -- two modes work properly testthat::expect_no_error( calc_nlcd(locs = eg_data, from = nlcdras, @@ -488,6 +492,21 @@ testthat::test_that("Check calc_nlcd works", { mode = "terra", radius = 300) ) + # -- multicore mode works properly + testthat::expect_no_error( + calc_nlcd(locs = eg_data, + from = nlcdras, + mode = "exact", + radius = 1000, + nthreads = 2L) + ) + testthat::expect_no_error( + calc_nlcd(locs = eg_data, + from = nlcdras, + mode = "terra", + radius = 1000, + nthreads = 2L) + ) # -- year is numeric From 59cdef859b69a31c8ae55fe578c5f591791ab8fb Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 17:50:02 -0400 Subject: [PATCH 36/65] Rd syntax check - download_data() --- R/download.R | 34 +++++++++++++++++----------------- R/process.R | 2 +- man/download_data.Rd | 35 ++++++++++++++++++----------------- man/process_aqs.Rd | 2 +- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/R/download.R b/R/download.R index ce874a90..ca99a3ac 100644 --- a/R/download.R +++ b/R/download.R @@ -17,24 +17,24 @@ #' @seealso #' For details of each download function per dataset, #' Please refer to: -#' * \link{`download_aqs`}: `"aqs"`, `"AQS"` -#' * \link{`download_ecoregion`}: `"ecoregions"`, `"ecoregion"` -#' * \link{`download_geos`}: `"geos"` -#' * \link{`download_gmted`}: `"gmted"`, `"GMTED"` -#' * \link{`download_koppen_geiger`}: `"koppen"`, `"koppengeiger"` -#' * \link{`download_merra2`}: "merra2", `"merra"`, `"MERRA"`, `"MERRA2"` -#' * \link{`download_narr_monolevel`}: `"narr_monolevel"`, `"monolevel"` -#' * \link{`download_narr_p_levels`}: `"narr_p_levels"`, `"p_levels"`, +#' * \code{\link{download_aqs}}: `"aqs"`, `"AQS"` +#' * \code{\link{download_ecoregion}}: `"ecoregions"`, `"ecoregion"` +#' * \code{\link{download_geos}}: `"geos"` +#' * \code{\link{download_gmted}}: `"gmted"`, `"GMTED"` +#' * \code{\link{download_koppen_geiger}}: `"koppen"`, `"koppengeiger"` +#' * \code{\link{download_merra2}}: "merra2", `"merra"`, `"MERRA"`, `"MERRA2"` +#' * \code{\link{download_narr_monolevel}}: `"narr_monolevel"`, `"monolevel"` +#' * \code{\link{download_narr_p_levels}}: `"narr_p_levels"`, `"p_levels"`, #' `"plevels"` -#' * \link{`download_nlcd`}: `"nlcd"`, `"NLCD"` -#' * \link{`download_hms`}: `"noaa"`, `"smoke"`, `"hms"` -#' * \link{`download_sedac_groads`}: `"sedac_groads"`, `"groads"` -#' * \link{`download_sedac_population`}: `"sedac_population"`, `"population"` -#' * \link{`download_modis`}: `"modis"`, `"MODIS"` -#' * \link{`download_tri`}: `"tri"`, `"TRI"` -#' * \link{`download_nei`}: `"nei"`, `"NEI"` -#' * \link{`download_gridmet`}: `"gridMET"`, `"gridmet"` -#' * \link{`download_terraclimate`}: `"TerraClimate"`, `"terraclimate"` +#' * \code{\link{download_nlcd}}: `"nlcd"`, `"NLCD"` +#' * \code{\link{download_hms}}: `"noaa"`, `"smoke"`, `"hms"` +#' * \code{\link{download_sedac_groads}}: `"sedac_groads"`, `"groads"` +#' * \code{\link{download_sedac_population}}: `"sedac_population"`, `"population"` +#' * \code{\link{download_modis}}: `"modis"`, `"MODIS"` +#' * \code{\link{download_tri}}: `"tri"`, `"TRI"` +#' * \code{\link{download_nei}}: `"nei"`, `"NEI"` +#' * \code{\link{download_gridmet}}: `"gridMET"`, `"gridmet"` +#' * \code{\link{download_terraclimate}}: `"TerraClimate"`, `"terraclimate"` #' @returns NULL #' @export download_data <- diff --git a/R/process.R b/R/process.R index d4c0f240..dd79ccb5 100644 --- a/R/process.R +++ b/R/process.R @@ -985,7 +985,7 @@ process_nei <- function( #' @param return_format character(1). `"terra"` or `"sf"` or `"data.table"`. #' @param ... Placeholders. #' @seealso -#' * [`download_aqs_data()`] +#' * [`download_aqs()`] #' * [EPA, n.d., _AQS Parameter Codes_]( #' https://aqs.epa.gov/aqsweb/documents/codetables/parameters.csv) #' @returns a `SpatVector` or sf object depending on the `return_format` diff --git a/man/download_data.Rd b/man/download_data.Rd index c455896a..9b6965e6 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -39,23 +39,24 @@ The \code{download_data()} function accesses and downloads atmospheric, meteorol For details of each download function per dataset, Please refer to: \itemize{ -\item \link{`download_aqs`}: \code{"aqs"}, \code{"AQS"} -\item \link{`download_ecoregion`}: \code{"ecoregions"}, \code{"ecoregion"} -\item \link{`download_geos`}: \code{"geos"} -\item \link{`download_gmted`}: \code{"gmted"}, \code{"GMTED"} -\item \link{`download_koppen_geiger`}: \code{"koppen"}, \code{"koppengeiger"} -\item \link{`download_merra2`}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} -\item \link{`download_narr_monolevel`}: \code{"narr_monolevel"}, \code{"monolevel"} -\item \link{`download_narr_p_levels`}: \code{"narr_p_levels"}, \code{"p_levels"}, \code{"plevels"} -\item \link{`download_nlcd`}: \code{"nlcd"}, \code{"NLCD"} -\item \link{`download_hms`}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} -\item \link{`download_sedac_groads`}: \code{"sedac_groads"}, \code{"groads"} -\item \link{`download_sedac_population`}: \code{"sedac_population"}, \code{"population"} -\item \link{`download_modis`}: \code{"modis"}, \code{"MODIS"} -\item \link{`download_tri`}: \code{"tri"}, \code{"TRI"} -\item \link{`download_nei`}: \code{"nei"}, \code{"NEI"} -\item \link{`download_gridmet`}: \code{"gridMET"}, \code{"gridmet"} -\item \link{`download_terraclimate`}: \code{"TerraClimate"}, \code{"terraclimate"} +\item \code{\link{download_aqs}}: \code{"aqs"}, \code{"AQS"} +\item \code{\link{download_ecoregion}}: \code{"ecoregions"}, \code{"ecoregion"} +\item \code{\link{download_geos}}: \code{"geos"} +\item \code{\link{download_gmted}}: \code{"gmted"}, \code{"GMTED"} +\item \code{\link{download_koppen_geiger}}: \code{"koppen"}, \code{"koppengeiger"} +\item \code{\link{download_merra2}}: "merra2", \code{"merra"}, \code{"MERRA"}, \code{"MERRA2"} +\item \code{\link{download_narr_monolevel}}: \code{"narr_monolevel"}, \code{"monolevel"} +\item \code{\link{download_narr_p_levels}}: \code{"narr_p_levels"}, \code{"p_levels"}, +\code{"plevels"} +\item \code{\link{download_nlcd}}: \code{"nlcd"}, \code{"NLCD"} +\item \code{\link{download_hms}}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} +\item \code{\link{download_sedac_groads}}: \code{"sedac_groads"}, \code{"groads"} +\item \code{\link{download_sedac_population}}: \code{"sedac_population"}, \code{"population"} +\item \code{\link{download_modis}}: \code{"modis"}, \code{"MODIS"} +\item \code{\link{download_tri}}: \code{"tri"}, \code{"TRI"} +\item \code{\link{download_nei}}: \code{"nei"}, \code{"NEI"} +\item \code{\link{download_gridmet}}: \code{"gridMET"}, \code{"gridmet"} +\item \code{\link{download_terraclimate}}: \code{"TerraClimate"}, \code{"terraclimate"} } } \author{ diff --git a/man/process_aqs.Rd b/man/process_aqs.Rd index bd55ad23..357e427b 100644 --- a/man/process_aqs.Rd +++ b/man/process_aqs.Rd @@ -44,7 +44,7 @@ a long processing time or even a crash. } \seealso{ \itemize{ -\item \code{\link[=download_aqs_data]{download_aqs_data()}} +\item \code{\link[=download_aqs]{download_aqs()}} \item \href{https://aqs.epa.gov/aqsweb/documents/codetables/parameters.csv}{EPA, n.d., \emph{AQS Parameter Codes}} } } From 6dfebbd2ec627d5c490d8257a7b154e96def98c8 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 18:02:25 -0400 Subject: [PATCH 37/65] lint + dependency fix --- DESCRIPTION | 2 +- R/download.R | 3 +- man/download_data.Rd | 3 +- tests/testthat/test-process.R | 69 +++++++++++++++++--------------- vignettes/download_functions.Rmd | 2 +- 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e5cbcb36..3442e503 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,7 +12,7 @@ Authors@R: c( ) Description: A Mechanism/Machine for Data, Environments, and User Setup package for health and climate research. It is fully tested, versioned, and open source and open access. Depends: R (>= 4.1.0) -Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), parallelly, stars, future, future.apply, tidyr, rlang, rstac, nhdplusTools, archive, collapse +Imports: dplyr, sf, sftime, stats, terra, methods, data.table, httr, rvest, exactextractr, utils, stringi, testthat (>= 3.0.0), parallelly, stars, future, future.apply, tidyr, rlang, rstac, nhdplusTools, archive, collapse, devtools Suggests: covr, withr, knitr, rmarkdown, lwgeom, FNN, doRNG Encoding: UTF-8 VignetteBuilder: knitr, rmarkdown diff --git a/R/download.R b/R/download.R index ca99a3ac..d3b69178 100644 --- a/R/download.R +++ b/R/download.R @@ -29,7 +29,8 @@ #' * \code{\link{download_nlcd}}: `"nlcd"`, `"NLCD"` #' * \code{\link{download_hms}}: `"noaa"`, `"smoke"`, `"hms"` #' * \code{\link{download_sedac_groads}}: `"sedac_groads"`, `"groads"` -#' * \code{\link{download_sedac_population}}: `"sedac_population"`, `"population"` +#' * \code{\link{download_sedac_population}}: `"sedac_population"`, +#' `"population"` #' * \code{\link{download_modis}}: `"modis"`, `"MODIS"` #' * \code{\link{download_tri}}: `"tri"`, `"TRI"` #' * \code{\link{download_nei}}: `"nei"`, `"NEI"` diff --git a/man/download_data.Rd b/man/download_data.Rd index 9b6965e6..d7da4c3e 100644 --- a/man/download_data.Rd +++ b/man/download_data.Rd @@ -51,7 +51,8 @@ Please refer to: \item \code{\link{download_nlcd}}: \code{"nlcd"}, \code{"NLCD"} \item \code{\link{download_hms}}: \code{"noaa"}, \code{"smoke"}, \code{"hms"} \item \code{\link{download_sedac_groads}}: \code{"sedac_groads"}, \code{"groads"} -\item \code{\link{download_sedac_population}}: \code{"sedac_population"}, \code{"population"} +\item \code{\link{download_sedac_population}}: \code{"sedac_population"}, +\code{"population"} \item \code{\link{download_modis}}: \code{"modis"}, \code{"MODIS"} \item \code{\link{download_tri}}: \code{"tri"}, \code{"TRI"} \item \code{\link{download_nei}}: \code{"nei"}, \code{"NEI"} diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 61ddcdea..c156c1cd 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -1463,45 +1463,48 @@ testthat::test_that("gridmet and terraclimate auxiliary functions.", { # test PRISM #### -testthat::test_that("process_prism returns a SpatRaster object with correct metadata", { - # Set up test data - withr::local_package("terra") - path <- testthat::test_path( - "..", "testdata", "prism", "PRISM_tmin_30yr_normal_4kmD1_0228_bil_test.nc" - ) - path_dir <- testthat::test_path( - "..", "testdata", "prism" - ) - element <- "tmin" - time <- "0228" +testthat::test_that( + "process_prism returns a SpatRaster object with correct metadata", + { + # Set up test data + withr::local_package("terra") + path <- testthat::test_path( + "..", "testdata", "prism", "PRISM_tmin_30yr_normal_4kmD1_0228_bil_test.nc" + ) + path_dir <- testthat::test_path( + "..", "testdata", "prism" + ) + element <- "tmin" + time <- "0228" - # Call the function - testthat::expect_no_error(result <- process_prism(path, element, time)) - testthat::expect_no_error(result2 <- process_prism(path_dir, element, time)) + # Call the function + testthat::expect_no_error(result <- process_prism(path, element, time)) + testthat::expect_no_error(result2 <- process_prism(path_dir, element, time)) - # Check the return type - testthat::expect_true(inherits(result, "SpatRaster")) - testthat::expect_true(inherits(result2, "SpatRaster")) + # Check the return type + testthat::expect_true(inherits(result, "SpatRaster")) + testthat::expect_true(inherits(result2, "SpatRaster")) - # Check the metadata - testthat::expect_equal(unname(terra::metags(result)["time"]), time) - testthat::expect_equal(unname(terra::metags(result)["element"]), element) + # Check the metadata + testthat::expect_equal(unname(terra::metags(result)["time"]), time) + testthat::expect_equal(unname(terra::metags(result)["element"]), element) - # Set up test data - path_bad <- "/path/to/nonexistent/folder" - element_bad <- "invalid_element" - time_bad <- "invalid_time" + # Set up test data + path_bad <- "/path/to/nonexistent/folder" + element_bad <- "invalid_element" + time_bad <- "invalid_time" - # Call the function and expect an error - testthat::expect_error(process_prism(NULL, element, time)) - testthat::expect_error( - testthat::expect_warning( - process_prism(path_bad, element, time) + # Call the function and expect an error + testthat::expect_error(process_prism(NULL, element, time)) + testthat::expect_error( + testthat::expect_warning( + process_prism(path_bad, element, time) + ) ) - ) - testthat::expect_error(process_prism(path_dir, element_bad, time)) - testthat::expect_error(process_prism(path_dir, element, time_bad)) -}) + testthat::expect_error(process_prism(path_dir, element_bad, time)) + testthat::expect_error(process_prism(path_dir, element, time_bad)) + } +) # test CropScape #### diff --git a/vignettes/download_functions.Rmd b/vignettes/download_functions.Rmd index b364bb0f..9b35156b 100644 --- a/vignettes/download_functions.Rmd +++ b/vignettes/download_functions.Rmd @@ -2,7 +2,7 @@ title: "download_data and NASA EarthData Account" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{download_data() and NASA EarthData Account} + %\VignetteIndexEntry{download_data and NASA EarthData Account} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} date: "2024-05-02" From 0ed2bb536958858d70ba42f506725ab2e2598dce Mon Sep 17 00:00:00 2001 From: Insang Song Date: Thu, 30 May 2024 23:15:50 -0400 Subject: [PATCH 38/65] bug fix + test fix - calc_nlcd column name sorting error fix - Download tests with directory_to_download argument fixed - Redo pkgdown action: the figure path is correct --- R/calculate_covariates.R | 16 ++++++++-------- R/download.R | 2 +- R/process.R | 2 +- man/download_huc.Rd | 2 +- tests/testthat/test-calculate_covariates.R | 22 ++++++++++++++-------- tests/testthat/test-download_functions.R | 2 -- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index 770bb3af..f8cd9d11 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -346,8 +346,8 @@ calc_nlcd <- function(from, ) }, seq_len(nrow(bufs_pol)), future.seed = TRUE - ) |> - collapse::rowbind(fill = TRUE) + ) + nlcd_at_bufs <- collapse::rowbind(nlcd_at_bufs, fill = TRUE) nlcd_at_bufs <- nlcd_at_bufs[, -seq(1, 2)] nlcd_cellcnt <- nlcd_at_bufs[, seq(1, ncol(nlcd_at_bufs), 1)] nlcd_cellcnt <- nlcd_cellcnt / rowSums(nlcd_cellcnt, na.rm = TRUE) @@ -355,23 +355,23 @@ calc_nlcd <- function(from, } else { class_query <- "value" # ratio of each nlcd class per buffer - bufs_pol <- bufs_pol |> + bufs_polx <- bufs_pol[terra::ext(from), ] |> sf::st_as_sf() |> sf::st_geometry() nlcd_at_bufs <- future.apply::future_Map( function(i) { exactextractr::exact_extract( from, - bufs_pol[i, ], + bufs_polx[i, ], fun = "frac", force_df = TRUE, progress = FALSE, max_cells_in_memory = max_cells ) - }, seq_len(length(bufs_pol)), + }, seq_len(length(bufs_polx)), future.seed = TRUE - ) |> - collapse::rowbind(fill = TRUE) + ) + nlcd_at_bufs <- collapse::rowbind(nlcd_at_bufs, fill = TRUE) # select only the columns of interest nlcd_at_buf_names <- names(nlcd_at_bufs) nlcd_val_cols <- @@ -387,7 +387,7 @@ calc_nlcd <- function(from, nlcd_names <- switch( mode, - exact = sort(as.numeric(nlcd_names)), + exact = as.numeric(nlcd_names), terra = nlcd_names ) nlcd_names <- diff --git a/R/download.R b/R/download.R index d3b69178..aa74b819 100644 --- a/R/download.R +++ b/R/download.R @@ -2865,7 +2865,7 @@ download_olm <- function( #' @author Insang Song #' @examples #' \dontrun{ -#' download_huc("Lower48", "Seamless", "~/data" +#' download_huc("Lower48", "Seamless", "/data" #' acknowledgement = TRUE, #' download = TRUE, #' unzip = TRUE) diff --git a/R/process.R b/R/process.R index dd79ccb5..1b000fa5 100644 --- a/R/process.R +++ b/R/process.R @@ -714,7 +714,7 @@ process_nlcd <- nlcd_file <- list.files( path, - pattern = paste0("nlcd_", year, "_.*.tif$"), + pattern = paste0("nlcd_", year, "_.*.(tif|img)$"), full.names = TRUE ) if (length(nlcd_file) == 0) { diff --git a/man/download_huc.Rd b/man/download_huc.Rd index affbfcbf..ec3c2e7e 100644 --- a/man/download_huc.Rd +++ b/man/download_huc.Rd @@ -53,7 +53,7 @@ please visit \href{https://www.epa.gov/waterdata/get-nhdplus-national-hydrograph } \examples{ \dontrun{ -download_huc("Lower48", "Seamless", "~/data" +download_huc("Lower48", "Seamless", "/data" acknowledgement = TRUE, download = TRUE, unzip = TRUE) diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 8abed92a..113090a0 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -471,6 +471,12 @@ testthat::test_that("Check calc_nlcd works", { radius = "1000"), "radius is not a numeric." ) + testthat::expect_error( + calc_nlcd(locs = eg_data, + from = nlcdras, + mode = "whatnot", + radius = 1000) + ) # -- buf_radius has likely value testthat::expect_error( calc_nlcd(locs = eg_data, @@ -481,10 +487,10 @@ testthat::test_that("Check calc_nlcd works", { # -- two modes work properly testthat::expect_no_error( - calc_nlcd(locs = eg_data, + calc_nlcd(locs = sf::st_as_sf(eg_data), from = nlcdras, mode = "exact", - radius = 300) + radius = 1000) ) testthat::expect_no_error( calc_nlcd(locs = eg_data, @@ -1486,7 +1492,7 @@ testthat::test_that("calc_lagged returns as expected.", { ncp <- data.frame(lon = -78.8277, lat = 35.95013) ncp$site_id <- "3799900018810101" # expect function - expect_true( + testthat::expect_true( is.function(calc_lagged) ) for (l in seq_along(lags)) { @@ -1526,10 +1532,10 @@ testthat::test_that("calc_lagged returns as expected.", { locs_id = "site_id", time_id = "time" ) - expect_identical(narr_lagged, narr_covariate) + testthat::expect_identical(narr_lagged, narr_covariate) } else { # expect error because 2018-01-01 will not have lag data from 2017-12-31 - expect_error( + testthat::expect_error( calc_lagged( from = narr_covariate, date = c("2018-01-01", "2018-01-10"), @@ -1546,13 +1552,13 @@ testthat::test_that("calc_lagged returns as expected.", { time_id = "time" ) # expect output is data.frame - expect_true( + testthat::expect_true( class(narr_lagged) == "data.frame" ) # expect lag day - expect_true(grepl("_[0-9]{1}_", colnames(narr_lagged)[3])) + testthat::expect_true(grepl("_[0-9]{1}$", colnames(narr_lagged)[3])) # expect no NA - expect_true(all(!is.na(narr_lagged))) + testthat::expect_true(all(!is.na(narr_lagged))) } } }) diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 76097c15..868929d7 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -709,7 +709,6 @@ testthat::test_that("SEDAC population data types are coerced.", { year = year, data_format = data_formats[f], data_resolution = data_resolutions[1], - directory_to_download = directory_to_download, directory_to_save = directory_to_save, acknowledgement = TRUE, download = FALSE, @@ -1224,7 +1223,6 @@ testthat::test_that("Test error cases in EPA gaftp sources 2", { remove_command = FALSE, unzip = FALSE, remove_zip = FALSE, - directory_to_download = directory_to_save, epa_certificate_path = certificate ) ) From 0602464bc7b866f05f04466397dc87feafc93a59 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Fri, 31 May 2024 10:14:05 -0400 Subject: [PATCH 39/65] =?UTF-8?q?test=20fix=20-=20@mitchellmanware,=20coul?= =?UTF-8?q?d=20you=20fix=20the=20HMS=20download=20test=3F=20I=20found=20"?= =?UTF-8?q?=E2=94=80=E2=94=80=20Failure:=20NOAA=20HMS=20Smoke=20download?= =?UTF-8?q?=20URLs=20have=20HTTP=20status=20200.=20=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80?= =?UTF-8?q?=E2=94=80=20...=20=3D=3D=203=20is=20not=20TRUE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `actual`: FALSE `expected`: TRUE ", but couldn't figure out the part of creating the subdirectories. Thank you. --- .../testthat/test-manipulate_spacetime_data.R | 237 +++++++++++------- 1 file changed, 152 insertions(+), 85 deletions(-) diff --git a/tests/testthat/test-manipulate_spacetime_data.R b/tests/testthat/test-manipulate_spacetime_data.R index 2e581a5a..ee759f01 100644 --- a/tests/testthat/test-manipulate_spacetime_data.R +++ b/tests/testthat/test-manipulate_spacetime_data.R @@ -1,5 +1,9 @@ +testthat::test_that("check_mysftime works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) -test_that("check_mysftime works as expected", { # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -12,16 +16,16 @@ test_that("check_mysftime works as expected", { ) # should work - expect_no_error(check_mysftime(x = mysft)) + testthat::expect_no_error(check_mysftime(x = mysft)) # check that error messages work well - expect_error(check_mysftime(stdata), "x is not a sftime") + testthat::expect_error(check_mysftime(stdata), "x is not a sftime") mysft <- sftime::st_as_sftime(as.data.frame(stdata), coords = c("lon", "lat"), crs = 4326, time_column_name = "time" ) - expect_error( + testthat::expect_error( check_mysftime(x = mysft), "x is not inherited from a data.table" ) @@ -32,7 +36,9 @@ test_that("check_mysftime works as expected", { crs = 4326, time_column_name = "date" ) - expect_error(check_mysftime(mysft), "time column should be called time") + testthat::expect_error( + check_mysftime(mysft), "time column should be called time" + ) mysft <- stdata |> sftime::st_as_sftime( coords = c("lon", "lat"), @@ -40,7 +46,7 @@ test_that("check_mysftime works as expected", { time_column_name = "time" ) |> dplyr::rename("geom" = "geometry") - expect_error( + testthat::expect_error( check_mysftime(mysft), "geometry column should be called geometry" ) @@ -58,14 +64,18 @@ test_that("check_mysftime works as expected", { for (i in 1:27) { mysft$geometry[i] <- pol } - expect_error( + testthat::expect_error( check_mysftime(mysft), "geometry is not a sfc_POINT" ) }) -test_that("check_mysf works as expected", { +testthat::test_that("check_mysf works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_options(list(sf_use_s2 = FALSE)) + # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -77,15 +87,15 @@ test_that("check_mysf works as expected", { ) # should work - expect_no_error(check_mysf(x = mysf)) + testthat::expect_no_error(check_mysf(x = mysf)) # check that error messages work well - expect_error(check_mysf(stdata), "x is not a sf") + testthat::expect_error(check_mysf(stdata), "x is not a sf") mysf <- sf::st_as_sf(as.data.frame(stdata), coords = c("lon", "lat"), crs = 4326 ) - expect_error( + testthat::expect_error( check_mysf(x = mysf), "x is not inherited from a data.table" ) @@ -95,7 +105,7 @@ test_that("check_mysf works as expected", { crs = 4326 ) |> dplyr::rename("geom" = "geometry") - expect_error( + testthat::expect_error( check_mysf(mysf), "geometry column should be called geometry" ) @@ -112,13 +122,17 @@ test_that("check_mysf works as expected", { for (i in 1:27) { mysf$geometry[i] <- pol } - expect_error( + testthat::expect_error( check_mysf(mysf), "geometry is not a sfc_POINT" ) }) -test_that("rename_time works as expected", { +testthat::test_that("rename_time works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -129,39 +143,39 @@ test_that("rename_time works as expected", { crs = 4326, time_column_name = "time" ) - expect_no_error(rename_time(mysft, "date")) - expect_equal( + testthat::expect_no_error(rename_time(mysft, "date")) + testthat::expect_equal( attributes(rename_time(mysft, "date"))$time_column, "date" ) - expect_error( + testthat::expect_error( rename_time(stdata, "date"), "x is not a sftime" ) }) -test_that("dt_as_mysftime works as expected", { +testthat::test_that("dt_as_mysftime works as expected", { # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), "spacetime_table.csv" )) # should work - expect_no_error(dt_as_mysftime( + testthat::expect_no_error(dt_as_mysftime( x = stdata, lonname = "lon", latname = "lat", timename = "time", crs = 4326 )) - expect_no_error(check_mysftime(dt_as_mysftime( + testthat::expect_no_error(check_mysftime(dt_as_mysftime( x = stdata, lonname = "lon", latname = "lat", timename = "time", crs = 4326 ))) - expect_error( + testthat::expect_error( dt_as_mysftime( x = stdata, lonname = "longitude", @@ -183,7 +197,7 @@ test_that("dt_as_mysftime works as expected", { ) }) -test_that("as_mysftime works as expected", { +testthat::test_that("as_mysftime works as expected", { withr::local_package("terra") withr::local_package("data.table") # open testing data @@ -192,33 +206,33 @@ test_that("as_mysftime works as expected", { "spacetime_table.csv" )) # with data.table - expect_no_error(as_mysftime( + testthat::expect_no_error(as_mysftime( x = stdata, lonname = "lon", latname = "lat", timename = "time", crs = 4326 )) - expect_no_error(check_mysftime(as_mysftime( + testthat::expect_no_error(check_mysftime(as_mysftime( x = stdata, lonname = "lon", latname = "lat", timename = "time", crs = 4326 ))) - expect_error( + testthat::expect_error( as_mysftime(x = stdata), "argument \"lonname\" is missing, with no default" ) # with data.frame - expect_no_error(as_mysftime( + testthat::expect_no_error(as_mysftime( x = as.data.frame(stdata), lonname = "lon", latname = "lat", timename = "time", crs = 4326 )) - expect_no_error(check_mysftime(as_mysftime( + testthat::expect_no_error(check_mysftime(as_mysftime( x = as.data.frame(stdata), lonname = "lon", latname = "lat", @@ -230,19 +244,19 @@ test_that("as_mysftime works as expected", { coords = c("lon", "lat"), crs = 4326 ) - expect_no_error(as_mysftime(mysf, "time")) + testthat::expect_no_error(as_mysftime(mysf, "time")) b <- mysf |> dplyr::rename("date" = "time") - expect_no_error(as_mysftime(b, "date")) - expect_no_error(check_mysftime(as_mysftime(b, "date"))) + testthat::expect_no_error(as_mysftime(b, "date")) + testthat::expect_no_error(check_mysftime(as_mysftime(b, "date"))) # with sftime mysft <- sftime::st_as_sftime(stdata, coords = c("lon", "lat"), crs = 4326, time_column_name = "time" ) - expect_no_error(as_mysftime(mysft, "time")) - expect_no_error(check_mysftime(as_mysftime(mysft, "time"))) + testthat::expect_no_error(as_mysftime(mysft, "time")) + testthat::expect_no_error(check_mysftime(as_mysftime(mysft, "time"))) # with SpatRaster myrast <- terra::rast( @@ -254,8 +268,10 @@ test_that("as_mysftime works as expected", { terra::values(myrast) <- seq(-5, 19) terra::add(myrast) <- c(myrast**2, myrast**3) names(myrast) <- c("2023-11-01", "2023-11-02", "2023-11-03") - expect_no_error(as_mysftime(x = myrast, varname = "altitude")) - expect_no_error(check_mysftime(as_mysftime(x = myrast, varname = "altitude"))) + testthat::expect_no_error(as_mysftime(x = myrast, varname = "altitude")) + testthat::expect_no_error( + check_mysftime(as_mysftime(x = myrast, varname = "altitude")) + ) # with SpatVector myvect <- terra::vect( stdata, @@ -263,8 +279,8 @@ test_that("as_mysftime works as expected", { crs = "EPSG:4326", keepgeom = FALSE ) - expect_no_error(as_mysftime(x = myvect)) - expect_no_error(check_mysftime(as_mysftime(x = myvect))) + testthat::expect_no_error(as_mysftime(x = myvect)) + testthat::expect_no_error(check_mysftime(as_mysftime(x = myvect))) myvect <- stdata |> dplyr::rename("time2" = time) |> terra::vect( @@ -272,7 +288,7 @@ test_that("as_mysftime works as expected", { crs = "EPSG:4326", keepgeom = FALSE ) - expect_error( + testthat::expect_error( as_mysftime(x = myvect), "timename column missing or mispelled" ) @@ -306,15 +322,15 @@ test_that("as_mysftime works as expected", { names(var2) <- c("2023-11-01", "2023-11-02", "2023-11-03") myrds <- terra::sds(var1, var2) names(myrds) <- c("var1", "var2") - expect_no_error(as_mysftime(myrds)) - expect_error( + testthat::expect_no_error(as_mysftime(myrds)) + testthat::expect_error( as_mysftime(x = "roquefort"), "x class not accepted" ) }) -test_that("sftime_as_spatvector as expected", { +testthat::test_that("sftime_as_spatvector as expected", { # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -325,32 +341,44 @@ test_that("sftime_as_spatvector as expected", { time_column_name = "time", crs = 4326 ) - expect_no_error(sftime_as_spatvector(mysftime)) + testthat::expect_no_error(sftime_as_spatvector(mysftime)) # with a different time column name: attributes(mysftime)$time_column <- "date" mysftime <- dplyr::rename(mysftime, "date" = "time") - expect_no_error(sftime_as_spatvector(mysftime)) + testthat::expect_no_error(sftime_as_spatvector(mysftime)) # doesn't work with other classes: - expect_error(sftime_as_spatvector(stdata)) + testthat::expect_error(sftime_as_spatvector(stdata)) }) -test_that("sf_as_mysftime works as expected", { +testthat::test_that("sf_as_mysftime works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) + # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), "spacetime_table.csv" )) mysf <- sf::st_as_sf(stdata, coords = c("lon", "lat"), crs = 4326) - expect_no_error(sf_as_mysftime(mysf, "time")) - expect_no_error(check_mysftime(sf_as_mysftime(mysf, "time"))) + testthat::expect_no_error(sf_as_mysftime(mysf, "time")) + testthat::expect_no_error(check_mysftime(sf_as_mysftime(mysf, "time"))) b <- mysf |> dplyr::rename("date" = "time") - expect_no_error(check_mysftime(sf_as_mysftime(b, "date"))) - expect_error(sf_as_mysftime(b, "time"), - "time column missing or mispelled") + testthat::expect_no_error(check_mysftime(sf_as_mysftime(b, "date"))) + testthat::expect_error( + sf_as_mysftime(b, "time"), + "time column missing or mispelled" + ) }) -test_that("sftime_as_mysftime works as expected", { +testthat::test_that("sftime_as_mysftime works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) + # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -361,18 +389,25 @@ test_that("sftime_as_mysftime works as expected", { time_column_name = "time", crs = 4326 ) - expect_no_error(sftime_as_mysftime(mysft, "time")) - expect_no_error(check_mysftime(sftime_as_mysftime(mysft, "time"))) - expect_error(sftime_as_mysftime(mysft, "date")) + testthat::expect_no_error(sftime_as_mysftime(mysft, "time")) + testthat::expect_no_error( + check_mysftime(sftime_as_mysftime(mysft, "time")) + ) + testthat::expect_error(sftime_as_mysftime(mysft, "date")) attributes(mysft)$time_column <- "date" mysft <- dplyr::rename(mysft, "date" = "time") - expect_no_error(check_mysftime(sf_as_mysftime(mysft, "date"))) - expect_error(sf_as_mysftime(mysft, "time"), - "time column missing or mispelled") + testthat::expect_no_error(check_mysftime(sf_as_mysftime(mysft, "date"))) + testthat::expect_error( + sf_as_mysftime(mysft, "time"), + "time column missing or mispelled" + ) }) -test_that("spatraster_as_sftime works as expected", { +testthat::test_that("spatraster_as_sftime works as expected", { + withr::local_package("terra") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) myrast <- terra::rast( extent = c(-112, -101, 33.5, 40.9), @@ -384,20 +419,23 @@ test_that("spatraster_as_sftime works as expected", { terra::add(myrast) <- c(myrast**2, myrast**3) names(myrast) <- c("2023-11-01", "2023-11-02", "2023-11-03") # conversion should work - expect_no_error(spatraster_as_sftime(myrast, "myvar")) - expect_no_error(spatraster_as_sftime(myrast, "myvar", "date")) + testthat::expect_no_error(spatraster_as_sftime(myrast, "myvar")) + testthat::expect_no_error(spatraster_as_sftime(myrast, "myvar", "date")) mysft <- spatraster_as_sftime(myrast, "myvar", "date") - expect_equal(attributes(mysft)$time, "date") + testthat::expect_equal(attributes(mysft)$time, "date") # conversion does not work because raster's names are not dates names(myrast) <- c("roquefort", "comte", "camembert") - expect_error( + testthat::expect_error( spatraster_as_sftime(myrast, "myvar"), "x layers might not be time" ) }) -test_that("spatrds_as_sftime works as expected", { +testthat::test_that("spatrds_as_sftime works as expected", { + withr::local_package("terra") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) var1 <- terra::rast( extent = c(-112, -101, 33.5, 40.9), @@ -428,13 +466,19 @@ test_that("spatrds_as_sftime works as expected", { myrds <- terra::sds(var1, var2) names(myrds) <- c("var1", "var2") # conversion should work - expect_no_error(spatrds_as_sftime(myrds, "time")) + testthat::expect_no_error(spatrds_as_sftime(myrds, "time")) mysft <- spatrds_as_sftime(myrds, "date") - expect_equal(attributes(mysft)$time, "date") + testthat::expect_equal(attributes(mysft)$time, "date") }) -test_that("sftime_as_sf works as expected", { +testthat::test_that("sftime_as_sf works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) + + # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -445,15 +489,26 @@ test_that("sftime_as_sf works as expected", { time_column_name = "time", crs = 4326 ) - expect_no_error(sftime_as_sf(mysftime)) - expect_no_error(sftime_as_sf(mysftime, keeptime = FALSE)) - expect_equal(class(sftime_as_sf(mysftime))[1], "sf") - expect_equal(class(sftime_as_sf(mysftime, keeptime = FALSE))[1], "sf") - expect_true("time" %in% colnames(sftime_as_sf(mysftime, keeptime = TRUE))) - expect_false("time" %in% colnames(sftime_as_sf(mysftime, keeptime = FALSE))) + testthat::expect_no_error(sftime_as_sf(mysftime)) + testthat::expect_no_error(sftime_as_sf(mysftime, keeptime = FALSE)) + testthat::expect_equal(class(sftime_as_sf(mysftime))[1], "sf") + testthat::expect_equal( + class(sftime_as_sf(mysftime, keeptime = FALSE))[1], "sf" + ) + testthat::expect_true( + "time" %in% colnames(sftime_as_sf(mysftime, keeptime = TRUE)) + ) + testthat::expect_false( + "time" %in% colnames(sftime_as_sf(mysftime, keeptime = FALSE)) + ) }) -test_that("sftime_as_sf works as expected", { +testthat::test_that("sftime_as_sf works as expected", { + withr::local_package("data.table") + withr::local_package("sf") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) + # open testing data stdata <- data.table::fread(paste0( testthat::test_path("..", "testdata/", ""), @@ -464,16 +519,25 @@ test_that("sftime_as_sf works as expected", { time_column_name = "time", crs = 4326 ) - expect_no_error(sftime_as_sf(mysftime)) - expect_no_error(sftime_as_sf(mysftime, keeptime = FALSE)) - expect_equal(class(sftime_as_sf(mysftime))[1], "sf") - expect_equal(class(sftime_as_sf(mysftime, keeptime = FALSE))[1], "sf") - expect_true("time" %in% colnames(sftime_as_sf(mysftime, keeptime = TRUE))) - expect_false("time" %in% colnames(sftime_as_sf(mysftime, keeptime = FALSE))) + testthat::expect_no_error(sftime_as_sf(mysftime)) + testthat::expect_no_error(sftime_as_sf(mysftime, keeptime = FALSE)) + testthat::expect_equal(class(sftime_as_sf(mysftime))[1], "sf") + testthat::expect_equal( + class(sftime_as_sf(mysftime, keeptime = FALSE))[1], "sf" + ) + testthat::expect_true( + "time" %in% colnames(sftime_as_sf(mysftime, keeptime = TRUE)) + ) + testthat::expect_false( + "time" %in% colnames(sftime_as_sf(mysftime, keeptime = FALSE)) + ) }) -test_that("sftime_as_spatraster works as expected", { +testthat::test_that("sftime_as_spatraster works as expected", { + withr::local_package("terra") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) myrast <- terra::rast( extent = c(-112, -101, 33.5, 40.9), @@ -485,15 +549,18 @@ test_that("sftime_as_spatraster works as expected", { terra::add(myrast) <- c(myrast**2, myrast**3) names(myrast) <- c("2023-11-01", "2023-11-02", "2023-11-03") mysftime <- as_mysftime(myrast, varname = "roquefort") - expect_no_error(sftime_as_spatraster(mysftime, "roquefort")) - expect_error( + testthat::expect_no_error(sftime_as_spatraster(mysftime, "roquefort")) + testthat::expect_error( sftime_as_spatraster(mysftime, "cheddar"), "varname missing or mispelled" ) }) -test_that("sftime_as_spatrds works as expected", { +testthat::test_that("sftime_as_spatrds works as expected", { + withr::local_package("terra") + withr::local_package("sftime") + withr::local_options(list(sf_use_s2 = FALSE)) var1 <- terra::rast( extent = c(-112, -101, 33.5, 40.9), @@ -526,8 +593,8 @@ test_that("sftime_as_spatrds works as expected", { # create a structured sftime mysft <- spatrds_as_sftime(myrds, "time") # conversion should work - expect_no_error(sftime_as_spatrds(mysft)) - expect_error(sftime_as_spatrds("hello"), "x is not a sftime") + testthat::expect_no_error(sftime_as_spatrds(mysft)) + testthat::expect_error(sftime_as_spatrds("hello"), "x is not a sftime") rename_time(mysft, "date") - expect_no_error(sftime_as_spatrds(mysft)) + testthat::expect_no_error(sftime_as_spatrds(mysft)) }) From f49641e5dd0f0148261577672a9850a148cee349 Mon Sep 17 00:00:00 2001 From: mitchellmanware Date: Fri, 31 May 2024 10:52:21 -0400 Subject: [PATCH 40/65] hms test fix --- tests/testthat/test-download_functions.R | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 868929d7..fba47c1e 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -459,12 +459,17 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { gsub("-", "", date_end), "_curl_commands.txt") # expect sub-directories to be created + if (data_formats[d] == "Shapefile") { + expected_folders <- 3 + } else { + expected_folders <- 2 + } testthat::expect_true( length( list.files( directory_to_save, include.dirs = TRUE ) - ) == 3 + ) == expected_folders ) # import commands commands <- read_commands(commands_path = commands_path) From 96c0ddfed9ba55c080528679a16aab243c2a90cb Mon Sep 17 00:00:00 2001 From: Insang Song Date: Fri, 31 May 2024 11:27:30 -0400 Subject: [PATCH 41/65] Vignette test error fix - Due to the deprecation of `directory_to_download` argument in download functions --- tests/testthat/test-process.R | 1 + vignettes/download_functions.Rmd | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index c156c1cd..8100af02 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -1511,6 +1511,7 @@ testthat::test_that( testthat::test_that( "process_cropscape returns a SpatRaster object with correct metadata", { # Set up test data + withr::local_package("terra") filepath <- testthat::test_path("..", "testdata/cropscape/cdl_30m_r_nc_2019_sub.tif") dirpath <- testthat::test_path("..", "testdata/cropscape") diff --git a/vignettes/download_functions.Rmd b/vignettes/download_functions.Rmd index 9b35156b..f180a70a 100644 --- a/vignettes/download_functions.Rmd +++ b/vignettes/download_functions.Rmd @@ -545,14 +545,13 @@ testthat::test_that( # parameters test_start <- "2023-12-28" test_end <- "2024-01-02" - test_directory <- "./data" + test_directory <- "./data/" # download download_data( dataset_name = "noaa", date_start = test_start, date_end = test_end, data_format = "Shapefile", - directory_to_download = test_directory, directory_to_save = test_directory, acknowledgement = TRUE, download = FALSE, @@ -593,7 +592,6 @@ testthat::test_that( date_start = test_start, date_end = test_end, data_format = "Shapefile", - directory_to_download = test_directory, directory_to_save = test_directory, acknowledgement = TRUE, download = FALSE, @@ -684,6 +682,9 @@ testthat::test_that( ) ``` +```{r, echo = FALSE, include = FALSE} +file.remove(commands_txt) +``` As expected, the test passes because the NOAA HMS Smoke dataset does not contain data for January 1-2, 1800. @@ -1001,7 +1002,6 @@ download_data( year = "2020", data_format = "GeoTIFF", data_resolution = "60 minute", - directory_to_download = "./sedac_population/", directory_to_save = "./sedac_population", acknowledgement = TRUE, download = TRUE, From ba71653c2d6054817b8ad2a976b1bed01feea5dc Mon Sep 17 00:00:00 2001 From: Insang Song Date: Fri, 31 May 2024 14:43:24 -0400 Subject: [PATCH 42/65] Minor change in docs - All tests passed locally --- R/calculate_covariates.R | 1 + R/process.R | 3 +++ man/process_ecoregion.Rd | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index f8cd9d11..c73814a0 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -394,6 +394,7 @@ calc_nlcd <- function(from, nlcd_classes$class[match(nlcd_names, nlcd_classes[[class_query]])] new_names <- sprintf("LDU_%s_0_%05d", nlcd_names, radius) names(nlcd_at_bufs) <- new_names + # merge locs_df with nlcd class fractions new_data_vect <- cbind(locs_df, as.integer(year), nlcd_at_bufs) if (geom) { diff --git a/R/process.R b/R/process.R index 1b000fa5..6837bb69 100644 --- a/R/process.R +++ b/R/process.R @@ -732,6 +732,9 @@ process_nlcd <- #' data, returning a `SpatVector` object. #' @param path character(1). Path to Ecoregion Shapefiles #' @param ... Placeholders. +#' @note The function will fix Tukey's bridge in Portland, ME. +#' This fix will ensure that the EPA air quality monitoring sites +#' will be located within the ecoregion. #' @author Insang Song #' @returns a `SpatVector` object #' @importFrom terra vect diff --git a/man/process_ecoregion.Rd b/man/process_ecoregion.Rd index b49d1c79..dc74c786 100644 --- a/man/process_ecoregion.Rd +++ b/man/process_ecoregion.Rd @@ -18,6 +18,11 @@ a \code{SpatVector} object The \code{\link{process_ecoregion}} function imports and cleans raw ecoregion data, returning a \code{SpatVector} object. } +\note{ +The function will fix Tukey's bridge in Portland, ME. +This fix will ensure that the EPA air quality monitoring sites +will be located within the ecoregion. +} \author{ Insang Song } From 67aefd326c2142b904ef402c3b973ec2d2cd5332 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Sun, 2 Jun 2024 19:24:33 -0400 Subject: [PATCH 43/65] download_aqs fix - writeLines to cat --- R/download.R | 6 +++--- R/process.R | 2 +- tests/testthat/test-download_functions.R | 16 ++++++---------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/R/download.R b/R/download.R index aa74b819..3753f00e 100644 --- a/R/download.R +++ b/R/download.R @@ -177,7 +177,7 @@ download_aqs <- resolution_temporal, "_", parameter_code, - "_%.0f.zip", + "_%d.zip", sep = "" ), year_sequence @@ -196,7 +196,7 @@ download_aqs <- resolution_temporal, "_", parameter_code, - "_%.0f.zip", + "_%d.zip", sep = "" ), year_sequence @@ -228,7 +228,7 @@ download_aqs <- ) download_sink(commands_txt) #### 8. concatenate and print download commands to "..._curl_commands.txt" - writeLines(download_commands) + cat(download_commands) #### 9. finish "..._curl_commands.txt" file sink() #### 10. build system command diff --git a/R/process.R b/R/process.R index 6837bb69..26f46212 100644 --- a/R/process.R +++ b/R/process.R @@ -2547,7 +2547,7 @@ process_terraclimate <- function( #' @param layer_name character(1). Layer name in the `path` #' @param huc_level character(1). Field name of HUC level #' @param huc_header character(1). The upper level HUC code header to extract -#' lower level HUCs. +#' lower level HUCs. #' @param ... Arguments passed to `nhdplusTools::get_huc()` #' @returns a `SpatVector` object #' @seealso [`nhdplusTools::get_huc`] diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index fba47c1e..c94cb29e 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -75,7 +75,8 @@ testthat::test_that("Errors when temporal ranges invalid.", { date_start = "1900-01-01", collection = "inst1_2d_asm_Nx", directory_to_save = testthat::test_path("..", "testdata/", ""), - acknowledgement = TRUE + acknowledgement = TRUE, + remove_command = TRUE ) ) expect_error( @@ -237,7 +238,7 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 2) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 20L, method = "HEAD") + url_status <- check_urls(urls = urls, size = 10L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, @@ -422,7 +423,7 @@ testthat::test_that("NARR p-levels download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 6) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 20L, method = "HEAD") + url_status <- check_urls(urls = urls, size = 10L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, @@ -476,7 +477,7 @@ testthat::test_that("NOAA HMS Smoke download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 6) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 3L, method = "HEAD") + url_status <- check_urls(urls = urls, size = 10L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, @@ -741,8 +742,6 @@ testthat::test_that("SEDAC population data types are coerced.", { url_status = url_status) # remove file with commands after test file.remove(commands_path) - # remove file with commands after test - unlink(directory_to_save, recursive = TRUE) } }) @@ -1252,7 +1251,7 @@ testthat::test_that("epa certificate", { testthat::expect_error( download_epa_certificate("file.txt") ) - testthat::expect_message( + testthat::expect_no_error( download_epa_certificate(file.path(tempdir(), "file.pem")) ) testthat::expect_no_error( @@ -1451,8 +1450,6 @@ testthat::test_that("terraclimate error with invalid variables", { }) - - testthat::test_that("download_cropscape throws an error for invalid year", { # Set up test data invalid_year <- 1996 @@ -1648,7 +1645,6 @@ testthat::test_that("download_prism downloads the correct data files", { }) - testthat::test_that("list_stac_files returns a character vector of file links", { withr::local_package("rstac") # Set up test data From 188238a0f701189f6ca0481bbfb08c1e118cb410 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 3 Jun 2024 13:52:19 -0400 Subject: [PATCH 44/65] Download function update - Koppen-Geiger refers to the direct file link - check_url_status: added 206 as a valid value since Koppen-Geiger S3 link only sends 206 Partial Content - AQS test: pick only one link - GET to HEAD for http tests --- R/download.R | 4 ++-- R/download_auxiliary.R | 8 ++++---- man/check_url_status.Rd | 2 +- tests/testthat/test-download_functions.R | 12 +++--------- tests/testthat/test-process.R | 1 - 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/R/download.R b/R/download.R index 3753f00e..67024e9b 100644 --- a/R/download.R +++ b/R/download.R @@ -1994,7 +1994,7 @@ download_koppen_geiger <- function( #### 7. define data resolution data_resolution <- gsub("\\.", "p", data_resolution) #### 8 define download URL - download_url <- "https://figshare.com/ndownloader/files/12407516" + download_url <- "https://s3-eu-west-1.amazonaws.com/pfigshare-u-files/12407516/Beck_KG_V1.zip" #### 9 build download file name download_name <- paste0( directory_to_download, @@ -2081,7 +2081,7 @@ download_koppen_geiger <- function( download_name = download_name ) if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) + unlink(directory_to_save, recursive = TRUE) } } diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index ba425465..2826e5db 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -318,7 +318,7 @@ generate_time_sequence <- #' Check HTTP status #' @description -#' Check if provided URL returns HTTP status 200. +#' Check if provided URL returns HTTP status 200 or 206. #' @param url Download URL to be checked. #' @param method httr method to obtain URL (`"HEAD"` or `"GET"`) #' @author Insang Song; Mitchell Manware @@ -331,15 +331,15 @@ check_url_status <- function( url, method = c("HEAD", "GET")) { method <- match.arg(method) - http_status_ok <- 200 + http_status_ok <- c(200, 206) if (method == "HEAD") { hd <- httr::HEAD(url) } else if (method == "GET") { hd <- httr::GET(url) } status <- hd$status_code - Sys.sleep(1.5) - return(status == http_status_ok) + Sys.sleep(1) + return(status %in% http_status_ok) } #' Import download commands diff --git a/man/check_url_status.Rd b/man/check_url_status.Rd index 94c64901..23cd3577 100644 --- a/man/check_url_status.Rd +++ b/man/check_url_status.Rd @@ -15,7 +15,7 @@ check_url_status(url, method = c("HEAD", "GET")) logical object } \description{ -Check if provided URL returns HTTP status 200. +Check if provided URL returns HTTP status 200 or 206. } \author{ Insang Song; Mitchell Manware diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index c94cb29e..02ae3071 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -148,15 +148,13 @@ testthat::test_that("EPA AQS download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 2) # check HTTP URL status - url_status <- check_urls(urls = urls, size = length(urls), method = "HEAD") + url_status <- check_urls(urls = urls, size = 1L, method = "HEAD") # implement unit tets test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) - # remove temporary aqs - unlink(directory_to_save, recursive = TRUE) }) @@ -789,15 +787,13 @@ testthat::test_that("Koppen Geiger download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 2) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 1L, method = "GET") + url_status <- check_urls(urls = urls, size = 1L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) # remove file with commands after test file.remove(commands_path) - # remove temporary kop - unlink(directory_to_save, recursive = TRUE) } } }) @@ -844,7 +840,7 @@ testthat::test_that("MODIS-MOD09GA download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 4) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 10L, method = "SKIP") + url_status <- check_urls(urls = urls, size = 3L, method = "SKIP") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, @@ -1046,7 +1042,6 @@ testthat::test_that("MODIS download error cases.", { remove_command = FALSE) ) - # define file path with commands commands_path <- paste0( directory_to_save, @@ -1072,7 +1067,6 @@ testthat::test_that("MODIS download error cases.", { }) - testthat::test_that("EPA TRI download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 8100af02..98a37f91 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -509,7 +509,6 @@ testthat::test_that("process_nei tests", { }) - ## ephemeral: process_conformity tests testthat::test_that("process_conformity tests", { withr::local_package("terra") From 8a7d71fd990b9f51b1421933aba7c979086cb307 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 3 Jun 2024 14:09:33 -0400 Subject: [PATCH 45/65] =?UTF-8?q?workflow=20to=20print=20errors=20-=20?= =?UTF-8?q?=F0=9F=A4=B7=F0=9F=8F=BB=E2=80=8D=E2=99=82=EF=B8=8F=20local=20t?= =?UTF-8?q?ests=20passed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-coverage-local.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 226ffe89..415304ac 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -40,8 +40,8 @@ jobs: - name: Test coverage run: > Rscript -e - "covd<-covr::coverage_to_list()$totalcoverage; - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)" + "tryCatch({covd<-covr::coverage_to_list()$totalcoverage; + write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) print(e))" shell: bash - name: Get Values From 2c401d18d636609178130343dc72b7a98b5860ff Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 3 Jun 2024 14:56:30 -0400 Subject: [PATCH 46/65] download patch - extract_urls: added trimws to remove leading blanks - workflow yaml to cat all error messages --- .github/workflows/test-coverage-local.yaml | 2 +- R/download.R | 2 +- R/download_auxiliary.R | 2 +- tests/testthat/test-download_functions.R | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 415304ac..1476ee3c 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -41,7 +41,7 @@ jobs: run: > Rscript -e "tryCatch({covd<-covr::coverage_to_list()$totalcoverage; - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) print(e))" + write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) stop(cat(e)))" shell: bash - name: Get Values diff --git a/R/download.R b/R/download.R index 67024e9b..7d73ec76 100644 --- a/R/download.R +++ b/R/download.R @@ -203,7 +203,7 @@ download_aqs <- ) #### 6. build download command download_commands <- paste0( - "curl ", + "curl -s --url ", download_urls, " --output ", download_names, diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index 2826e5db..e2da75c6 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -374,7 +374,7 @@ extract_urls <- function( } urls <- sapply( strsplit( - commands, + trimws(commands), " " ), function(x, l) x[l], diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 02ae3071..2df31799 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -146,7 +146,7 @@ testthat::test_that("EPA AQS download URLs have HTTP status 200.", { # import commands commands <- read_commands(commands_path = commands_path) # extract urls - urls <- extract_urls(commands = commands, position = 2) + urls <- extract_urls(commands = commands, position = 4) # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "HEAD") # implement unit tets From 53bff7bff2f98a6b0457541160b1027d29ff6dc1 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 3 Jun 2024 15:05:19 -0400 Subject: [PATCH 47/65] ad hoc patch - setup-r-dependencies error --- .github/workflows/test-coverage-local.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 1476ee3c..876fdb3a 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -24,8 +24,10 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::covr - needs: coverage + extra-packages: | + any::pak + any::covr + needs: coverage - name: Cache C++ and R dependencies uses: actions/cache@v2 From efc92c1621d387adc55d69467497b99c77c2be33 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Mon, 3 Jun 2024 15:51:41 -0400 Subject: [PATCH 48/65] yaml fix --- .github/workflows/test-coverage-local.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 876fdb3a..77c9d64d 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -43,7 +43,7 @@ jobs: run: > Rscript -e "tryCatch({covd<-covr::coverage_to_list()$totalcoverage; - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) stop(cat(e)))" + write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) stop(print(e)))" shell: bash - name: Get Values From ad092fb4d5a7e7b4ab032f19d1a5934671fd0fac Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 08:40:14 -0400 Subject: [PATCH 49/65] printing testcov results --- .github/workflows/test-coverage-local.yaml | 7 +++++++ R/process.R | 6 ++---- tests/testthat/test-process.R | 10 ++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 77c9d64d..4f9dd22a 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -46,6 +46,13 @@ jobs: write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) stop(print(e)))" shell: bash + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + - name: Get Values id: get-values shell: bash diff --git a/R/process.R b/R/process.R index 26f46212..4f73d27c 100644 --- a/R/process.R +++ b/R/process.R @@ -2589,14 +2589,12 @@ process_huc <- huc_header = NULL, ... ) { - if (!file.exists(path) && !dir.exists(path)) { + if (missing(path) || (!file.exists(path) && !dir.exists(path))) { hucpoly <- try( rlang::inject(nhdplusTools::get_huc(!!!list(...))) ) if (inherits(hucpoly, "try-error")) { - stop( - "HUC data was not found." - ) + stop("HUC data was not found.") } hucpoly <- terra::vect(hucpoly) } diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 98a37f91..04455555 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -1583,12 +1583,13 @@ testthat::test_that("process_huc", ) ) - # Set up test data - path <- file.path(path, "..") + path2 <- testthat::test_path( + "..", "testdata", "huc12" + ) # Call the function and expect an error - testthat::expect_error(process_huc(path)) + testthat::expect_error(process_huc(path2)) # using nhdplusTools testthat::expect_no_error( test3 <- process_huc( @@ -1597,7 +1598,8 @@ testthat::test_that("process_huc", huc_level = NULL, huc_header = NULL, id = "030202", - type = "huc06" + type = "huc06", + t_srs = "EPSG:5070" ) ) testthat::expect_s4_class(test3, "SpatVector") From 1d48f65c9c4ead4999903406b147c34407b36e15 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 09:19:51 -0400 Subject: [PATCH 50/65] yaml edit - test coverage error printing -- 2nd try --- .github/workflows/test-coverage-local.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 4f9dd22a..335f16cb 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -40,10 +40,16 @@ jobs: dependencies-${{ runner.os }}- - name: Test coverage - run: > - Rscript -e - "tryCatch({covd<-covr::coverage_to_list()$totalcoverage; - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, error=function(e) stop(print(e)))" + run: | + Rscript -e \ + "tryCatch({ \ + sink('${{ runner.temp }}/package/testthat.Rout.res'); \ + cov <- covr::package_coverage(); \ + sink(); \ + covd <- covr::coverage_to_list(cov)$totalcoverage; \ + write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)},; \ + error=function(e) stop(print(e))) \ + " shell: bash - name: Show testthat output From 724d0504c8551a8ee426d7ade9b275fb651d4d52 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 09:24:52 -0400 Subject: [PATCH 51/65] yaml fix 3 --- .github/workflows/test-coverage-local.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 335f16cb..5836dd51 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -43,11 +43,11 @@ jobs: run: | Rscript -e \ "tryCatch({ \ - sink('${{ runner.temp }}/package/testthat.Rout.res'); \ - cov <- covr::package_coverage(); \ - sink(); \ - covd <- covr::coverage_to_list(cov)$totalcoverage; \ - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)},; \ + sink('${{ runner.temp }}/package/testthat.Rout.res') \ + cov <- covr::package_coverage() \ + sink() \ + covd <- covr::coverage_to_list(cov)$totalcoverage \ + write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, \ error=function(e) stop(print(e))) \ " shell: bash From b9bf675ffc5363f33e63f8cbe93fbb7c576b182a Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 09:39:04 -0400 Subject: [PATCH 52/65] yaml + R script for GH Action --- .github/workflows/test-coverage-local.yaml | 10 +--------- .github/workflows/test-coverage.R | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/test-coverage.R diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 5836dd51..a79be4dd 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -41,15 +41,7 @@ jobs: - name: Test coverage run: | - Rscript -e \ - "tryCatch({ \ - sink('${{ runner.temp }}/package/testthat.Rout.res') \ - cov <- covr::package_coverage() \ - sink() \ - covd <- covr::coverage_to_list(cov)$totalcoverage \ - write.table(covd[length(covd)], file = '${{ github.workspace }}/local_cov.Rout', row.names = F, col.names = F)}, \ - error=function(e) stop(print(e))) \ - " + Rscript ${{ github.workspace }}/.github/workflows/test-coverage.R ${{ runner.temp }} ${{ github.workspace }} shell: bash - name: Show testthat output diff --git a/.github/workflows/test-coverage.R b/.github/workflows/test-coverage.R new file mode 100644 index 00000000..cfaeb6c3 --- /dev/null +++ b/.github/workflows/test-coverage.R @@ -0,0 +1,14 @@ +args <- commandArgs(trailingOnly = TRUE) +runnertemp <- args[[1]] +ghworkspace <- args[[2]] + +sink(paste0(runnertemp, '/package/testthat.Rout.res')) +cov <- covr::package_coverage() +sink() +covd <- covr::coverage_to_list(cov)$totalcoverage +write.table( + covd[length(covd)], + file = file.path(ghworkspace, 'local_cov.Rout'), + row.names = FALSE, + col.names = FALSE +) From ac2af2e2e54c873e5a5b247ed277bec8651ffc5e Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 09:51:02 -0400 Subject: [PATCH 53/65] create runner temp dir --- .github/workflows/test-coverage.R | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-coverage.R b/.github/workflows/test-coverage.R index cfaeb6c3..c6f96686 100644 --- a/.github/workflows/test-coverage.R +++ b/.github/workflows/test-coverage.R @@ -2,6 +2,7 @@ args <- commandArgs(trailingOnly = TRUE) runnertemp <- args[[1]] ghworkspace <- args[[2]] +dir.create(runnertemp, showWarnings = FALSE, recursive = TRUE) sink(paste0(runnertemp, '/package/testthat.Rout.res')) cov <- covr::package_coverage() sink() From 98c69a440475d3032ea6c93d99ef0d2f1ad7f98e Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 10:00:16 -0400 Subject: [PATCH 54/65] yaml: create dir in runner temp --- .github/workflows/test-coverage.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-coverage.R b/.github/workflows/test-coverage.R index c6f96686..e1403ba0 100644 --- a/.github/workflows/test-coverage.R +++ b/.github/workflows/test-coverage.R @@ -2,7 +2,7 @@ args <- commandArgs(trailingOnly = TRUE) runnertemp <- args[[1]] ghworkspace <- args[[2]] -dir.create(runnertemp, showWarnings = FALSE, recursive = TRUE) +dir.create(file.path(runnertemp, "package"), showWarnings = FALSE, recursive = TRUE) sink(paste0(runnertemp, '/package/testthat.Rout.res')) cov <- covr::package_coverage() sink() From 71f1eeebd68dbb5477ee75f73a24e8cf4ce0c2c5 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 10:46:42 -0400 Subject: [PATCH 55/65] yaml tweak - pkgdown.yaml: test coverage html - color code for coverage rate: green/orange to green/yellow --- .github/workflows/pkgdown.yaml | 31 +++++++++++++++++++++- .github/workflows/test-coverage-local.yaml | 2 +- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index eb333254..3fb73f25 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -12,7 +12,30 @@ on: name: pkgdown jobs: + test-covr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + + - name: Install dependencies + run: | + install.packages(c("covr", "testthat")) + install.packages(".", repos = NULL, type = "source") + + - name: Run tests and generate covr report + run: | + covr::report(file = "coverage_report.html") + + - name: Upload covr report as artifact + uses: actions/upload-artifact@v4 + with: + name: covr-report + path: coverage_report.html + pkgdown: + needs: test-covr runs-on: ubuntu-latest # Only restrict concurrency for non-PR jobs concurrency: @@ -24,6 +47,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Download covr report + uses: actions/download-artifact@v4 + with: + name: covr-report + path: . + - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 @@ -41,7 +70,7 @@ jobs: - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.4.1 + uses: JamesIves/github-pages-deploy-action@v4.6.1 with: clean: false branch: gh-pages diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index a79be4dd..0e76c247 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -93,7 +93,7 @@ jobs: run: | npm i -g badgen-cli export COV=${{ steps.get-values.outputs.coverage }} - COLOR=$(node -p '+process.env.COV >= 95 ? `green` : `orange`') + COLOR=$(node -p '+process.env.COV >= 95 ? `green` : `yellow`') mkdir -p badges badgen -j coverage -s $COV% -c $COLOR > badges/coverage.svg From 25aa2bb5a1f68a666890baf41335fe1623104b00 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 11:13:57 -0400 Subject: [PATCH 56/65] Test fix + yaml change - pkgdown: coverage inclusion test (2nd try) - nhdplusTools memoisation led to fail in GH Action: temporarily excluding from code coverage - rollback calc_lagged tests --- .github/workflows/pkgdown.yaml | 23 +++++++++++++++++----- .github/workflows/test-coverage-local.yaml | 2 +- R/process.R | 3 +++ tests/testthat/test-calculate_covariates.R | 9 +++++---- tests/testthat/test-process.R | 13 ------------ 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 3fb73f25..a3d4a411 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -19,14 +19,27 @@ jobs: - uses: r-lib/actions/setup-r@v2 - - name: Install dependencies - run: | - install.packages(c("covr", "testthat")) - install.packages(".", repos = NULL, type = "source") + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + any::pak + any::covr + any::devtools + needs: coverage + + - name: Cache C++ and R dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/R + ~/.local/share/R + key: dependencies-${{ runner.os }}-${{ hashFiles('**/DESCRIPTION') }} + restore-keys: | + dependencies-${{ runner.os }}- - name: Run tests and generate covr report run: | - covr::report(file = "coverage_report.html") + Rscript -e 'covr::report(file = "coverage_report.html")' - name: Upload covr report as artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index 0e76c247..c4fce4d6 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -30,7 +30,7 @@ jobs: needs: coverage - name: Cache C++ and R dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.cache/R diff --git a/R/process.R b/R/process.R index 4f73d27c..c606ea44 100644 --- a/R/process.R +++ b/R/process.R @@ -2589,6 +2589,8 @@ process_huc <- huc_header = NULL, ... ) { + # exclude the coverage due to write permission related to memoization + #nocov start if (missing(path) || (!file.exists(path) && !dir.exists(path))) { hucpoly <- try( rlang::inject(nhdplusTools::get_huc(!!!list(...))) @@ -2598,6 +2600,7 @@ process_huc <- } hucpoly <- terra::vect(hucpoly) } + #nocov end if (file.exists(path) || dir.exists(path)) { if (!is.null(huc_header)) { querybase <- diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 113090a0..0acfa958 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -182,10 +182,11 @@ testthat::test_that("calc_modis works well.", { withr::local_package("terra") withr::local_package("stars") withr::local_package("lwgeom") - withr::local_package("foreach") - withr::local_package("doParallel") withr::local_options( - list(sf_use_s2 = FALSE) + list( + sf_use_s2 = FALSE, + future.resolve.recursive = 2L + ) ) site_faux <- @@ -1527,7 +1528,7 @@ testthat::test_that("calc_lagged returns as expected.", { if (lags[l] == 0) { narr_lagged <- calc_lagged( from = narr_covariate, - date = c("2018-01-05", "2018-01-10"), + date = c("2018-01-01", "2018-01-10"), lag = lags[l], locs_id = "site_id", time_id = "time" diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 04455555..982f6f9a 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -1590,19 +1590,6 @@ testthat::test_that("process_huc", # Call the function and expect an error testthat::expect_error(process_huc(path2)) - # using nhdplusTools - testthat::expect_no_error( - test3 <- process_huc( - "", - layer_name = NULL, - huc_level = NULL, - huc_header = NULL, - id = "030202", - type = "huc06", - t_srs = "EPSG:5070" - ) - ) - testthat::expect_s4_class(test3, "SpatVector") } ) From 462be82fc757a283c7bf400c434f1bc3c6338fd2 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 11:49:52 -0400 Subject: [PATCH 57/65] calc_lagged patch + yaml fix - yaml - setup-r: use-public-rspm - calc_lagged: date alignment test logic fix --- .github/workflows/pkgdown.yaml | 2 ++ R/calculate_covariates.R | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index a3d4a411..204f6820 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -18,6 +18,8 @@ jobs: - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index c73814a0..ae8c25eb 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -2115,13 +2115,14 @@ calc_lagged <- function( return(from) } #### extract times - time <- from[[time_id]] + time <- as.character(from[[time_id]]) + dateseq <- seq(as.Date(date[1]) - lag, as.Date(date[2]), by = 1) + dateseq <- as.character(dateseq) + align <- setdiff(dateseq, unique(time)) ### check temporal alignment - if (!all(c(as.Date(date)[1] - lag, as.Date(date)[2]) %in% time)) { + if (length(align) > 0) { stop( - paste0( - "Dates requested in `date` do not align with data available in `from`." - ) + "Dates requested in `date` do not align with data available in `from`." ) } unique_locs <- unique(from[[locs_id]]) From dc899d55a9ffe41791d9c7dd5d49fa09597d202d Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 11:54:27 -0400 Subject: [PATCH 58/65] yaml fix for covr::report --- .github/workflows/pkgdown.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 204f6820..42175ec3 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -27,6 +27,8 @@ jobs: any::pak any::covr any::devtools + any::DT + any::htmltools needs: coverage - name: Cache C++ and R dependencies From f992ecef9127c742efd5f0087202aeb02f485d16 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 4 Jun 2024 13:05:20 -0400 Subject: [PATCH 59/65] test coverage improvement --- R/calculate_covariates_auxiliary.R | 2 +- R/download.R | 11 ++- ...ate_boundaries_2024-02-07_wget_command.txt | 1 - ...ate_boundaries_2024-02-08_wget_command.txt | 1 - tests/testthat/test-calculate_covariates.R | 7 ++ tests/testthat/test-download_functions.R | 67 ++++++++++++++----- tests/testthat/test-process.R | 20 +++++- 7 files changed, 81 insertions(+), 28 deletions(-) delete mode 100644 tests/testdata/us_eco_l3_state_boundaries_2024-02-07_wget_command.txt delete mode 100644 tests/testdata/us_eco_l3_state_boundaries_2024-02-08_wget_command.txt diff --git a/R/calculate_covariates_auxiliary.R b/R/calculate_covariates_auxiliary.R index 8a808c2a..39b63d0f 100644 --- a/R/calculate_covariates_auxiliary.R +++ b/R/calculate_covariates_auxiliary.R @@ -265,7 +265,7 @@ calc_time <- function( time, format) { if (format == "timeless") { - return() + return(time) } else if (format == "date") { return_time <- as.POSIXlt( time, diff --git a/R/download.R b/R/download.R index 7d73ec76..0aadafc8 100644 --- a/R/download.R +++ b/R/download.R @@ -335,8 +335,7 @@ download_ecoregion <- function( directory_to_save ) if (file.exists(path_downloaded_file)) { - message("Requested files exist in the target directory.\n") - return(NULL) + stop("Requested files exist in the target directory.\n") } #### 5. define download URL download_epa_certificate( @@ -486,7 +485,7 @@ download_geos <- function( collection_loop, "/" ) - if (!file.exists(download_folder)) { + if (!dir.exists(download_folder)) { dir.create(download_folder) } for (d in seq_along(date_sequence)) { @@ -945,7 +944,7 @@ download_merra2 <- function( directory_to_save, collection_loop ) - if (!file.exists(download_folder)) { + if (!dir.exists(download_folder)) { dir.create(download_folder) } download_name <- paste0( @@ -1082,7 +1081,7 @@ download_narr_monolevel <- function( for (v in seq_along(variables_list)) { variable <- variables_list[v] folder <- paste0(directory_to_save, variable, "/") - if (!(file.exists(folder))) { + if (!(dir.exists(folder))) { dir.create(folder) } for (y in seq_along(years)) { @@ -1211,7 +1210,7 @@ download_narr_p_levels <- function( for (v in seq_along(variables_list)) { variable <- variables_list[v] folder <- paste0(directory_to_save, variable, "/") - if (!(file.exists(folder))) { + if (!(dir.exists(folder))) { dir.create(folder) } for (y in seq_along(years)) { diff --git a/tests/testdata/us_eco_l3_state_boundaries_2024-02-07_wget_command.txt b/tests/testdata/us_eco_l3_state_boundaries_2024-02-07_wget_command.txt deleted file mode 100644 index 88706648..00000000 --- a/tests/testdata/us_eco_l3_state_boundaries_2024-02-07_wget_command.txt +++ /dev/null @@ -1 +0,0 @@ -wget --ca-certificate=/tmp/Rtmp5cEQ3l/cacert_gaftp_epa.pem https://gaftp.epa.gov/EPADataCommons/ORD/Ecoregions/us/us_eco_l3_state_boundaries.zip -O tests/testthat/../testdata//us_eco_l3_state_boundaries.zip diff --git a/tests/testdata/us_eco_l3_state_boundaries_2024-02-08_wget_command.txt b/tests/testdata/us_eco_l3_state_boundaries_2024-02-08_wget_command.txt deleted file mode 100644 index 41b8e70e..00000000 --- a/tests/testdata/us_eco_l3_state_boundaries_2024-02-08_wget_command.txt +++ /dev/null @@ -1 +0,0 @@ -wget --ca-certificate=/tmp/RtmpeRLmFs/cacert_gaftp_epa.pem https://gaftp.epa.gov/EPADataCommons/ORD/Ecoregions/us/us_eco_l3_state_boundaries.zip -O tests/testthat/../testdata//us_eco_l3_state_boundaries.zip diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index 0acfa958..cecf496b 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -1652,3 +1652,10 @@ testthat::test_that("calc_check_time identifies missing `time` column.", { ) ) }) + +# Calc message +testthat::test_that("calc_message exception", + testthat::expect_no_error( + calc_message("gmted", "mean", "2020", "year", NULL) + ) +) diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 2df31799..f2fc3ad2 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -49,9 +49,9 @@ testthat::test_that("Errors when temporal ranges invalid.", { expect_error( download_aqs( year_start = 1900, + year_end = 1919, acknowledgement = TRUE, directory_to_save = testthat::test_path("..", "testdata/", ""), - directory_to_download = testthat::test_path("..", "testdata/", "") ) ) expect_error( @@ -79,11 +79,16 @@ testthat::test_that("Errors when temporal ranges invalid.", { remove_command = TRUE ) ) + file.remove( + testthat::test_path( + "../testdata", "merra2_1900-01-01_2023-09-01_wget_commands.txt" + ) + ) + sink() expect_error( download_hms( date_start = "1900-01-01", directory_to_save = testthat::test_path("..", "testdata/", ""), - directory_to_download = testthat::test_path("..", "testdata/", ""), acknowledgement = TRUE ) ) @@ -157,7 +162,7 @@ testthat::test_that("EPA AQS download URLs have HTTP status 200.", { file.remove(commands_path) }) - +# Ecoregion tests #### testthat::test_that("Ecoregion download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") @@ -207,6 +212,7 @@ testthat::test_that("Ecoregion download URLs have HTTP status 200.", { unlink(directory_to_save, recursive = TRUE) }) +# GEOS-CF tests #### testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") @@ -217,13 +223,15 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { "chm_inst_1hr_g1440x721_p23") directory_to_save <- testthat::test_path("..", "testdata/", "") # run download function - download_data(dataset_name = "geos", - date_start = date_start, - date_end = date_end, - collection = collections, - directory_to_save = directory_to_save, - acknowledgement = TRUE, - download = FALSE) + testthat::expect_no_error( + download_data(dataset_name = "geos", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save, + acknowledgement = TRUE, + download = FALSE) + ) # define file path with commands commands_path <- paste0(directory_to_save, "geos_", @@ -245,6 +253,7 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { file.remove(commands_path) }) +# GMTED tests #### testthat::test_that("GMTED download URLs have HTTP status 200.", { withr::local_package("httr") # function parameters @@ -296,6 +305,7 @@ testthat::test_that("GMTED download URLs have HTTP status 200.", { } }) +# MERRA2 #### testthat::test_that("MERRA2 download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") @@ -305,13 +315,15 @@ testthat::test_that("MERRA2 download URLs have HTTP status 200.", { collections <- c("inst1_2d_asm_Nx", "inst3_3d_asm_Np") directory_to_save <- testthat::test_path("..", "testdata/", "") # run download function - download_data(dataset_name = "merra2", - date_start = date_start, - date_end = date_end, - collection = collections, - directory_to_save = directory_to_save, - acknowledgement = TRUE, - download = FALSE) + testthat::expect_no_error( + download_data(dataset_name = "merra2", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save, + acknowledgement = TRUE, + download = FALSE) + ) # define path with commands commands_path <- paste0(directory_to_save, "merra2_", @@ -333,6 +345,7 @@ testthat::test_that("MERRA2 download URLs have HTTP status 200.", { file.remove(commands_path) }) +# MERRA2 Collection error test #### testthat::test_that("MERRA2 returns message with unrecognized collection.", { # function parameters collections <- "uNrEcOgNiZeD" @@ -382,6 +395,7 @@ testthat::test_that("NARR monolevel download URLs have HTTP status 200.", { file.remove(commands_path) }) +# NARR -- Monolevel #### testthat::test_that("NARR monolevel error with invalid years.", { testthat::expect_error( download_data( @@ -395,6 +409,7 @@ testthat::test_that("NARR monolevel error with invalid years.", { ) }) +# NARR -- p-levels #### testthat::test_that("NARR p-levels download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") @@ -1778,3 +1793,21 @@ testthat::test_that( } ) + +testthat::test_that("download_sink test", { + testfile <- testthat::test_path("../testdata", "sink_test.txt") + file.create(testfile) + testthat::expect_no_error( + download_sink(testfile) + ) + sink() + file.remove(testfile) +}) + +testthat::test_that("download_remove_zips test", { + testfile <- testthat::test_path("../testdata", "coyote.zip") + file.create(testfile) + testthat::expect_no_error( + download_remove_zips(remove = TRUE, testfile) + ) +}) diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 982f6f9a..1d3f9572 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -266,7 +266,6 @@ testthat::test_that("process_modis_merge is good to go", { ) ) - }) @@ -492,6 +491,9 @@ testthat::test_that("process_nei tests", { testthat::expect_s4_class(neinc, "SpatVector") # error cases + testthat::expect_error( + process_nei(testthat::test_path("../testdata", "modis"), year = 2017) + ) testthat::expect_error( process_nei(path_nei, year = 2030, county = path_cnty) ) @@ -970,6 +972,18 @@ testthat::test_that("process_locs_vector vector data and missing columns.", { locs_id = "site_id" ) ) + # error if one of "lat" or "lon" is missing (or both) + ncpp <- data.frame(long = -78.8277, lat = 35.95013) + ncpp$site_id <- "3799900018810101" + + expect_error( + process_locs_vector( + locs = ncpp, crs = "EPSG:4326", 0 + ) + ) + expect_error( + process_locs_vector(array(1)) + ) }) # test AQS #### @@ -1101,8 +1115,10 @@ testthat::test_that("process_aqs", { # expect testthat::expect_s3_class(aqssf, "sf") - # error cases + testthat::expect_error( + process_aqs(testthat::test_path("../testdata", "modis")) + ) testthat::expect_error( process_aqs(path = 1L) ) From fc8f06a1f6913136610dfdc095b52075df4c047e Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 12 Jun 2024 09:35:40 -0400 Subject: [PATCH 60/65] AQS event processing update - process_aqs: cases with multiple event types are handled - Documentation is revised to describe the event processing --- R/calculate_covariates.R | 5 +++-- R/process.R | 22 +++++++++++++++++++--- man/process_aqs.Rd | 6 +++++- tests/testthat/test-process.R | 17 ++++++++++++++++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/R/calculate_covariates.R b/R/calculate_covariates.R index ae8c25eb..3c1f03b2 100644 --- a/R/calculate_covariates.R +++ b/R/calculate_covariates.R @@ -935,9 +935,10 @@ calc_temporal_dummies <- colnames(dt_month_dum) <- sprintf("DUM_%s_0_00000", shortmn) - # weekday (starts from 1-Monday) + # weekday (starts from 0 - Sunday) vec_wday <- as.POSIXlt(locs$time)$wday - dt_wday_dum <- dummify(vec_wday, seq(1L, 7L)) + # subtracting 1 due to the difference in the base + dt_wday_dum <- dummify(vec_wday, seq(1L, 7L) - 1) colnames(dt_wday_dum) <- sprintf("DUM_WKDY%d_0_00000", seq(1L, 7L)) diff --git a/R/process.R b/R/process.R index c606ea44..3de7c182 100644 --- a/R/process.R +++ b/R/process.R @@ -976,7 +976,11 @@ process_nei <- function( #' Process unique U.S. EPA AQS sites #' @description #' The \code{process_aqs()} function cleans and imports raw air quality -#' monitoring sites, returning a single `SpatVector` or sf object. +#' monitoring sites, returning a single `SpatVector` or sf object. +#' Some sites report multiple measurements per day with and without +#' [exceptional events](https://www.epa.gov/sites/default/files/2016-10/documents/exceptional_events.pdf) +#' the internal procedure of this function keeps "Included" if there +#' are multiple event types per site-time. #' @param path character(1). Directory path to daily measurement data. #' @param date character(2). Start and end date. #' Should be in `"YYYY-MM-DD"` format and sorted. If `NULL`, @@ -991,7 +995,7 @@ process_nei <- function( #' * [`download_aqs()`] #' * [EPA, n.d., _AQS Parameter Codes_]( #' https://aqs.epa.gov/aqsweb/documents/codetables/parameters.csv) -#' @returns a `SpatVector` or sf object depending on the `return_format` +#' @returns a SpatVector, sf, or data.table object depending on the `return_format` #' @importFrom data.table as.data.table #' @importFrom utils read.csv #' @importFrom terra vect @@ -1061,7 +1065,7 @@ process_aqs <- dplyr::filter(POC == min(POC)) |> dplyr::mutate(time = Date.Local) |> dplyr::ungroup() - col_sel <- c("site_id", "Longitude", "Latitude", "Datum") + col_sel <- c("site_id", "Longitude", "Latitude", "Event.Type", "Datum") if (mode != "sparse") { sites_v <- unique(sites[, col_sel]) } else { @@ -1070,6 +1074,18 @@ process_aqs <- sites_v <- sites |> dplyr::select(dplyr::all_of(col_sel)) |> dplyr::distinct() + # excluding site-time with multiple event types + # sites_vdup will be "subtracted" from the original sites_v + sites_vdup <- sites_v |> + dplyr::group_by(site_id, time) |> + dplyr::filter(dplyr::n() > 1) |> + dplyr::filter(Event.Type == "Excluded") |> + ungroup() + sites_v <- + dplyr::anti_join( + sites_v, sites_vdup, + by = c("site_id", "time", "Event.Type") + ) } names(sites_v)[2:3] <- c("lon", "lat") sites_v <- data.table::as.data.table(sites_v) diff --git a/man/process_aqs.Rd b/man/process_aqs.Rd index 357e427b..ee49f127 100644 --- a/man/process_aqs.Rd +++ b/man/process_aqs.Rd @@ -31,11 +31,15 @@ or "sparse" (date-location pairs with available data) or \item{...}{Placeholders.} } \value{ -a \code{SpatVector} or sf object depending on the \code{return_format} +a SpatVector, sf, or data.table object depending on the \code{return_format} } \description{ The \code{process_aqs()} function cleans and imports raw air quality monitoring sites, returning a single \code{SpatVector} or sf object. +Some sites report multiple measurements per day with and without +\href{https://www.epa.gov/sites/default/files/2016-10/documents/exceptional_events.pdf}{exceptional events} +the internal procedure of this function keeps "Included" if there +are multiple event types per site-time. } \note{ Choose \code{date} and \code{mode} values with caution. diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index 1d3f9572..afb0a51e 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -410,12 +410,27 @@ testthat::test_that("Other MODIS function errors", { # test Ecoregions #### testthat::test_that("read ecoregion", { withr::local_package("terra") + withr::local_package("sf") + withr::local_options(list(sf_use_s2 = FALSE)) path_eco <- testthat::test_path("..", "testdata", "eco_l3_clip.gpkg") - testthat::expect_no_error( process_ecoregion(path_eco) ) + + ecotemp <- sf::st_read(path_eco) + # nolint start + addpoly <- + "POLYGON ((-70.2681 43.6787, -70.252234 43.677145, -70.251036 -43.680758, -70.268666 43.681505, -70.2681 43.6787))" + # nolint end + addpoly <- sf::st_as_sfc(addpoly, crs = "EPSG:4326") + addpoly <- sf::st_transform(addpoly, sf::st_crs(ecotemp)) + ecotemp[1, "geom"] <- addpoly + tdir <- tempdir() + sf::st_write(ecotemp, paste0(tdir, "/ecoregions.gpkg"), append = FALSE) + testthat::expect_no_error( + suppressWarnings(process_ecoregion(paste0(tdir, "/ecoregions.gpkg"))) + ) }) From 5dd89c008a0199cdf6d5e28a6fbe115022ef13b7 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Wed, 12 Jun 2024 13:16:10 -0400 Subject: [PATCH 61/65] process_aqs patch - logic fix: from now `date` cannot be NULL. Records will be filtered with a date sequence generated from `date`. --- NAMESPACE | 2 ++ R/process.R | 58 +++++++++++++++++------------------ man/process_aqs.Rd | 5 +-- tests/testthat/test-process.R | 27 +++++++++++----- 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index cb043a00..2595c1ff 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -134,12 +134,14 @@ importFrom(dplyr,across) importFrom(dplyr,all_of) importFrom(dplyr,as_tibble) importFrom(dplyr,bind_rows) +importFrom(dplyr,distinct) importFrom(dplyr,ends_with) importFrom(dplyr,filter) importFrom(dplyr,group_by) importFrom(dplyr,lag) importFrom(dplyr,left_join) importFrom(dplyr,mutate) +importFrom(dplyr,select) importFrom(dplyr,summarize) importFrom(dplyr,ungroup) importFrom(exactextractr,exact_extract) diff --git a/R/process.R b/R/process.R index 3de7c182..adce2480 100644 --- a/R/process.R +++ b/R/process.R @@ -977,14 +977,15 @@ process_nei <- function( #' @description #' The \code{process_aqs()} function cleans and imports raw air quality #' monitoring sites, returning a single `SpatVector` or sf object. +#' `date` is used to filter the raw data read from csv files. +#' Filtered rows are then processed according to `mode` argument. #' Some sites report multiple measurements per day with and without #' [exceptional events](https://www.epa.gov/sites/default/files/2016-10/documents/exceptional_events.pdf) #' the internal procedure of this function keeps "Included" if there #' are multiple event types per site-time. #' @param path character(1). Directory path to daily measurement data. #' @param date character(2). Start and end date. -#' Should be in `"YYYY-MM-DD"` format and sorted. If `NULL`, -#' only unique locations are returned. +#' Should be in `"YYYY-MM-DD"` format and sorted. #' @param mode character(1). One of "full" (all dates * all locations) #' or "sparse" (date-location pairs with available data) or #' "location" (unique locations). @@ -998,12 +999,9 @@ process_nei <- function( #' @returns a SpatVector, sf, or data.table object depending on the `return_format` #' @importFrom data.table as.data.table #' @importFrom utils read.csv -#' @importFrom terra vect -#' @importFrom terra project +#' @importFrom terra vect project #' @importFrom sf st_as_sf -#' @importFrom dplyr group_by -#' @importFrom dplyr ungroup -#' @importFrom dplyr filter +#' @importFrom dplyr group_by ungroup filter mutate select distinct #' @note Choose `date` and `mode` values with caution. #' The function may return a massive data.table, resulting in #' a long processing time or even a crash. @@ -1027,6 +1025,8 @@ process_aqs <- if (length(date) != 2) { stop("date should be a character vector of length 2.") } + } else { + stop("date should be defined.") } if (length(path) == 1 && dir.exists(path)) { path <- list.files( @@ -1057,18 +1057,25 @@ process_aqs <- Date.Local <- NULL Sample.Duration <- NULL + date_start <- as.Date(date[1]) + date_end <- as.Date(date[2]) + date_sequence <- seq(date_start, date_end, "day") + date_sequence <- as.character(date_sequence) + # select relevant fields only sites <- sites |> dplyr::as_tibble() |> + dplyr::filter(as.character(Date.Local) %in% date_sequence) |> dplyr::filter(startsWith(Sample.Duration, "24")) |> dplyr::group_by(site_id) |> dplyr::filter(POC == min(POC)) |> dplyr::mutate(time = Date.Local) |> dplyr::ungroup() - col_sel <- c("site_id", "Longitude", "Latitude", "Event.Type", "Datum") + col_sel <- c("site_id", "Longitude", "Latitude", "Datum") if (mode != "sparse") { sites_v <- unique(sites[, col_sel]) } else { + col_sel <- append(col_sel, "Event.Type") col_sel <- append(col_sel, "time") col_sel <- append(col_sel, data_field) sites_v <- sites |> @@ -1080,7 +1087,7 @@ process_aqs <- dplyr::group_by(site_id, time) |> dplyr::filter(dplyr::n() > 1) |> dplyr::filter(Event.Type == "Excluded") |> - ungroup() + dplyr::ungroup() sites_v <- dplyr::anti_join( sites_v, sites_vdup, @@ -1111,27 +1118,18 @@ process_aqs <- final_sites <- final_sites[, grep("Datum", names(final_sites), invert = TRUE), with = FALSE] - if (!is.null(date)) { - date_start <- as.Date(date[1]) - date_end <- as.Date(date[2]) - date_sequence <- seq(date_start, date_end, "day") - date_sequence <- as.character(date_sequence) - - if (mode == "full") { - final_sites <- - split(date_sequence, date_sequence) |> - lapply(function(x) { - fs_time <- final_sites - fs_time$time <- x - return(fs_time) - }) - final_sites <- data.table::rbindlist(final_sites, fill = TRUE) - } - if (mode == "sparse") { - final_sites <- - final_sites[final_sites$time %in% date_sequence, ] - final_sites <- unique(final_sites) - } + if (mode == "full") { + final_sites <- + split(date_sequence, date_sequence) |> + lapply(function(x) { + fs_time <- final_sites + fs_time$time <- x + return(fs_time) + }) + final_sites <- data.table::rbindlist(final_sites, fill = TRUE) + } + if (mode == "sparse") { + final_sites <- unique(final_sites) } final_sites <- diff --git a/man/process_aqs.Rd b/man/process_aqs.Rd index ee49f127..f8c7fa29 100644 --- a/man/process_aqs.Rd +++ b/man/process_aqs.Rd @@ -17,8 +17,7 @@ process_aqs( \item{path}{character(1). Directory path to daily measurement data.} \item{date}{character(2). Start and end date. -Should be in \code{"YYYY-MM-DD"} format and sorted. If \code{NULL}, -only unique locations are returned.} +Should be in \code{"YYYY-MM-DD"} format and sorted.} \item{mode}{character(1). One of "full" (all dates * all locations) or "sparse" (date-location pairs with available data) or @@ -36,6 +35,8 @@ a SpatVector, sf, or data.table object depending on the \code{return_format} \description{ The \code{process_aqs()} function cleans and imports raw air quality monitoring sites, returning a single \code{SpatVector} or sf object. +\code{date} is used to filter the raw data read from csv files. +Filtered rows are then processed according to \code{mode} argument. Some sites report multiple measurements per day with and without \href{https://www.epa.gov/sites/default/files/2016-10/documents/exceptional_events.pdf}{exceptional events} the internal procedure of this function keeps "Included" if there diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index afb0a51e..f9965407 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -1016,9 +1016,6 @@ testthat::test_that("process_aqs", { ) # main test - testthat::expect_no_error( - aqs <- process_aqs(path = aqssub, date = NULL) - ) testthat::expect_no_error( aqsft <- process_aqs( path = aqssub, @@ -1045,7 +1042,6 @@ testthat::test_that("process_aqs", { ) # expect - testthat::expect_s4_class(aqs, "SpatVector") testthat::expect_s4_class(aqsft, "SpatVector") testthat::expect_s4_class(aqsst, "SpatVector") testthat::expect_s4_class(aqslt, "SpatVector") @@ -1094,6 +1090,15 @@ testthat::test_that("process_aqs", { return_format = "data.table" ) ) + testthat::expect_no_error( + aqssdd <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "sparse", + data_field = "Arithmetic.Mean", + return_format = "data.table" + ) + ) testthat::expect_no_error( aqsld <- process_aqs( path = aqssub, @@ -1102,13 +1107,21 @@ testthat::test_that("process_aqs", { return_format = "data.table" ) ) + testthat::expect_no_error( + aqsldd <- process_aqs( + path = aqssub, + date = c("2022-02-04", "2022-02-28"), + mode = "location", + data_field = "Arithmetic.Mean", + return_format = "data.table" + ) + ) testthat::expect_s3_class(aqsfd, "data.table") testthat::expect_s3_class(aqssd, "data.table") + testthat::expect_s3_class(aqssdd, "data.table") testthat::expect_s3_class(aqsld, "data.table") + testthat::expect_s3_class(aqsldd, "data.table") - testthat::expect_no_error( - aqssf <- process_aqs(path = aqssub, date = NULL, return_format = "sf") - ) testthat::expect_no_error( aqssf <- process_aqs( path = testd, From 7b1eea0019585ac54fd9a452d5bdc5bf589c0262 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 18 Jun 2024 13:19:54 -0400 Subject: [PATCH 62/65] test coverage improvement --- .github/workflows/test-coverage-local.yaml | 4 + tests/testthat/test-calculate_covariates.R | 60 +++++++++++- tests/testthat/test-process.R | 104 +++++++++++++++++++++ 3 files changed, 165 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-coverage-local.yaml b/.github/workflows/test-coverage-local.yaml index c4fce4d6..4fcca8b4 100644 --- a/.github/workflows/test-coverage-local.yaml +++ b/.github/workflows/test-coverage-local.yaml @@ -68,6 +68,10 @@ jobs: id: patch-comparison shell: bash run: | + if ( ! test -f cov_current.Rout ); then + echo "0" >> cov_current.Rout + fi + cov_patch="${{ steps.get-values.outputs.coverage }}" cov_current=$(cat cov_current.Rout) echo "Current coverage: $cov_current" diff --git a/tests/testthat/test-calculate_covariates.R b/tests/testthat/test-calculate_covariates.R index cecf496b..e4050a0e 100644 --- a/tests/testthat/test-calculate_covariates.R +++ b/tests/testthat/test-calculate_covariates.R @@ -358,6 +358,13 @@ testthat::test_that("calc_modis works well.", { locs = sf::st_as_sf(site_faux) ) ) + testthat::expect_error( + calc_modis_daily( + from = terra::rast(nrow = 3, ncol = 3, vals = 1:9, names = "a"), + date = "2021-08-15", + locs = array(1:12, dim = c(2, 2, 3)) + ) + ) site_faux0 <- site_faux names(site_faux0)[2] <- "date" testthat::expect_error( @@ -415,7 +422,7 @@ testthat::test_that("calc_modis works well.", { preprocess = process_bluemarble, name_covariates = c("MOD_NITLT_0_", "MOD_K1_"), subdataset = 3L, - nthreads = 1, + nthreads = 2, tile_df = process_bluemarble_corners(c(9, 10), c(5, 5)) ) ) @@ -1655,7 +1662,54 @@ testthat::test_that("calc_check_time identifies missing `time` column.", { # Calc message testthat::test_that("calc_message exception", + { + testthat::expect_no_error( + calc_message("gmted", "mean", "2020", "year", NULL) + ) + testthat::expect_no_error( + calc_message("narr", "shum", 2000, "year", NULL) + ) + } +) + +# calc time +testthat::test_that("calc time remains", { testthat::expect_no_error( - calc_message("gmted", "mean", "2020", "year", NULL) + rr <- calc_time("eternal", "timeless") ) -) + testthat::expect_true(rr == "eternal") +}) + +# calc worker +testthat::test_that("calc_worker remaining", { + withr::local_package("terra") + withr::local_package("sf") + withr::local_package("exactextractr") + withr::local_options(sf_use_s2 = FALSE) + + ncp <- data.frame(lon = -78.8277, lat = 35.95013, time = "boundless") + ncp$site_id <- "3799900018810101" + ncpt <- + terra::vect(ncp, geom = c("lon", "lat"), + keepgeom = TRUE, crs = "EPSG:4326") + nc <- system.file("gpkg/nc.gpkg", package = "sf") + nc <- terra::vect(nc) + nc <- terra::project(nc, "EPSG:4326") + ncrast <- terra::rast(nc, resolution = 0.05) + terra::values(ncrast) <- rgamma(terra::ncell(ncrast), 1, 1e-4) + + testthat::expect_no_error( + cwres <- + calc_worker( + from = ncrast, + dataset = "whatever", + locs_vector = ncpt, + locs_df = ncp, + time = ncpt$time, + time_type = "timeless", + radius = 1e5, + max_cells = 3e7 + ) + ) + testthat::expect_s3_class(cwres, "data.frame") +}) diff --git a/tests/testthat/test-process.R b/tests/testthat/test-process.R index f9965407..02e655ca 100644 --- a/tests/testthat/test-process.R +++ b/tests/testthat/test-process.R @@ -619,6 +619,15 @@ testthat::test_that("process_sedac_population returns null for netCDF.", { ) }) +testthat::test_that("sedac_codes", { + string <- "2.5 minute" + testthat::expect_no_error( + code <- process_sedac_codes(string) + ) + testthat::expect_equal(code, "2pt5_min") +}) + + # test HMS #### testthat::test_that("process_hms returns expected.", { withr::local_package("terra") @@ -746,6 +755,32 @@ testthat::test_that("import_gmted returns error with non-vector variable.", { ) }) +testthat::test_that("gmted_codes inversion", { + teststring <- "mx" + testthat::expect_no_error( + statorig <- process_gmted_codes( + teststring, + statistic = TRUE, + resolution = FALSE, + invert = TRUE + ) + ) + testthat::expect_equal(statorig, "Maximum Statistic") + + teststring <- "75" + testthat::expect_no_error( + resoorig <- process_gmted_codes( + teststring, + statistic = FALSE, + resolution = TRUE, + invert = TRUE + ) + ) + testthat::expect_equal(resoorig, "7.5 arc-seconds") +}) + + +## test NARR #### testthat::test_that("process_narr returns expected.", { withr::local_package("terra") variables <- c( @@ -1652,3 +1687,72 @@ testthat::test_that("process_olm", { ) }) # nolint end + +## AUX tests #### +testthat::test_that("loc_radius tests", { + withr::local_package("terra") + withr::local_package("sf") + withr::local_options(list(sf_use_s2 = FALSE)) + + lon <- seq(-112, -101, length.out = 5) # create lon sequence + lat <- seq(33.5, 40.9, length.out = 5) # create lat sequence + df <- expand.grid("lon" = lon, "lat" = lat) # expand to regular grid + df <- rbind(df, df) + df$time <- c(rep("2023-11-02", 25), rep("2023-11-03", 25)) + df$var1 <- 1:50 + df$var2 <- 51:100 + dfsf <- sf::st_as_sf( + df, + coords = c("lon", "lat"), + crs = "EPSG:4326", + remove = FALSE + ) + dftr <- terra::vect(dfsf) + + testthat::expect_no_error( + dftrb00 <- process_locs_radius(dftr, 0) + ) + testthat::expect_no_error( + dftrb1k <- process_locs_radius(dftr, 1000L) + ) + testthat::expect_true(terra::geomtype(dftrb00) == "points") + testthat::expect_true(terra::geomtype(dftrb1k) == "polygons") + testthat::expect_s4_class(dftrb00, "SpatVector") + testthat::expect_s4_class(dftrb1k, "SpatVector") +}) + +testthat::test_that("process_locs_vector tests", { + withr::local_package("terra") + withr::local_package("sf") + withr::local_options(list(sf_use_s2 = FALSE)) + + lon <- seq(-112, -101, length.out = 5) # create lon sequence + lat <- seq(33.5, 40.9, length.out = 5) # create lat sequence + df <- expand.grid("lon" = lon, "lat" = lat) # expand to regular grid + dfsf <- sf::st_as_sf( + df, + coords = c("lon", "lat"), + crs = "EPSG:4326", + remove = FALSE + ) + dftr <- terra::vect(dfsf) + + testthat::expect_no_error( + dftr1 <- process_locs_vector(dftr, "EPSG:4326", 0) + ) + testthat::expect_no_error( + dfsftr <- process_locs_vector(dfsf, "EPSG:4326", 0) + ) + testthat::expect_no_error( + dfdftr <- process_locs_vector(df, "EPSG:4326", 0) + ) + testthat::expect_no_error( + dfdftrb <- process_locs_vector(df, "EPSG:4326", radius = 1000L) + ) + testthat::expect_s4_class(dftr1, "SpatVector") + testthat::expect_s4_class(dfsftr, "SpatVector") + testthat::expect_s4_class(dfdftr, "SpatVector") + testthat::expect_s4_class(dfdftrb, "SpatVector") + testthat::expect_true(terra::geomtype(dfdftr) == "points") + testthat::expect_true(terra::geomtype(dfdftrb) == "polygons") +}) From 73b040ac08ea65a0d736c0cfda684529e5603e40 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 18 Jun 2024 15:08:58 -0400 Subject: [PATCH 63/65] test coverage + edit download_functions - download_koppen_geiger: dropped overspecified file removal that cannot be tested - download_remove_zips: merging unlink() into the function, removed bare unlink()s in functions --- R/download.R | 105 ++++++----------------- R/download_auxiliary.R | 3 + tests/testthat/test-download_functions.R | 84 +++++++++++++++--- 3 files changed, 102 insertions(+), 90 deletions(-) diff --git a/R/download.R b/R/download.R index 0aadafc8..8abca046 100644 --- a/R/download.R +++ b/R/download.R @@ -243,20 +243,17 @@ download_aqs <- system_command = system_command ) #### 12. unzip data - for (n in seq_along(download_names)) { - download_unzip( - file_name = download_names[n], - directory_to_unzip = directory_to_save, - unzip = unzip - ) - download_remove_zips( - remove = remove_zip, - download_name = download_names[n] - ) - } - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } + vapply( + download_names, + download_unzip, + FUN.VALUE = logical(1), + directory_to_unzip = directory_to_save, + unzip = unzip + ) + download_remove_zips( + remove = remove_zip, + download_name = download_names + ) #### 13. remove command file download_remove_command( commands_txt = commands_txt, @@ -327,16 +324,6 @@ download_ecoregion <- function( directories <- download_setup_dir(directory_original, zip = TRUE) directory_to_download <- directories[1] directory_to_save <- directories[2] - #### 4. Check the presence of file - ## This part is hard-coded as the original file appears to - ## be a misnomer. May need to be modified accordingly in the future. - path_downloaded_file <- sprintf( - "%sus_eco_l3_state_boundaries.shp", - directory_to_save - ) - if (file.exists(path_downloaded_file)) { - stop("Requested files exist in the target directory.\n") - } #### 5. define download URL download_epa_certificate( epa_certificate_path = epa_certificate_path, @@ -348,9 +335,9 @@ download_ecoregion <- function( "us_eco_l3_state_boundaries.zip" ) #### 6. build download file name - download_name <- sprintf( - "%sus_eco_l3_state_boundaries.zip", - directory_to_download + download_name <- file.path( + directory_to_download, + "us_eco_l3_state_boundaries.zip" ) #### 7. build download command download_command <- @@ -404,9 +391,6 @@ download_ecoregion <- function( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } } # nolint start @@ -486,7 +470,7 @@ download_geos <- function( "/" ) if (!dir.exists(download_folder)) { - dir.create(download_folder) + dir.create(download_folder, recursive = TRUE) } for (d in seq_along(date_sequence)) { date <- date_sequence[d] @@ -710,9 +694,6 @@ download_gmted <- function( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } } # nolint start @@ -945,7 +926,7 @@ download_merra2 <- function( collection_loop ) if (!dir.exists(download_folder)) { - dir.create(download_folder) + dir.create(download_folder, recursive = TRUE) } download_name <- paste0( download_folder, @@ -978,8 +959,8 @@ download_merra2 <- function( collection_loop, "/metadata/" ) - if (!file.exists(download_folder_metadata)) { - dir.create(download_folder_metadata) + if (!dir.exists(download_folder_metadata)) { + dir.create(download_folder_metadata, recursive = TRUE) } download_name_metadata <- paste0( download_folder_metadata, @@ -1081,8 +1062,8 @@ download_narr_monolevel <- function( for (v in seq_along(variables_list)) { variable <- variables_list[v] folder <- paste0(directory_to_save, variable, "/") - if (!(dir.exists(folder))) { - dir.create(folder) + if (!dir.exists(folder)) { + dir.create(folder, recursive = TRUE) } for (y in seq_along(years)) { year <- years[y] @@ -1210,8 +1191,8 @@ download_narr_p_levels <- function( for (v in seq_along(variables_list)) { variable <- variables_list[v] folder <- paste0(directory_to_save, variable, "/") - if (!(dir.exists(folder))) { - dir.create(folder) + if (!dir.exists(folder)) { + dir.create(folder, recursive = TRUE) } for (y in seq_along(years)) { year <- years[y] @@ -1421,9 +1402,6 @@ download_nlcd <- function( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } #### 18. remove command text download_remove_command( commands_txt = commands_txt, @@ -1569,9 +1547,6 @@ download_sedac_groads <- function( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } } # nolint start @@ -1749,9 +1724,6 @@ download_sedac_population <- function( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } } # nolint start @@ -1913,7 +1885,8 @@ download_hms <- function( #### 13. end if data_format == "KML" if (data_format == "KML") { unlink(directory_to_download, recursive = TRUE) - return(cat(paste0("KML files cannot be unzipped.\n"))) + cat(paste0("KML files cannot be unzipped.\n")) + return(TRUE) } #### 14. unzip downloaded zip files for (d in seq_along(download_names)) { @@ -1928,9 +1901,6 @@ download_hms <- function( remove = remove_zip, download_name = download_names ) - if (remove_zip) { - unlink(directory_to_download, recursive = TRUE) - } } # nolint end: cyclocomp @@ -2051,37 +2021,12 @@ download_koppen_geiger <- function( directory_to_unzip = directory_to_save, unzip = unzip ) - if (unzip) { - #### 16. remove unwanted files - wanted_names <- grep( - "conf", - list.files( - path = directory_to_save, - pattern = sprintf( - "Beck_KG_.*_%s_.*%s.*\\.tif$|legend\\.txt", - period, - data_resolution - ), - full.names = TRUE - ), - invert = TRUE, - value = TRUE - ) - all_names <- list.files( - path = directory_to_save, - full.names = TRUE - ) - unwanted_names <- all_names[!all_names %in% wanted_names] - file.remove(unwanted_names) - } + #### 19. remove zip files download_remove_zips( remove = remove_zip, download_name = download_name ) - if (remove_zip) { - unlink(directory_to_save, recursive = TRUE) - } } diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index e2da75c6..eb71a3bf 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -201,6 +201,9 @@ download_remove_zips <- if (remove) { cat(paste0("Removing download files...\n")) file.remove(download_name) + # oftentimes zipfiles are stored in zip_files under + # directory_to_save in download functions. + unlink(dirname(dirname(download_name)), recursive = TRUE) cat(paste0("Download files removed.\n")) } } diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index f2fc3ad2..2bae6ebc 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -206,10 +206,25 @@ testthat::test_that("Ecoregion download URLs have HTTP status 200.", { test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) - # remove file with commands after test - file.remove(commands_path) - # remove temporary eco - unlink(directory_to_save, recursive = TRUE) + + file.create( + file.path(directory_to_save, "zip_files", + "us_eco_l3_state_boundaries.zip"), + recursive = TRUE + ) + testthat::expect_no_error( + download_data( + dataset_name = "ecoregion", + directory_to_save = directory_to_save, + acknowledgement = TRUE, + unzip = FALSE, + remove_zip = TRUE, + download = FALSE, + remove_command = TRUE, + epa_certificate_path = certificate + ) + ) + }) # GEOS-CF tests #### @@ -222,6 +237,8 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { collections <- c("aqc_tavg_1hr_g1440x721_v1", "chm_inst_1hr_g1440x721_p23") directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save2 <- testthat::test_path("..", "testdata", "geos_temp") + # run download function testthat::expect_no_error( download_data(dataset_name = "geos", @@ -244,11 +261,22 @@ testthat::test_that("GEOS-CF download URLs have HTTP status 200.", { # extract urls urls <- extract_urls(commands = commands, position = 2) # check HTTP URL status - url_status <- check_urls(urls = urls, size = 10L, method = "HEAD") + url_status <- check_urls(urls = urls, size = 2L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) + testthat::expect_no_error( + download_data(dataset_name = "geos", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save2, + acknowledgement = TRUE, + remove_command = TRUE, + download = FALSE) + ) + # remove file with commands after test file.remove(commands_path) }) @@ -292,15 +320,31 @@ testthat::test_that("GMTED download URLs have HTTP status 200.", { commands <- read_commands(commands_path = commands_path) # extract urls urls <- extract_urls(commands = commands, position = 6) + filename <- extract_urls(commands = commands, position = 4) # check HTTP URL status url_status <- check_urls(urls = urls, size = 1L, method = "HEAD") # implement unit tests test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) + + file.create( + file.path(filename), + recursive = TRUE + ) # remove file with commands after test - file.remove(commands_path) # remove temporary gmted + testthat::expect_no_error( + download_data(dataset_name = "gmted", + statistic = statistics[s], + resolution = resolution, + directory_to_save = directory_to_save, + acknowledgement = TRUE, + unzip = FALSE, + remove_zip = TRUE, + remove_command = TRUE, + download = FALSE) + ) unlink(directory_to_save, recursive = TRUE) } }) @@ -314,6 +358,7 @@ testthat::test_that("MERRA2 download URLs have HTTP status 200.", { date_end <- "2022-03-08" collections <- c("inst1_2d_asm_Nx", "inst3_3d_asm_Np") directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save2 <- testthat::test_path("..", "testdata", "hej") # run download function testthat::expect_no_error( download_data(dataset_name = "merra2", @@ -341,6 +386,16 @@ testthat::test_that("MERRA2 download URLs have HTTP status 200.", { test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) + testthat::expect_no_error( + download_data(dataset_name = "merra2", + date_start = date_start, + date_end = date_end, + collection = collections, + directory_to_save = directory_to_save2, + acknowledgement = TRUE, + remove_command = TRUE, + download = FALSE) + ) # remove file with commands after test file.remove(commands_path) }) @@ -360,6 +415,7 @@ testthat::test_that("MERRA2 returns message with unrecognized collection.", { ) }) +## NARR Monolevel #### testthat::test_that("NARR monolevel download URLs have HTTP status 200.", { withr::local_package("httr") withr::local_package("stringr") @@ -395,7 +451,6 @@ testthat::test_that("NARR monolevel download URLs have HTTP status 200.", { file.remove(commands_path) }) -# NARR -- Monolevel #### testthat::test_that("NARR monolevel error with invalid years.", { testthat::expect_error( download_data( @@ -418,6 +473,7 @@ testthat::test_that("NARR p-levels download URLs have HTTP status 200.", { year_end <- 2021 variables <- c("shum", "omega") directory_to_save <- testthat::test_path("..", "testdata/", "") + directory_to_save2 <- testthat::test_path("..", "testdata", "hej") # run download function download_data(dataset_name = "narr_p_levels", year_start = year_start, @@ -441,6 +497,14 @@ testthat::test_that("NARR p-levels download URLs have HTTP status 200.", { test_download_functions(directory_to_save = directory_to_save, commands_path = commands_path, url_status = url_status) + download_data(dataset_name = "narr_p_levels", + year_start = year_start, + year_end = year_end, + variables = variables, + directory_to_save = directory_to_save2, + acknowledgement = TRUE, + remove_command = TRUE, + download = FALSE) # remove file with commands after test file.remove(commands_path) }) @@ -1659,7 +1723,7 @@ testthat::test_that("list_stac_files returns a character vector of file links", # Set up test data stac_json <- "https://s3.eu-central-1.wasabisys.com/stac/openlandmap/catalog.json" format <- "tif" - which <- 64 + which <- 35 # Call the function testthat::expect_message( @@ -1805,8 +1869,8 @@ testthat::test_that("download_sink test", { }) testthat::test_that("download_remove_zips test", { - testfile <- testthat::test_path("../testdata", "coyote.zip") - file.create(testfile) + testfile <- testthat::test_path("../testdata", "yellowstone", "coyote.zip") + file.create(testfile, recursive = TRUE) testthat::expect_no_error( download_remove_zips(remove = TRUE, testfile) ) From 9bd7c502cda66cd2607dfb9eff8699165be0ddcf Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 18 Jun 2024 15:36:04 -0400 Subject: [PATCH 64/65] test error fix - download_aqs: vapply to sapply - download_remove_zips tests - download_remove_zips documentation edited --- R/download.R | 3 +-- R/download_auxiliary.R | 2 ++ man/download_remove_zips.Rd | 2 ++ tests/testthat/test-download_functions.R | 4 +++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/R/download.R b/R/download.R index 8abca046..50835a79 100644 --- a/R/download.R +++ b/R/download.R @@ -243,10 +243,9 @@ download_aqs <- system_command = system_command ) #### 12. unzip data - vapply( + sapply( download_names, download_unzip, - FUN.VALUE = logical(1), directory_to_unzip = directory_to_save, unzip = unzip ) diff --git a/R/download_auxiliary.R b/R/download_auxiliary.R index eb71a3bf..2aa019a8 100644 --- a/R/download_auxiliary.R +++ b/R/download_auxiliary.R @@ -189,8 +189,10 @@ download_unzip <- #' @param remove logical(1). Confirm removal. Default is FALSE. #' @param download_name character. Full zip file path #' @note +#' !!! USE THE FUNCTION WITH CAUTION !!! #' If \code{remove = TRUE}, ensure that \code{unzip = TRUE}. Choosing to remove #' ".zip" files without unzipping will retain none of the downloaded data. +#' then it will remove all files in the second higher level directory. #' @returns NULL #' @keywords internal #' @export diff --git a/man/download_remove_zips.Rd b/man/download_remove_zips.Rd index a28d5902..72d26dbc 100644 --- a/man/download_remove_zips.Rd +++ b/man/download_remove_zips.Rd @@ -15,7 +15,9 @@ download_remove_zips(remove = FALSE, download_name) Remove downloaded ".zip" files. } \note{ +!!! USE THE FUNCTION WITH CAUTION !!! If \code{remove = TRUE}, ensure that \code{unzip = TRUE}. Choosing to remove ".zip" files without unzipping will retain none of the downloaded data. +then it will remove all files in the second higher level directory. } \keyword{internal} diff --git a/tests/testthat/test-download_functions.R b/tests/testthat/test-download_functions.R index 2bae6ebc..a7c113ae 100644 --- a/tests/testthat/test-download_functions.R +++ b/tests/testthat/test-download_functions.R @@ -1869,7 +1869,9 @@ testthat::test_that("download_sink test", { }) testthat::test_that("download_remove_zips test", { - testfile <- testthat::test_path("../testdata", "yellowstone", "coyote.zip") + testfile <- + testthat::test_path("..", "testdata", "yellowstone/barren", "coyote.zip") + dir.create(dirname(testfile), recursive = TRUE) file.create(testfile, recursive = TRUE) testthat::expect_no_error( download_remove_zips(remove = TRUE, testfile) From bc5c58e0621ebe3a35fefc17e5f48c935a5e1579 Mon Sep 17 00:00:00 2001 From: Insang Song Date: Tue, 18 Jun 2024 15:51:18 -0400 Subject: [PATCH 65/65] Fixing 0.05% short of coverage - Non-testable lines in download_cropscape get nocov --- R/download.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/download.R b/R/download.R index 50835a79..1edf5410 100644 --- a/R/download.R +++ b/R/download.R @@ -3039,6 +3039,7 @@ download_cropscape <- function( # note that this part does not utilize download_unzip # as duplicate file names are across multiple zip files if (download) { + # nocov start if (unzip) { extension <- ifelse(source == "USDA", "\\.zip", "(\\.tar|\\.tar\\.gz)") dir_unzip <- gsub(extension, "", download_names) @@ -3046,12 +3047,12 @@ download_cropscape <- function( archive::archive_extract(download_names[fn], exdir = dir_unzip[fn]) } } + # nocov end } message("Requests were processed.\n") #### 10. remove download commands download_remove_command(commands_txt = commands_txt, remove = remove_command) - } # nolint end