-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
geospatial function updates #527
base: develop
Are you sure you want to change the base?
Conversation
cristinamullin
commented
Sep 18, 2024
- includes all updates from Add non-ATTAINS catchments to GetATTAINS, error handling updates #521
- includes upstream changes from develop & fixes merge conflicts
Co-authored-by: Matt Brousil <[email protected]>
Co-authored-by: Matt Brousil <[email protected]>
Co-authored-by: Matt Brousil <[email protected]>
GetATTAINS changes, error handling updates
arcgislayers is not available on CRAN so this causes check errors. Hopefully it will become available on CRAN again in the future
test-GeospatilFcuntions.R failing lines 112-120 # fetchNHD ---- testthat::test_that( desc = "fetchNHD handles valid input data", code = { valid_data <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(0, 0))), crs = 4326) result <- fetchNHD(.data = valid_data) expect_false(is.null(result)) } )
There is still an issue with using the arcgislayers package as a dependency for the TADA_GetATTAINS function since it is not available on CRAN anymore. I tried to add arcgislayers (and its dependencies arcgis and arcpbf) under "Remotes:" in the description file, but that did not work. e.g., Remotes: github::R-ArcGIS/arcgislayers
It looks like we have two options: 1) we can include the code from arcgislayers directly in TADA instead of importing that package, or 2) we wait for them to get that package back on CRAN (not sure how long that might be, but it only just fell off CRAN last month). https://stackoverflow.com/questions/59549652/how-can-a-package-on-cran-import-a-package-not-on-cran |
@cristinamullin yes it's so unfortunate that they dropped it off CRAN (I think it may have been like the day or two before my initial pull request 😭). I do have confidence that it will be brought back online, but no idea how long that might take... Another option would be for me to use a different downloading approach and remove the arcgislayers package functions for the time being (with the hope of putting it back in place once it's back on CRAN - arcgislayers is by far the fastest way to download geospatial data out there). This would likely take me about a day to complete? In the meantime, I am currently exploring how easy it would be for us to add in (and compartmentalize) the current {arcgis} suite of functions and add to EPATADA. Please stay tuned! |
Ugh, yes very unfortunate timing! We are interested in submitting TADA to CRAN soon... and it could be that this is available on CRAN again by the time we are ready for our submission... so I hate to create additional work but it could also take them a long time. I like the options you mentioned 1) if we can use a different package or 2) add in (and compartmentalize) the current {arcgis} suite of functions and add to EPATADA. Thanks for looking into it! |
fetchNHD <- function(.data, resolution = "Hi", features = "catchments"){ | ||
|
||
suppressMessages(suppressWarnings({ | ||
|
||
sf::sf_use_s2(FALSE) | ||
# If data is already spatial, just make sure it is in the right CRS | ||
if (!is.null(.data) & inherits(.data, "sf")) { | ||
if (sf::st_crs(.data)$epsg != 4326) { | ||
geospatial_data <- .data %>% | ||
sf::st_transform(4326) | ||
} else { | ||
geospatial_data <- .data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchNHD <- function(.data, resolution = "Hi", features = "catchments"){ | |
suppressMessages(suppressWarnings({ | |
sf::sf_use_s2(FALSE) | |
# If data is already spatial, just make sure it is in the right CRS | |
if (!is.null(.data) & inherits(.data, "sf")) { | |
if (sf::st_crs(.data)$epsg != 4326) { | |
geospatial_data <- .data %>% | |
sf::st_transform(4326) | |
} else { | |
geospatial_data <- .data | |
fetchNHD <- function(.data, resolution = "Hi", features = "catchments"){ | |
suppressMessages(suppressWarnings({ | |
sf::sf_use_s2(FALSE) | |
# If data is already spatial, just make sure it is in the right CRS | |
if (!is.null(.data) & inherits(.data, "sf")) { | |
if (sf::st_crs(.data)$epsg != 4326) { | |
geospatial_data <- .data %>% | |
sf::st_transform(4326) | |
} else { | |
geospatial_data <- .data | |
} | |
} else { | |
# ... Otherwise transform into a spatial object then do the same thing: | |
geospatial_data <- .data %>% | |
# convert dataframe to a spatial object | |
TADA_MakeSpatial(.data = ., crs = 4326) %>% | |
dplyr::mutate(geometry_join = geometry) | |
} | |
})) | |
# Reduce WQP data to unique coordinates | |
unique_sites <- dplyr::distinct(geospatial_data, geometry) | |
# If user wants HighRes NHD... | |
if(resolution %in% c("Hi", "hi")){ | |
suppressMessages(suppressWarnings({ | |
feature_downloader <- function(baseurls, sf_bbox) { | |
# starting at feature 1 (i.e., no offset): | |
offset <- 0 | |
# empty list to store all features in | |
all_features <- list() | |
repeat { | |
query <- urltools::param_set(baseurls, key = "geometry", value = sf_bbox) %>% | |
urltools::param_set(key = "inSR", value = 4326) %>% | |
# Total of 2000 features at a time... | |
urltools::param_set(key = "resultRecordCount", value = 2000) %>% | |
# ... starting at the "offset": | |
urltools::param_set(key = "resultOffset", value = offset) %>% | |
urltools::param_set(key = "spatialRel", value = "esriSpatialRelIntersects") %>% | |
urltools::param_set(key = "f", value = "geojson") %>% | |
urltools::param_set(key = "outFields", value = "*") %>% | |
urltools::param_set(key = "geometryType", value = "esriGeometryEnvelope") %>% | |
urltools::param_set(key = "returnGeometry", value = "true") %>% | |
urltools::param_set(key = "returnTrueCurves", value = "false") %>% | |
urltools::param_set(key = "returnIdsOnly", value = "false") %>% | |
urltools::param_set(key = "returnCountOnly", value = "false") %>% | |
urltools::param_set(key = "returnZ", value = "false") %>% | |
urltools::param_set(key = "returnM", value = "false") %>% | |
urltools::param_set(key = "returnDistinctValues", value = "false") %>% | |
urltools::param_set(key = "returnExtentOnly", value = "false") %>% | |
urltools::param_set(key = "featureEncoding", value = "esriDefault") | |
# Fetch features within the offset window and append to list: | |
features <- suppressMessages(suppressWarnings({ | |
tryCatch( | |
{ | |
geojsonsf::geojson_sf(query) | |
}, | |
error = function(e) { | |
NULL | |
} | |
) | |
})) | |
# Exit loop if no more features or error occurred | |
if (is.null(features) || nrow(features) == 0) { | |
break | |
} | |
all_features <- c(all_features, list(features)) | |
# once done, change offset by 2000 features: | |
offset <- offset + 2000 | |
} | |
all_features <- dplyr::bind_rows(all_features) | |
} | |
# bounding box of user's WQP data | |
suppressMessages(suppressWarnings({ | |
bbox_raw <- unique_sites %>% | |
sf::st_buffer(0.0000001) %>% | |
dplyr::rowwise() %>% | |
dplyr::mutate(bbox = purrr::map(geometry, sf::st_bbox)) | |
bbox <- bbox_raw %>% | |
# convert bounding box to characters + encode for use within the API URL | |
dplyr::mutate(text_bbox = urltools::url_encode(toString(bbox))) %>% | |
dplyr::pull(text_bbox) | |
})) | |
# grab nhd catchments and subset to unique site intersection: | |
nhd_catchments <- vector("list") | |
# try(nhd_catchments <- bbox %>% | |
# purrr::map(~feature_downloader(baseurls = "https://hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer/10/query?", sf_bbox = .)) %>% | |
# dplyr::bind_rows() %>% | |
# sf::st_make_valid(), | |
# silent = TRUE) | |
try(nhd_catchments <- bbox %>% | |
purrr::map(~ tryCatch( | |
feature_downloader(baseurls = "https://hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer/10/query?", sf_bbox = .) | |
, | |
error = function(e) NULL)), | |
silent = TRUE) | |
nhd_catchments <- nhd_catchments %>% | |
dplyr::bind_rows() %>% | |
sf::st_make_valid() | |
try(nhd_catchments <- nhd_catchments %>% | |
dplyr::select(nhdplusid, | |
catchmentareasqkm = areasqkm) %>% | |
dplyr::mutate(NHD.nhdplusid = as.character(nhdplusid), | |
NHD.resolution = "HR", | |
NHD.catchmentareasqkm = as.numeric(catchmentareasqkm)) %>% | |
dplyr::select(NHD.nhdplusid, NHD.resolution, NHD.catchmentareasqkm, geometry), silent = TRUE) | |
})) | |
# Empty version of the df will be returned if no associated catchments | |
# to avoid breaking downstream fxns reliant on catchment info. | |
if(nrow(nhd_catchments) == 0 && "catchments" %in% features){ | |
print("No NHD HR features associated with your area of interest.") | |
nhd_catchments <- tibble::tibble(NHD.nhdplusid = character(), | |
NHD.resolution = character(), | |
NHD.catchmentareasqkm = numeric()) | |
} | |
if(nrow(nhd_catchments) == 0 && !"catchments" %in% features){ | |
stop("No NHD HR features associated with your area of interest.") | |
} | |
if(length(features) == 1 && features == "catchments") { | |
return(nhd_catchments) | |
} | |
# Grab flowlines - | |
if("flowlines" %in% features && nrow(nhd_catchments) > 0){ | |
suppressMessages(suppressWarnings({ | |
# # use catchments to grab other NHD features | |
# geospatial_aoi <- nhd_catchments %>% | |
# sf::st_as_sfc() | |
# | |
# # select the layer by id from the items list called above (3 is HR flowlines) | |
# nhd_hr_flowlines <- arcgislayers::get_layer(nhd_hr, 3) | |
# | |
# # use catchments to return associated flowlines | |
# nhd_flowlines <- vector("list", length = length(geospatial_aoi)) | |
# | |
# for(i in 1:length(geospatial_aoi)){ | |
# try(nhd_flowlines[[i]] <- arcgislayers::arc_select(nhd_hr_flowlines, | |
# # where = query, | |
# filter_geom = geospatial_aoi[i], | |
# crs = sf::st_crs(geospatial_aoi[i])) %>% | |
# sf::st_make_valid(), silent = TRUE) | |
# | |
# try(geometry_col <- sf::st_geometry(nhd_flowlines[[i]]) | |
# , silent = TRUE) | |
# | |
# try(nhd_flowlines[[i]] <- nhd_flowlines[[i]] %>% | |
# dplyr::mutate(dplyr::across(dplyr::where(~ !identical(., geometry_col)), ~ as.character(.))) | |
# , silent = TRUE) | |
# } | |
# bounding box of user's WQP data | |
suppressMessages(suppressWarnings({ | |
bbox_raw <- nhd_catchments %>% | |
dplyr::rowwise() %>% | |
dplyr::mutate(bbox = purrr::map(geometry, sf::st_bbox)) | |
bbox <- bbox_raw %>% | |
# convert bounding box to characters + encode for use within the API URL | |
dplyr::mutate(text_bbox = urltools::url_encode(toString(bbox))) %>% | |
dplyr::pull(text_bbox) | |
})) | |
# grab nhd flowlines | |
nhd_flowlines <- vector("list") | |
nhd_flowlines <- bbox %>% | |
purrr::map(~ tryCatch( | |
feature_downloader(baseurls = "https://hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer/3/query?", sf_bbox = .) %>% | |
dplyr::mutate_at(dplyr::vars(-("geometry")), as.character), | |
error = function(e) NULL # Return NULL on error to suppress it | |
)) | |
nhd_flowlines <- nhd_flowlines %>% | |
purrr::keep(~!is.null(.)) %>% | |
purrr::keep(~!is.character(.)) | |
try(nhd_flowlines <- nhd_flowlines %>% | |
dplyr::bind_rows() %>% | |
dplyr::distinct(), | |
silent = TRUE) | |
})) | |
if(length(features) == 1 && features == "flowlines") { | |
if(length(nhd_flowlines) == 0 || is.null(nhd_flowlines)){print("There are no NHD flowlines associated with your area of interest.")} | |
return(nhd_flowlines) | |
} | |
if(length(nhd_flowlines) == 0 || is.null(nhd_flowlines)){print("There are no NHD flowlines associated with your area of interest.")} | |
} | |
# Grab waterbodies - | |
if("waterbodies" %in% features & nrow(nhd_catchments) > 0){ | |
suppressMessages(suppressWarnings({ | |
# geospatial_aoi <- nhd_catchments %>% | |
# sf::st_as_sfc() | |
# | |
# # select the layer by id from the items list called above (9 is HR waterbodies) | |
# nhd_hr_waterbodies <- arcgislayers::get_layer(nhd_hr, 9) | |
# | |
# # use bbox to return associated waterbodies | |
# nhd_waterbodies <- vector("list", length = length(geospatial_aoi)) | |
# | |
# for(i in 1:length(geospatial_aoi)){ | |
# try(nhd_waterbodies[[i]] <- arcgislayers::arc_select(nhd_hr_waterbodies, | |
# # where = query, | |
# filter_geom = geospatial_aoi[i], | |
# crs = sf::st_crs(geospatial_aoi[i])) %>% | |
# sf::st_make_valid(), silent = TRUE) | |
# | |
# try(geometry_col <- sf::st_geometry(nhd_waterbodies[[i]]) | |
# , silent = TRUE) | |
# | |
# try(nhd_waterbodies[[i]] <- nhd_waterbodies[[i]] %>% | |
# dplyr::mutate(dplyr::across(dplyr::where(~ !identical(., geometry_col)), ~ as.character(.))) | |
# , silent = TRUE) | |
# | |
# } | |
# bounding box of user's WQP data | |
suppressMessages(suppressWarnings({ | |
bbox_raw <- nhd_catchments %>% | |
dplyr::rowwise() %>% | |
dplyr::mutate(bbox = purrr::map(geometry, sf::st_bbox)) | |
bbox <- bbox_raw %>% | |
# convert bounding box to characters + encode for use within the API URL | |
dplyr::mutate(text_bbox = urltools::url_encode(toString(bbox))) %>% | |
dplyr::pull(text_bbox) | |
})) | |
# grab nhd waterbodies | |
nhd_waterbodies <- vector("list") | |
nhd_waterbodies <- bbox %>% | |
purrr::map(~ tryCatch( | |
feature_downloader(baseurls = "https://hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer/9/query?", sf_bbox = .) %>% | |
dplyr::mutate_at(dplyr::vars(-("geometry")), as.character), | |
error = function(e) NULL # Return NULL on error to suppress it | |
)) | |
nhd_waterbodies <- nhd_waterbodies %>% | |
purrr::keep(~!is.null(.)) %>% | |
purrr::keep(~!is.character(.)) | |
try(nhd_waterbodies <- nhd_waterbodies %>% | |
dplyr::bind_rows() %>% | |
dplyr::distinct(), | |
silent = TRUE) | |
})) | |
if(length(features) == 1 && features == "waterbodies") { | |
if(length(nhd_waterbodies) == 0 || is.null(nhd_waterbodies)){print("There are no NHD waterbodies associated with your area of interest.")} | |
return(nhd_waterbodies) | |
} | |
if(length(nhd_waterbodies) == 0 || is.null(nhd_waterbodies)){print("There are no NHD waterbodies associated with your area of interest.")} | |
} | |
# Combinations of features selected, and what they return: | |
if(length(features) == 2 && "catchments" %in% features && "flowlines" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_flowlines" = nhd_flowlines) | |
return(nhd_list) | |
} else if(length(features) == 2 && "catchments" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_waterbodies" = nhd_waterbodies) | |
return(nhd_list) | |
} else if(length(features) == 2 && "flowlines" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_flowlines" = nhd_flowlines, | |
"NHD_waterbodies" = nhd_waterbodies) | |
return(nhd_list) | |
} else if(length(features) == 3 && "catchments" %in% features && "flowlines" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_flowlines" = nhd_flowlines, | |
"NHD_waterbodies" = nhd_waterbodies) | |
} else {stop("Please select between 'catchments', 'flowlines', 'waterbodies', or any combination for `feature` argument.")} | |
# If user wants NHDPlus V2... | |
} else if(resolution %in% c("Med", "med")){ | |
suppressMessages(suppressWarnings({ | |
nhd_catchments <- vector("list", length = nrow(unique_sites)) | |
for(i in 1:nrow(unique_sites)){ | |
# Use {nhdplusTools} to grab associated catchments... | |
try(nhd_catchments[[i]] <- nhdplusTools::get_nhdplus(AOI = unique_sites[i,], realization = "catchment") %>% | |
sf::st_make_valid() %>% | |
dplyr::select(comid = featureid, | |
catchmentareasqkm = areasqkm) %>% | |
dplyr::mutate(NHD.comid = as.character(comid), | |
NHD.resolution = "nhdplusV2", | |
NHD.catchmentareasqkm = as.numeric(catchmentareasqkm)) %>% | |
dplyr::select(NHD.comid, NHD.resolution, NHD.catchmentareasqkm, geometry) | |
, silent = TRUE) | |
} | |
nhd_catchments <- nhd_catchments %>% | |
purrr::keep(~!is.null(.)) | |
try(nhd_catchments <- dplyr::bind_rows(nhd_catchments) %>% | |
dplyr::distinct(), silent = TRUE) | |
# if NHD catchments are not in the correct CRS, transform them | |
try(if (sf::st_crs(nhd_catchments) != sf::st_crs(geospatial_data)) { | |
nhd_catchments <- nhd_catchments %>% | |
sf::st_transform(sf::st_crs(geospatial_data)$epsg) | |
}, silent = TRUE) | |
})) | |
if(nrow(nhd_catchments) == 0 && "catchments" %in% features){ | |
print("No NHDPlus V2 features associated with your WQP observations.") | |
nhd_catchments <- tibble::tibble(NHD.comid = character(), | |
NHD.resolution = character(), | |
NHD.catchmentareasqkm = numeric()) | |
} | |
if(nrow(nhd_catchments) == 0 && !"catchments" %in% features){ | |
stop("No NHDPlus V2 features associated with your WQP observations.") | |
} | |
if(length(features) == 1 && features == "catchments") { | |
return(nhd_catchments) | |
} | |
# Grab flowlines - | |
if("flowlines" %in% features && nrow(nhd_catchments) > 0){ | |
suppressMessages(suppressWarnings({ | |
nhd_flowlines <- vector("list", length = nrow(nhd_catchments)) | |
# use catchments to grab other NHD features: | |
unique_sites <- nhd_catchments | |
for(i in 1:nrow(unique_sites)){ | |
# Use {nhdplusTools} to grab associated flowlines... | |
try(nhd_flowlines[[i]] <- nhdplusTools::get_nhdplus(AOI = unique_sites[i,], realization = "flowline") %>% | |
sf::st_make_valid() | |
, silent = TRUE) | |
try(geometry_col <- sf::st_geometry(nhd_flowlines[[i]]) | |
, silent = TRUE) | |
try(nhd_flowlines[[i]] <- nhd_flowlines[[i]] %>% | |
dplyr::mutate(dplyr::across(dplyr::where(~ !identical(., geometry_col)), ~ as.character(.))) | |
, silent = TRUE) | |
} | |
nhd_flowlines <- nhd_flowlines %>% | |
purrr::keep(~!is.null(.)) | |
try(nhd_flowlines <- dplyr::bind_rows(nhd_flowlines)) %>% | |
dplyr::distinct() | |
# if NHD flowlines are not in the correct CRS, transform them | |
try(if (sf::st_crs(nhd_flowlines) != sf::st_crs(geospatial_data)) { | |
nhd_flowlines <- nhd_flowlines %>% | |
sf::st_transform(sf::st_crs(geospatial_data)$epsg) | |
}, silent = TRUE) | |
})) | |
if(nrow(nhd_flowlines) == 0 && "flowlines" %in% features){ | |
print("No NHDPlus V2 flowlines associated with your WQP observations.") | |
} | |
if(length(features) == 1 && features == "flowlines") { | |
return(nhd_flowlines) | |
} | |
} | |
# Grab waterbodies - | |
if("waterbodies" %in% features && nrow(nhd_catchments) > 0){ | |
suppressMessages(suppressWarnings({ | |
nhd_waterbodies <- vector("list", length = nrow(nhd_catchments)) | |
# use catchments to grab other NHD features: | |
unique_sites <- nhd_catchments | |
for(i in 1:nrow(unique_sites)){ | |
# Use {nhdplusTools} to grab associated flowlines... | |
try(nhd_waterbodies[[i]] <- nhdplusTools::get_waterbodies(AOI = unique_sites[i,]) %>% | |
sf::st_make_valid() | |
, silent = TRUE) | |
try(geometry_col <- sf::st_geometry(nhd_waterbodies[[i]]) | |
, silent = TRUE) | |
try(nhd_waterbodies[[i]] <- nhd_waterbodies[[i]] %>% | |
dplyr::mutate(dplyr::across(dplyr::where(~ !identical(., geometry_col)), ~ as.character(.))) | |
, silent = TRUE) | |
} | |
nhd_waterbodies <- nhd_waterbodies %>% | |
purrr::keep(~!is.null(.)) | |
try(nhd_waterbodies <- dplyr::bind_rows(nhd_waterbodies) %>% | |
dplyr::distinct(), | |
silent = TRUE) | |
# if NHD waterbodies are not in the correct CRS, transform them | |
try(if (sf::st_crs(nhd_waterbodies) != sf::st_crs(geospatial_data)) { | |
nhd_waterbodies <- nhd_waterbodies %>% | |
sf::st_transform(sf::st_crs(geospatial_data)$epsg) | |
}, silent = TRUE) | |
})) | |
if(nrow(nhd_waterbodies) == 0 && "waterbodies" %in% features){ | |
print("No NHDPlus V2 waterbodies associated with your WQP observations.") | |
} | |
if(length(features) == 1 && features == "waterbodies") { | |
return(nhd_waterbodies) | |
} | |
} | |
# Combinations of features selected, and what they return: | |
if(length(features) == 2 && "catchments" %in% features && "flowlines" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_flowlines" = nhd_flowlines) | |
return(nhd_list) | |
} else if(length(features) == 2 && "catchments" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_waterbodies" = nhd_waterbodies) | |
return(nhd_list) | |
} else if(length(features) == 2 && "flowlines" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_flowlines" = nhd_flowlines, | |
"NHD_waterbodies" = nhd_waterbodies) | |
return(nhd_list) | |
} else if(length(features) == 3 && "catchments" %in% features && "flowlines" %in% features && "waterbodies" %in% features){ | |
nhd_list <- list("NHD_catchments" = nhd_catchments, | |
"NHD_flowlines" = nhd_flowlines, | |
"NHD_waterbodies" = nhd_waterbodies) | |
} else {stop("Please select between 'catchments', 'flowlines', 'waterbodies', or any combination for `feature` argument.")} | |
} else { | |
stop('User-supplied resolution unavailable. Please select between "Med" or "Hi".') | |
} | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion should completely replace all code that makes up fetchNHD
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cristinamullin - I wasn't able to get the required code in {arcgislayers} to run properly when added to EPATADA directly. 😞 So instead I have written new "placeholder" functions that do not use anything from {arcgislayers}, and are subsequently a bit slower...
I also just commented out the original functions that used {arcgislayers} so that it will be easier for us to put it back in place when the package is put back on CRAN.
Please let me know how best to incorporate these changes: currently they are just code suggestions. (I was having difficulty fetching the ROSSyndicate-review branch, but I'm also not super branch savvy. I will touch base with my team Monday and hopefully have something a bit more robust if needed.)
I'm so sorry for all the mess associated with this PR!