diff --git a/.gitignore b/.gitignore index 357de0bde..0877d1373 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ whiteboxR.Rproj *.shx *.prj *.dbf +*.cpg *.tif settings.json __MACOSX diff --git a/DESCRIPTION b/DESCRIPTION index 98cd46f93..5ca18ae10 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,16 +1,16 @@ Package: whitebox Type: Package Title: 'WhiteboxTools' R Frontend -Version: 2.4.1 +Version: 2.4.2 Description: An R frontend for the 'WhiteboxTools' library, which is an advanced geospatial data analysis platform developed by Prof. John Lindsay at the University of Guelph's Geomorphometry and Hydrogeomatics Research Group. 'WhiteboxTools' can be used to perform common geographical information systems (GIS) analysis operations, such as cost-distance analysis, distance buffering, and raster reclassification. Remote sensing and image processing tasks include image enhancement (e.g. panchromatic sharpening, contrast adjustments), image mosaicing, numerous filtering operations, simple classification (k-means), and common image transformations. 'WhiteboxTools' also contains advanced tooling for spatial hydrological analysis (e.g. flow-accumulation, watershed delineation, stream network analysis, sink removal), terrain analysis (e.g. common terrain indices such as slope, curvatures, wetness index, hillshading; hypsometric analysis; multi-scale topographic position analysis), and LiDAR data processing. Suggested citation: Lindsay (2016) . Authors@R: c(person("Qiusheng", "Wu", email = "giswqs@gmail.com", role = c("aut")), - person("Andrew", "Brown", email = "brown.andrewg@gmail.com", role = c("ctb", "cre"))) + person("Andrew", "Brown", email = "brown.andrewg@gmail.com", role = c("aut", "cre"), comment=c(ORCID="0000-0002-4565-533X"))) Maintainer: Andrew Brown License: MIT + file LICENSE SystemRequirements: WhiteboxTools (https://github.com/jblindsay/whitebox-tools/releases/latest) Encoding: UTF-8 Language: en-US -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Roxygen: list(markdown = TRUE) URL: https://whiteboxr.gishub.org/, https://github.com/opengeos/whiteboxR BugReports: https://github.com/opengeos/whiteboxR/issues diff --git a/NAMESPACE b/NAMESPACE index 0cb2aaa3c..22a0ca1ea 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,7 @@ S3method(wbt_result,wbt_result) export(check_whitebox_binary) export(install_whitebox) export(sample_dem_data) +export(sample_soils_data) export(wbt) export(wbt_absolute_value) export(wbt_accumulation_curvature) diff --git a/NEWS.md b/NEWS.md index 86fdf56e0..bc83e1a17 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,17 @@ +# whitebox 2.4.2 + + * `wbt_source()`: now accepts `tmpdir` argument which defaults to `tempdir()` (not `getwd()` or `wbt_wd()`) that is used for storing the intermediate shapefiles needed for WhiteboxTools + + * Also, the pattern for temporary file names is now customizable via `pattern` argument + + * Bug fixes for `wbt_source()`: + + * No longer writes temporary intermediate files to the working directory when passed a non-shapefile vector or non-GeoTIFF raster data source. + + * The temporary directory is used by default, unless new `tmpdir` argument is specified. This could be a breaking change if you were relying on the temporary files to be present in the WhiteboxTools working directory. Specify `tmpdir` in call to `wbt_source()` to make old behavior explicit. + + * Properly uses `layer` argument for data sources (e.g. GPKG) that may contain multiple vector layers or raster bands of interest (thanks to @mps9506 for reporting; #132) + # whitebox 2.4.1 * Rebuilt `wbttools` and `wbttoolparameters` with WhiteboxTools v2.4.0 diff --git a/R/wbt.R b/R/wbt.R index dad564493..c4924749f 100644 --- a/R/wbt.R +++ b/R/wbt.R @@ -1,5 +1,5 @@ #' Initialize 'WhiteboxTools' -#' +#' #' `wbt_init()`: Check if a suitable 'WhiteboxTools' executable is present. Search default path in package directory or set it manually with `exe_path`. #' #' @param exe_path Default `exe_path` is result of `wbt_exe_path()` which checks a few user-settable options before defaulting to the package installation directory sub-directory "WBT". May be overridden if a custom path is needed. @@ -40,11 +40,11 @@ wbt_init <- function(exe_path = wbt_exe_path(shell_quote = FALSE), !is.null(wd) || !is.null(verbose) || !is.null(compress_rasters)) { - + if (!is.null(wd) && length(wd) > 0 && (is.na(wd) || wd == "")) { .wbt_wd_unset() } - + # set the path with wbt_options wbt_options(exe_path = exe_path, ...) } @@ -61,7 +61,7 @@ wbt_init <- function(exe_path = wbt_exe_path(shell_quote = FALSE), if (wbt_verbose()) { message("WhiteboxTools Executable Path (whitebox.exe_path) reverted to:\n\t", new_exe_path) } - } + } if (check_version) { # check version info, provide ONE message per session if mismatched exv <- try(wbt_version(extract = TRUE), silent = TRUE) @@ -81,7 +81,7 @@ wbt_init <- function(exe_path = wbt_exe_path(shell_quote = FALSE), warned <- FALSE } if (wbt_verbose() && isFALSE(warned)) { - message("NOTE: Installed WhiteboxTools version (", exv, + message("NOTE: Installed WhiteboxTools version (", exv, ") is ", ifelse(exv > pkv, "newer", "older"), " than the package data (", pkv, ").") try(assign("whitebox.warned_version_difference", @@ -101,7 +101,7 @@ wbt_init <- function(exe_path = wbt_exe_path(shell_quote = FALSE), #' #' - **`whitebox.wd`** - character. Path to WhiteboxTools working directory. Used as `--wd` argument for tools that support it when `wd` is not specified elsewhere. #' -#' - **`whitebox.verbose`** - logical. Should standard output from calls to executable be `cat()` out for readability? When `whitebox.verbose=FALSE` no output is produced. Set the value of `whitebox.verbose` with `wbt_verbose()` `verbose` argument. Default is result of `interactive()` if R package options are unset. +#' - **`whitebox.verbose`** - logical. Should standard output from calls to executable be `cat()` out for readability? When `whitebox.verbose=FALSE` no output is produced. Set the value of `whitebox.verbose` with `wbt_verbose()` `verbose` argument. Default is result of `interactive()` if R package options are unset. #' #' - **`whitebox.compress_rasters`** - logical. Should raster output from WhiteboxTools be compressed? Default: `NULL` uses existing WhiteboxTools settings. Set the value of `whitebox.compress_rasters` with `wbt_compress_rasters()` `compress_rasters` argument. #' @@ -302,10 +302,10 @@ wbt_data_dir <- function() { #' \dontrun{ #' #' ## wbt_wd(): -#' +#' #' # no working directory set #' wbt_wd(wd = "") -#' +#' #' # set WBT working directory to R working directory #' wbt_wd(wd = getwd()) #' } @@ -325,7 +325,7 @@ wbt_wd <- function(wd = NULL) { if (nchar(syswd) > 0 && dir.exists(syswd)) { return(syswd) } - + # package option checked next; if missing default is getwd() (unspecified should be same as getwd()) res <- getOption("whitebox.wd") @@ -547,7 +547,7 @@ wbt_install <- function(pkg_dir = wbt_data_dir(), platform = NULL, force = FALSE if (.Machine$sizeof.pointer != 8) { return(invisible(.unsupported())) } - + if (missing(platform) || is.null(platform)) { if (os == "Linux") { url <- "https://www.whiteboxgeo.com/WBT_Linux/WhiteboxTools_linux_amd64.zip" @@ -563,11 +563,11 @@ wbt_install <- function(pkg_dir = wbt_data_dir(), platform = NULL, force = FALSE return(invisible(.unsupported())) } } else { - # supports alternative platforms/filenames + # supports alternative platforms/filenames # e.g. linux_musl, darwin_m_series url <- paste0("https://www.whiteboxgeo.com/WBT_", os, "/WhiteboxTools_", - platform, ".zip") + platform, ".zip") } filename <- basename(url) @@ -609,7 +609,7 @@ wbt_install <- function(pkg_dir = wbt_data_dir(), platform = NULL, force = FALSE ex_dir <- file.path(pkg_dir, gsub("\\.zip$", "", basename(exe_zip))) file.copy(file.path(ex_dir, "WBT"), pkg_dir, recursive = TRUE) file.remove(list.files(ex_dir, recursive = TRUE, full.names = TRUE)) - + # subfolder WBT/whitebox_tools exe_path_out <- file.path(pkg_dir, "WBT", basename(exe_path)) Sys.chmod(exe_path_out, '755') @@ -646,11 +646,11 @@ wbt_install <- function(pkg_dir = wbt_data_dir(), platform = NULL, force = FALSE # many packages provide an "install_*" method; alias wbt_install mirrors the wbt_ prefix for most operations. Documentation refers to install_whitebox() #' Download and Install 'WhiteboxTools' -#' +#' #' This function downloads the 'WhiteboxTools' binary if needed. Pre-compiled binaries are #' only available for download for 64-bit Linux (default compiled with glibc on Ubuntu 22.04; #' use `platform="linux_musl"` for musl/earlier versions of glibc), Windows and Mac OS (ARM and -#' Intel) platforms. If you need WhiteboxTools for another platform follow the instructions to +#' Intel) platforms. If you need WhiteboxTools for another platform follow the instructions to #' build from source: \url{https://github.com/jblindsay/whitebox-tools} #' #' 'WhiteboxTools' and all of its extensions can be uninstalled by passing the `remove=TRUE` argument. @@ -703,13 +703,13 @@ wbt_install_extension <- function(extension = c( "MacOS_Intel")) } else { # non-default options include: linux_musl, MacOS_ARM - sufx <- platform + sufx <- platform } - + if (sn == "Darwin" && Sys.info()["machine"] == "arm64") { suffix <- "MacOS_ARM" } - + # GTE if ("GeneralToolsetExtension" %in% extension) { url <- sprintf("https://www.whiteboxgeo.com/GTE_%s/%s_%s.zip", sn, "GeneralToolsetExtension", sufx) @@ -726,7 +726,7 @@ wbt_install_extension <- function(extension = c( } #' Activate 'WhiteboxTools' Extensions -#' +#' #' @param email Email Address #' @param activation_key Activation Key #' @param seat Seat Number (Default `1`) @@ -744,7 +744,7 @@ wbt_activate <- function(email, activation_key, seat = 1, } #' Help description for 'WhiteboxTools' -#' +#' #' @return Returns the help description for 'WhiteboxTools' as an R character vector. #' @export #' @keywords General @@ -763,7 +763,7 @@ wbt_help <- function() { #' License information for 'WhiteboxTools' -#' +#' #' @return Returns the license information for WhiteboxTools as an R character vector. #' @export #' @keywords General @@ -804,7 +804,7 @@ wbt_version <- function(extract = FALSE) { } #' All available tools in 'WhiteboxTools' -#' +#' #' @param keywords Keywords may be used to search available tools. Default `"''"` returns all available tools. #' #' @return Return all available tools in WhiteboxTools that contain the keywords. @@ -826,7 +826,7 @@ wbt_list_tools <- function(keywords = "''") { #' The toolbox for a specific tool in WhiteboxTools -#' +#' #' Retrieve the toolbox for a specific tool. #' #' @param tool_name The name of the tool. @@ -857,7 +857,7 @@ wbt_toolbox <- function(tool_name = NULL) { #' Help description for a specific tool in 'WhiteboxTools' -#' +#' #' Retrieves the help description for a specific tool. #' #' @param tool_name The name of the tool. @@ -893,7 +893,7 @@ wbt_tool_help <- function(tool_name = NULL) { #' #' @return Returns the tool parameter descriptions for a specific tool. #' @export -#' @keywords General +#' @keywords General #' #' @examples #' \dontrun{ @@ -914,8 +914,8 @@ wbt_tool_parameters <- function(tool_name, quiet = FALSE) { #' @param tool_name Name of the tool. #' @param viewer Show source code in browser? default: `TRUE` #' @return Returns a GitHub URL to view the source code of the tool. -#' @export -#' @keywords General +#' @export +#' @keywords General #' #' @examples #' \dontrun{ @@ -943,7 +943,7 @@ wbt_view_code <- function(tool_name, viewer = FALSE) { #' @param command_only Return command that would be run with `system()`? Default: `FALSE` #' #' @return Returns the (character) output of the tool. -#' @export +#' @export #' @keywords General #' @seealso \link{wbt_list_tools} #' @examples @@ -1011,16 +1011,16 @@ wbt_internal_tool_name <- function(tool_name) { } wbt_match_tool_name <- function(tool_name, result = c('tool_name', 'function_name')) { - + wbttools <- NULL load(system.file("data/wbttools.rda", package = "whitebox")) - + result <- match.arg(result, choices = c('tool_name', 'function_name'), several.ok = TRUE) - + idx <- match(tolower(wbttools$tool_name), tolower(gsub("[ _]", "", tool_name))) - + wbttools[idx[which(!is.na(idx))], result, drop = FALSE] - + } #' Wrapper method for `system()` calls of `whitebox_tools` @@ -1039,7 +1039,7 @@ wbt_system_call <- function(argstring, command_only = FALSE, ignore.stderr = FALSE, shell_quote = TRUE, - check_version = TRUE, + check_version = TRUE, ...) { wbt_init(..., check_version = check_version) @@ -1125,13 +1125,13 @@ wbt_system_call <- function(argstring, # support for path expansion in input/output file arguments #' Prepare File Paths for WhiteboxTools Commands -#' -#' Performs path expansion with `path.expand()` and shell quotes with `shQuote()` the input paths. -#' -#' @details If an input vector contains `";"` or `","` this is considered, path expansion is performed on the substrings. If the input vector has length greater than `1`, the vector is concatenated with `","` or `";"` to create a single output string. -#' +#' +#' Performs path expansion with `path.expand()` and shell quotes with `shQuote()` the input paths. +#' +#' @details If an input vector contains `";"` or `","` this is considered, path expansion is performed on the substrings. If the input vector has length greater than `1`, the vector is concatenated with `","` or `";"` to create a single output string. +#' #' @param x character or `terra` object. Vector of file paths or strings of file paths for passing as arguments to WhiteboxTools. If the object is of class `SpatRaster`, `SpatRasterCollection`, `SpatVector` or `SpatVectorProxy` the sources are extracted with `terra::sources()` -#' +#' #' @param shell_quote logical. Shell quotes around result? Default: `TRUE` #' @param delimiter character. Either `","` (default) or `";"` allowed by WhiteboxTools. #' @param check_exists logical. Check if file(s) in x exist? Useful for input values. Default: `FALSE` @@ -1142,27 +1142,27 @@ wbt_system_call <- function(argstring, #' @keywords General #' #' @examples -#' +#' #' wbt_file_path("./abc.tif") -#' +#' #' wbt_file_path("./abc.tif;./def.tif") -#' +#' #' wbt_file_path("./abc.tif,./def.tif") -#' +#' #' wbt_file_path(c("./abc.tif", "./def.tif")) -#' +#' #' wbt_file_path("~/abc.tif", shell_quote = FALSE) -#' +#' #' wbt_file_path(c("~/abc.tif", "~/def.tif")) -#' +#' wbt_file_path <- function(x, shell_quote = TRUE, delimiter = ",", check_exists = FALSE) { if (inherits(x, c("RasterLayer", "RasterStack"))) { if (requireNamespace("terra")) { x <- terra::rast(x) } } - - if (inherits(x, c('SpatRaster','SpatRasterCollection', + + if (inherits(x, c('SpatRaster','SpatRasterCollection', 'SpatVector', 'SpatVectorProxy'))) { if (requireNamespace("terra")) { x2 <- paste0(terra::sources(x), collapse = delimiter) @@ -1172,7 +1172,7 @@ wbt_file_path <- function(x, shell_quote = TRUE, delimiter = ",", check_exists = x <- x2 } } - + delimiter <- match.arg(trimws(delimiter), c(",", ";")) x <- path.expand(strsplit( paste0(as.character(x), collapse = ","), ";|," @@ -1180,7 +1180,7 @@ wbt_file_path <- function(x, shell_quote = TRUE, delimiter = ",", check_exists = if (check_exists) { y <- !file.exists(x) if (any(y)) { - stop(sprintf("File%s not found: %s", + stop(sprintf("File%s not found: %s", ifelse(sum(y) > 1, "s",""), paste0(x[y], collapse = ", ")), call. = FALSE) @@ -1190,48 +1190,6 @@ wbt_file_path <- function(x, shell_quote = TRUE, delimiter = ",", check_exists = if (shell_quote) shQuote(x) else x } -#' Convenience method for path to sample DEM -#' -#' Get a file path to DEM.tif stored in extdata subfolder of whitebox package installation directory. If needed, download the TIFF file from GitHub. -#' -#' @param destfile Path to target location of sample data. Will be downloaded if does not exist. Defaults to file path of extdata subfolder of whitebox package installation directory. -#' @param ... additional arguments to download.file() -#' -#' @return character. -#' @export -#' @keywords General datasets -#' -#' @examples -#' -#' if (check_whitebox_binary()) { -#' wbt_slope(sample_dem_data(), output = "slope.tif") -#' } -#' unlink(c('slope.tif', 'settings.json')) -#' @importFrom utils download.file -sample_dem_data <- function(destfile = file.path(system.file('extdata', package="whitebox"), 'DEM.tif'), ...) { - if (missing(destfile)) { - fp <- system.file("extdata/DEM.tif", package = "whitebox")[1] - } else { - if (!file.exists(destfile)) { - fp <- "" - } else { - fp <- destfile - } - } - if (fp == "") { - try(download.file("https://github.com/opengeos/whiteboxR/raw/master/inst/extdata/DEM.tif", - destfile = destfile, - mode = "wb", ...)) - if (missing(destfile)) { - fp <- system.file("extdata/DEM.tif", package = "whitebox")[1] - } else { - if (file.exists(destfile)) { - fp <- destfile - } - } - } - fp -} #' Convenience method for setting RUST_BACKTRACE options for debugging #' diff --git a/R/wbt_source.R b/R/wbt_source.R index f2b186424..5d8c6e56a 100644 --- a/R/wbt_source.R +++ b/R/wbt_source.R @@ -1,49 +1,146 @@ #' Initialize an R object containing spatial data for use by WhiteboxTools #' -#' @param x A terra SpatVector or sf object, or a path to a file that can be read as a SpatVectorProxy +#' @param x A terra SpatVector or sf object (in memory) or a path to a file that +#' can be read as a SpatVectorProxy. Or a memory or file-based SpatRaster. +#' When `x` has multiple layers/bands, the first layer is used by default; use +#' the \code{layer} argument to select a specific layer/band. #' @param dsn Data source path / file name -#' @param layer Data layer -#' @param force Force write of vector data to file? Default: FALSE (only write if file does not exist) -#' @param ... Additional arguments passed to `terra::writeVector()` or `sf::st_write()` +#' @param layer Data layer. For vectors, `layer` is interpreted as a layer +#' name (character); for rasters, `layer` is interpreted as a band index or +#' name (integer OR character) +#' @param tmpdir Directory to write temporary ESRI Shapefiles for vector input +#' in memory or otherwise not already in shapefile. Default: `tempdir()` +#' @param pattern Character vector giving the initial part of the temporary file +#' name +#' @param force Force write of vector data to file? Default: FALSE (only write +#' if file does not exist and new file is needed) #' @param verbose Print information about data source and contents? -#' @return An R object with attributes `wbt_dsn` and `wbt_layer` set as needed to support reading and writing R objects from file by WhiteboxTools. +#' @param ... Additional arguments passed to `terra::writeVector()` or +#' `sf::st_write()`, or `terra::writeRaster` (for rasters). +#' @return An R object (SpatRaster, SpatVector, SpatVectorProxy, sf) with +#' attributes `wbt_dsn` and `wbt_layer` set as needed to support reading and +#' writing R objects from file by WhiteboxTools. #' @keywords General #' @export wbt_source <- function(x, dsn = NULL, layer = NULL, force = FALSE, + tmpdir = tempdir(), + pattern = "wbt", verbose = wbt_verbose(), ...) { - if (!requireNamespace("terra")) { - stop("package `terra` is required to convert vector sources to `wbt()`-compatible SpatVectorProxy", call. = FALSE) + if (length(layer) > 1) { + stop("argument `layer` must have length 1 or 0 (NULL)", call. = FALSE) } - if (is.character(x)) { - if (file.exists(x)) { - # convert to shapefile if needed - if (!grepl("\\.shp$", x)) { - xp <- paste0(basename(x), "_", basename(tempfile()), ".shp") - fp <- file.path(tempdir(), xp) + .check_pkg_ns <- function(pkg) { + if (!requireNamespace(pkg, quietly = TRUE)) { + stop("package `", pkg, "` is required to convert to `wbt()`-compatible data sources", call. = FALSE) + } + } - if (!requireNamespace("terra")) { - stop("package `terra` is required to convert non-Shapefile vector sources to Shapefile") + .first_source <- function(x) { + src <- terra::sources(x) + if (length(src) > 0 && any(nzchar(src))) { + if (length(src) > 1) { + if (verbose) { + message("object 'x' has multiple source files; using first non-empty source path") } + } + src <- src[which(nzchar(src))[1]] + } + src + } + + if (is.character(x)) { + if (file.exists(x)) { + .check_pkg_ns("terra") - x2 <- terra::vect(x, layer = ifelse(is.null(layer), "", layer)) - if (terra::writeVector(x2, fp)) { + # convert to shapefile if needed + x2 <- try(terra::vect(x, layer = ifelse(is.null(layer), "", layer), proxy = TRUE), silent = TRUE) + fp <- file.path(tmpdir, paste0(basename(x), "_", basename(tempfile(pattern = pattern)))) + if (!inherits(x2, 'try-error') && !grepl("\\.shp$", x, ignore.case = TRUE)) { + fp <- paste0(fp, ".shp") + res <- try(terra::writeVector(terra::query(x2), fp), silent = !verbose) + if (!inherits(res, 'try-error') && file.exists(fp)) { x <- fp } else { - stop("Failed to convert `x` (", x, ") to Shapefile.") + stop("Failed to write `x` (", x, ") to Shapefile: ", fp, "\n", res[1], call. = FALSE) } + } else if (inherits(x2, 'try-error')) { + is_geotiff <- grepl("\\.tiff?$", x, ignore.case = TRUE) + + # check if we need to write a new file + # - not a geotiff OR + # - a layer is specified that is not the first layer + write_new_file <- !is_geotiff || + (length(layer) > 0 && + layer[1] != 1 && + layer[1] != names(terra::rast(x))[1]) + + if (write_new_file) { + # try reading a raster file and writing to geotiff + fp <- paste0(fp, ".tif") + if (length(layer) > 0) { + x2 <- terra::rast(x, lyrs = layer[1]) + } else { + x2 <- terra::rast(x) + } + res <- try(terra::writeRaster(x2, fp), silent = TRUE) + if (!inherits(res, 'try-error') && file.exists(fp)) { + x <- fp + } else { + stop( + "Failed to write `x` (", + x, + ") to GeoTIFF: ", + fp, + "\n", + res, + call. = FALSE + ) + } + } + x <- terra::rast(x) + } + + if (!inherits(x, 'SpatRaster')) { + # a SpatVectorProxy allows us to get some basic info without loading the whole file + x <- terra::vect(x, proxy = TRUE) } - # a SpatVectorProxy allows us to get some basic info without loading the whole file - x <- terra::vect(x, proxy = TRUE) - attr(x, 'wbt_dsn') <- terra::sources(x) + if (is.character(x) && !file.exists(x)) { + stop("File (", x, ") does not exist", call. = FALSE) + } + + if (!inherits(x, c("SpatRaster", "SpatVectorProxy"))) { + stop("Unhandled input object type: ", paste(class(x), collapse = ", ")) + } + + attr(x, 'wbt_dsn') <- .first_source(x) attr(x, 'wbt_layer') <- layer return(x) + } else { + stop("File (", x, ") does not exist", call. = FALSE) + } + } + + ext <- ".shp" + if (inherits(x, c('SpatRaster', 'RasterLayer', + 'RasterStack', 'RasterBrick'))) { + .check_pkg_ns("terra") + if (!inherits(x, 'SpatRaster')) { + x <- terra::rast(x) + } + ext <- ".tif" + + if (is.null(dsn)) { + src <- .first_source(x) + if (nzchar(src)) { + dsn <- src + } } } @@ -57,35 +154,52 @@ wbt_source <- function(x, # only supported vector format is the ESRI Shapefile. # TODO: dbf limitations? use alternate wbt/gdal common format? if (!is.null(layer)) { - bn <- layer - } else bn <- "file" - wd <- wbt_wd() - if (wd == "") - wd <- getwd() - ext <- ".shp" - if (inherits(x, 'SpatRaster') || - inherits(x, 'RasterLayer') || - inherits(x, 'RasterStack') || - inherits(x, 'RasterBrick')) { - ext <- ".tif" + bn <- paste0(pattern, "_", layer) + } else { + bn <- pattern } - dsn <- tempfile(pattern = bn, tmpdir = wd, fileext = ext) + dsn <- tempfile(pattern = bn, tmpdir = tmpdir, fileext = ext) # } } if (!file.exists(dsn) || force) { - # convert less common types to core types - if (inherits(x, 'sfc') || inherits(x, 'Spatial')) { - x <- sf::st_as_sf(x) - } - # write to file/db - if (inherits(x, 'SpatVector')) { - terra::writeVector(x, filename = dsn, layer = layer, ...) - } else if (inherits(x, 'sf')) { - sf::st_write(x, dsn = dsn, layer = layer, quiet = !verbose, ...) - } else if (inherits(x, 'SpatRaster')) { - terra::writeRaster(x, filename = dsn) + # write to file/db + if (inherits(x, c('SpatVector', 'SpatVectorProxy', 'SpatRaster'))) { + .check_pkg_ns("terra") + if (inherits(x, 'SpatVectorProxy')) { + x <- terra::query(x) + } + if (inherits(x, 'SpatVector')) { + terra::writeVector(x, + filename = dsn, + overwrite = force, + ...) + } else if (inherits(x, 'SpatRaster')) { + if (!is.null(layer)) { + x <- x[[layer[1]]] + } else if (terra::nlyr(x) > 1) { + x <- x[[1]] + } + terra::writeRaster(x, filename = dsn, overwrite = force, ...) + } + } else { + .check_pkg_ns("sf") + + # convert less common types to core types + if (inherits(x, 'sfc') || inherits(x, 'Spatial')) { + x <- sf::st_as_sf(x) + } + + if (inherits(x, 'sf')) { + sf::st_write( + x, + dsn = dsn, + quiet = !verbose, + delete_dsn = force, + ... + ) + } } } diff --git a/R/whitebox-package.R b/R/whitebox-package.R index a909043a5..350b5d940 100644 --- a/R/whitebox-package.R +++ b/R/whitebox-package.R @@ -67,6 +67,55 @@ whitebox.env <- new.env() #' @keywords datasets "wbttoolparameters" +#' Convenience method for path to sample DEM and soils data +#' +#' Get a file path to DEM.tif or STATSGO2.shp stored in extdata subfolder of whitebox package installation directory. +#' +#' @param destfile Path to target location of sample data. Will be downloaded if does not exist. Defaults to file path of extdata subfolder of whitebox package installation directory. +#' @param ... additional arguments to download.file() +#' +#' @return character. +#' @export +#' @keywords General datasets +#' @rdname extdata-gis +#' @examples +#' +#' if (check_whitebox_binary()) { +#' wbt_slope(sample_dem_data(), output = "slope.tif") +#' } +#' unlink(c('slope.tif', 'settings.json')) +#' @importFrom utils download.file +sample_dem_data <- function(destfile = file.path(system.file('extdata', package="whitebox"), 'DEM.tif'), ...) { + if (missing(destfile)) { + fp <- system.file("extdata/DEM.tif", package = "whitebox")[1] + } else { + if (!file.exists(destfile)) { + fp <- "" + } else { + fp <- destfile + } + } + if (fp == "") { + try(download.file("https://github.com/opengeos/whiteboxR/raw/master/inst/extdata/DEM.tif", + destfile = destfile, + mode = "wb", ...)) + if (missing(destfile)) { + fp <- system.file("extdata/DEM.tif", package = "whitebox")[1] + } else { + if (file.exists(destfile)) { + fp <- destfile + } + } + } + fp +} + +#' @export +#' @rdname extdata-gis +sample_soils_data <- function() { + system.file("extdata", "STATSGO2.shp", package = "whitebox")[1] +} + # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start diff --git a/data-raw/STATSGO_shapefile.R b/data-raw/STATSGO_shapefile.R new file mode 100644 index 000000000..e27bba4a6 --- /dev/null +++ b/data-raw/STATSGO_shapefile.R @@ -0,0 +1,18 @@ +# generate inst/extdata/STATSGO2.shp and sidecar files +# +# Digital General Soil Map of the United States or STATSGO2 is a broad-based +# inventory of soils and non-soil areas that occur in a repeatable pattern on +# the landscape and that can be cartographically shown at the scale mapped of +# 1:250,000 for most of U.S +# +# https://www.nrcs.usda.gov/resources/data-and-reports/description-of-statsgo2-database +# +dem <- terra::rast(whitebox::sample_dem_data()) +shp <- soilDB::SDA_spatialQuery( + dem, + what = "mupolygon", + db = "statsgo", + addFields = c("mapunit.musym", "mapunit.muname") +) +statsgo <- terra::crop(terra::project(shp, dem), dem) +terra::writeVector(statsgo, "inst/extdata/STATSGO2.shp") diff --git a/inst/CITATION b/inst/CITATION index 7fc37e863..088755a5a 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -4,10 +4,10 @@ bibentry( bibtype = "Manual", title = "'whitebox': 'WhiteboxTools' R Frontend", author = "Qiusheng Wu, Andrew Brown", - note = "R package version 2.2.0", + note = "R package version 2.4.2", url = "https://CRAN.R-project.org/package=whitebox", - year = "2022", - textVersion = "Wu, Q., Brown, A. (2022). whitebox: 'WhiteboxTools' R Frontend. R package version 2.2.0. " + year = "2025", + textVersion = "Wu, Q., Brown, A. (2025). whitebox: 'WhiteboxTools' R Frontend. R package version 2.4.2. " ) bibentry( diff --git a/inst/WORDLIST b/inst/WORDLIST index d51eb8870..d03374de0 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -8,11 +8,9 @@ BACKTRACE BreachDepressions Breakline CMD -CRS CamelCase Ceil Centre -Centroid Christoph ClassificationError Colour @@ -52,6 +50,7 @@ François Frontend GAT GDAL +GPKG GeoKey GeoPackage GeoTIFF @@ -77,7 +76,6 @@ InPlaceAdd InPlaceSubtract InverseMultiQuadric Isobasins -JSON JandR Jens Jenson @@ -108,6 +106,7 @@ NDI NoData Northness Num +ORCID OSAVI OptionList POLYLINE @@ -136,7 +135,6 @@ Rasterizes Reclass Reinitializes RemoveSpurs -Rescale Rgb Robinne Rosenfeld @@ -206,7 +204,6 @@ cageo centerlines centred centres -centroid cls codecov colour @@ -219,7 +216,6 @@ darboux decorrelation depressionless dev -differencing dinf directionality doi @@ -282,7 +278,6 @@ lengthed lidar liu lq -macOS magrittr medoid meso @@ -303,12 +298,10 @@ neighbouring neighbours nn nodata -normals num nw olympic parallelepiped -parallelize parsable pearson planchon @@ -318,13 +311,10 @@ pre pts quant quartic -quartile ransac raster's -rasters rbf reclass -repo rgb rgdal sca @@ -332,7 +322,6 @@ scattergram se shapetype sibson -sigmoid sigmoidal silverman sinh @@ -358,7 +347,6 @@ vals viewshed viewsheds viridi -viridis wang watershedding wb diff --git a/inst/extdata/STATSGO2.cpg b/inst/extdata/STATSGO2.cpg new file mode 100644 index 000000000..3ad133c04 --- /dev/null +++ b/inst/extdata/STATSGO2.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/inst/extdata/STATSGO2.dbf b/inst/extdata/STATSGO2.dbf new file mode 100644 index 000000000..6b9cde508 Binary files /dev/null and b/inst/extdata/STATSGO2.dbf differ diff --git a/inst/extdata/STATSGO2.prj b/inst/extdata/STATSGO2.prj new file mode 100644 index 000000000..e5d6720ad --- /dev/null +++ b/inst/extdata/STATSGO2.prj @@ -0,0 +1 @@ +PROJCS["NAD_1983_UTM_Zone_18N",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-75.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/inst/extdata/STATSGO2.shp b/inst/extdata/STATSGO2.shp new file mode 100644 index 000000000..816a48089 Binary files /dev/null and b/inst/extdata/STATSGO2.shp differ diff --git a/inst/extdata/STATSGO2.shx b/inst/extdata/STATSGO2.shx new file mode 100644 index 000000000..bf3f98782 Binary files /dev/null and b/inst/extdata/STATSGO2.shx differ diff --git a/man/sample_dem_data.Rd b/man/extdata-gis.Rd similarity index 70% rename from man/sample_dem_data.Rd rename to man/extdata-gis.Rd index 932d92db9..75cc87702 100644 --- a/man/sample_dem_data.Rd +++ b/man/extdata-gis.Rd @@ -1,13 +1,16 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/wbt.R +% Please edit documentation in R/whitebox-package.R \name{sample_dem_data} \alias{sample_dem_data} -\title{Convenience method for path to sample DEM} +\alias{sample_soils_data} +\title{Convenience method for path to sample DEM and soils data} \usage{ sample_dem_data( destfile = file.path(system.file("extdata", package = "whitebox"), "DEM.tif"), ... ) + +sample_soils_data() } \arguments{ \item{destfile}{Path to target location of sample data. Will be downloaded if does not exist. Defaults to file path of extdata subfolder of whitebox package installation directory.} @@ -18,7 +21,7 @@ sample_dem_data( character. } \description{ -Get a file path to DEM.tif stored in extdata subfolder of whitebox package installation directory. If needed, download the TIFF file from GitHub. +Get a file path to DEM.tif or STATSGO2.shp stored in extdata subfolder of whitebox package installation directory. } \examples{ diff --git a/man/wbt_source.Rd b/man/wbt_source.Rd index ff376b278..29f92e1ef 100644 --- a/man/wbt_source.Rd +++ b/man/wbt_source.Rd @@ -9,25 +9,42 @@ wbt_source( dsn = NULL, layer = NULL, force = FALSE, + tmpdir = tempdir(), + pattern = "wbt", verbose = wbt_verbose(), ... ) } \arguments{ -\item{x}{A terra SpatVector or sf object, or a path to a file that can be read as a SpatVectorProxy} +\item{x}{A terra SpatVector or sf object (in memory) or a path to a file that +can be read as a SpatVectorProxy. Or a memory or file-based SpatRaster. +When \code{x} has multiple layers/bands, the first layer is used by default; use +the \code{layer} argument to select a specific layer/band.} \item{dsn}{Data source path / file name} -\item{layer}{Data layer} +\item{layer}{Data layer. For vectors, \code{layer} is interpreted as a layer +name (character); for rasters, \code{layer} is interpreted as a band index or +name (integer OR character)} -\item{force}{Force write of vector data to file? Default: FALSE (only write if file does not exist)} +\item{force}{Force write of vector data to file? Default: FALSE (only write +if file does not exist and new file is needed)} + +\item{tmpdir}{Directory to write temporary ESRI Shapefiles for vector input +in memory or otherwise not already in shapefile. Default: \code{tempdir()}} + +\item{pattern}{Character vector giving the initial part of the temporary file +name} \item{verbose}{Print information about data source and contents?} -\item{...}{Additional arguments passed to \code{terra::writeVector()} or \code{sf::st_write()}} +\item{...}{Additional arguments passed to \code{terra::writeVector()} or +\code{sf::st_write()}, or \code{terra::writeRaster} (for rasters).} } \value{ -An R object with attributes \code{wbt_dsn} and \code{wbt_layer} set as needed to support reading and writing R objects from file by WhiteboxTools. +An R object (SpatRaster, SpatVector, SpatVectorProxy, sf) with +attributes \code{wbt_dsn} and \code{wbt_layer} set as needed to support reading and +writing R objects from file by WhiteboxTools. } \description{ Initialize an R object containing spatial data for use by WhiteboxTools diff --git a/man/whitebox-package.Rd b/man/whitebox-package.Rd index c675daa08..0eae16de6 100644 --- a/man/whitebox-package.Rd +++ b/man/whitebox-package.Rd @@ -26,7 +26,7 @@ The package options can be overridden with system environment variables: \code{R \code{\link[=wbt_init]{wbt_init()}}, \code{\link[=wbt_options]{wbt_options()}}, \code{\link[=install_whitebox]{install_whitebox()}} } \author{ -\strong{Maintainer}: Andrew Brown \email{brown.andrewg@gmail.com} [contributor] +\strong{Maintainer}: Andrew Brown \email{brown.andrewg@gmail.com} (\href{https://orcid.org/0000-0002-4565-533X}{ORCID}) Authors: \itemize{ diff --git a/tests/testthat/test-wbt_source.R b/tests/testthat/test-wbt_source.R new file mode 100644 index 000000000..82d9df5a0 --- /dev/null +++ b/tests/testthat/test-wbt_source.R @@ -0,0 +1,114 @@ +test_that("wbt_source (raster) works", { + + skip_if_not_installed("terra") + + f <- sample_dem_data() + + # raster source from geotiff path + src <- wbt_source(f) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.tif$", x)) + expect_true(file.exists(x)) + + dem <- terra::rast(f) + + # raster source from spatraster (with source file) + src <- wbt_source(dem) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.tif$", x)) + expect_true(file.exists(x)) + + dem2 <- dem*2 + + # raster source from spatraster (in memory) + src <- wbt_source(dem2) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.tif$", x)) + expect_true(file.exists(x)) + + tf <- tempfile(fileext = ".gpkg") + terra::writeRaster(dem, tf, gdal = c("RASTER_TABLE=one")) + terra::writeRaster(dem2, tf, gdal = c("RASTER_TABLE=two", "APPEND_SUBDATASET=YES")) + + # raster source from non-geotiff + src <- wbt_source(tf) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.tif$", x)) + expect_true(file.exists(x)) + + # raster source from non-geotiff + src <- wbt_source(tf, layer = "two") + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.tif$", x)) + expect_true(file.exists(x)) + + unlink(tf) +}) + +test_that("wbt_source (vector) works", { + + skip_if_not_installed("terra") + + f <- sample_soils_data() + + # vector source from shapefile + src <- wbt_source(f) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.shp$", x)) + expect_true(file.exists(x)) + + vf <- terra::vect(f) + + # vector source from spatvector (in memory) + src <- wbt_source(vf) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.shp$", x)) + expect_true(file.exists(x)) + + vf2 <- terra::vect(f, proxy = TRUE) + + # vector source from spatvectorproxy + src <- wbt_source(vf2) + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.shp$", x)) + expect_true(file.exists(x)) + + tf <- tempfile(fileext = ".gpkg") + + terra::writeVector(vf, tf, layer = "one") + terra::writeVector(vf, tf, layer = "two", insert = TRUE) + + # vector source from non-shapefile + src <- suppressWarnings(wbt_source(tf)) + # terra warning for multiple layers but layer unspecified + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.shp$", x)) + expect_true(file.exists(x)) + + src <- wbt_source(tf, layer = "two") + + x <- attr(src, "wbt_dsn") + + expect_true(grepl("\\.shp$", x)) + expect_true(file.exists(x)) + + unlink(tf) +})