wbt() method: call ‘Whitebox’ tools by name

# `wbt()` method: call ‘Whitebox’ tools by name

# `wbt()`

The `wbt()` method is a high-level wrapper function to manage flow of information between R and WhiteboxTools. With `wbt()` one can run sequential analyses in Whitebox while having R prepare inputs, generate commands, and visualize results.

    library(whitebox)

## Getting Started

Know what tool you want to run but can’t remember the required parameters? Just type `wbt("toolname")`

    wbt("slope")
    #> Required arguments:
    #> - [ ] dem -- Input raster DEM file.
    #> - [ ] output -- Output raster file.
    #>
    #> Optional arguments:
    #> - [ ] zfactor -- Optional multiplier for when the vertical and horizontal units are not the same.
    #> - [ ] units -- Units of output raster; options include 'degrees', 'radians', 'percent'
    #> <wbt_result>
    #> No parameters (Slope)
    #>
    #>  Slope ERROR: 'dem', 'output' required

Output produced on error and with invalid arguments helps guide use interactively by prompting with required and optional arguments as a checklist.

## Result Objects

All calls to `wbt()` return an S3 object with class `wbt_result`.

    # get file path of sample DEM
    dem <- sample_dem_data()

    wbt("slope", dem = dem, output = file.path(tempdir(), "slope.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.8s
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> dem  : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/slope.tif'
    #> --- RESULT ---
    #> $output
    #>   File result:  //tmp/RtmpL1Ccs3/slope.tif exists (Last modified: 2025-10-19 11:36:41.520767)

Whether a system call succeeds or fails the parameters that were passed to the WhiteboxTools executable and references to any output file paths that were specified are returned. If output files reference spatial data (e.g. a GeoTIFF file) they may be converted to a file-based R object providing information about the result.

    wbt("slope", goof = dem, output = file.path(tempdir(), "slope.tif"))
    #> Error: invalid parameter 'goof'
    #> Required arguments:
    #> - [ ] dem -- Input raster DEM file.
    #> - [*] output -- Output raster file.
    #>
    #> Optional arguments:
    #> - [ ] zfactor -- Optional multiplier for when the vertical and horizontal units are not the same.
    #> - [ ] units -- Units of output raster; options include 'degrees', 'radians', 'percent'
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> goof : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/slope.tif'
    #>
    #>  Slope ERROR: 'goof' invalid; 'dem' required

When a call fails an informative error message issued, and the error will be cached inside the result. Prior runs references are stored as well for sequential tool runs; more on that below.

### `wbt_result` class

A `wbt_result` class object is a base R `list()` with standard element names (`"tool"`, `"args"`, `"stdout"`, `"crs"`, `"result"`, `"history"`) and the `"class"` attribute `"wbt_result"`.

    str(wbt("slope", dem = dem, output = file.path(tempdir(), "slope.tif")), max.level = 1)
    #> Slope - Elapsed Time (excluding I/O): 0.9s
    #> List of 6
    #>  $ tool   : chr "Slope"
    #>  $ args   : chr "--dem=/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif --output=/tmp/RtmpL1Ccs3/slope.tif"
    #>  $ stdout : chr "Slope - Elapsed Time (excluding I/O): 0.9s"
    #>  $ crs    : chr ""
    #>   ..- attr(*, "package")= Named chr ""
    #>   .. ..- attr(*, "names")= chr "dem"
    #>  $ result :List of 1
    #>  $ history:List of 1
    #>  - attr(*, "class")= chr "wbt_result"

Any `output` produced by the tool (usually file paths set by the user) will be included in the `wbt_result$result` list.

### `wbt_result` on error

If there is an error a `try-error` class object with an error message is returned in lieu of a list in `$result`

    # on error there is a try-error object in $result
    x <- wbt("slope")
    #> Required arguments:
    #> - [ ] dem -- Input raster DEM file.
    #> - [ ] output -- Output raster file.
    #>
    #> Optional arguments:
    #> - [ ] zfactor -- Optional multiplier for when the vertical and horizontal units are not the same.
    #> - [ ] units -- Units of output raster; options include 'degrees', 'radians', 'percent'
    inherits(x$result, 'try-error')
    #> [1] TRUE
    message(x$result[1])
    #> ERROR: 'dem', 'output' required

## Vignette Topics

We now will cover how the `wbt()` method and `wbt_result` class can be used for the following:

- Input and Output with R spatial objects

- Running Sequences of Tools (optionally: with pipe `|>` or `%>%`)

- Handling Coordinate Reference Systems

# Input and Output with R spatial objects

A feature of `wbt()` is that it handles input/output and file name management if you are using R objects as input. It can be an onerous task to manage files for workflows involving many tool runs.

If you use a `terra` object as input, you will get a `SpatRaster`. If you use a `raster` object as your input object frontend, you will get a `RasterLayer` object as output for tools that produce a raster. There will be file paths associated with that R object.

### Compare `raster` and `terra` as R raster object frontends

    if (requireNamespace("raster")) {
      rdem <- raster::raster(dem)

      # raster input; raster output
      r1 <- wbt("slope", dem = rdem, output = file.path(tempdir(), "slope.tif"))
      r1
      class(r1$result$output)
    }
    #> Loading required namespace: raster
    #> Slope - Elapsed Time (excluding I/O): 0.9s
    #> [1] "RasterLayer"
    #> attr(,"package")
    #> [1] "raster"

    tdem <- terra::rast(dem)

    ## terra input; terra output
    t1 <- wbt("slope", dem = tdem, output =  file.path(tempdir(), "slope.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.9s
    t1
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> dem  : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/slope.tif'
    #> --- RESULT ---
    #> $output
    #> class       : SpatRaster
    #> size        : 188, 237, 1  (nrow, ncol, nlyr)
    #> resolution  : 90, 90  (x, y)
    #> extent      : 664692, 686022, 4878904, 4895824  (xmin, xmax, ymin, ymax)
    #> coord. ref. : NAD83 / UTM zone 18N (EPSG:26918)
    #> source      : slope.tif
    #> name        : slope
    class(t1$result$output)
    #> [1] "SpatRaster"
    #> attr(,"package")
    #> [1] "terra"

The user still needs to specify paths but `wbt()` eases the process of converting input objects to paths and reading output paths to R objects after the tool has run. In principle any R object that references a file with type that Whitebox supports can be handled under the current (simple; proof of concept) system. Using file-based representations may not be the most efficient, but allows for a tangible trail of the steps in a workflow to be followed that otherwise might be hard to track.

# `wbt_source()`: rasters in memory, and vector data

The terra and raster packages use pointers to a file/raster connection except when a grid is entirely in memory. Processing these types of rasters with whitebox requires writing out as a temporary GeoTIFF file for tool input. In general `wbt()` can handle this for you.

Vector data (point, line and polygon geometries) are commonly stored stored in sf data.frames, sp objects, or terra SpatVector objects. These types are read into memory on creation.

Reading vector data into R memory is not needed to run Whitebox tools, so `wbt_source()` provides a means to annotate objects with information about source file path (similar to how terra/raster handle raster sources) such that they can be processed further by `wbt()` with minimal overhead. Objects like the terra SpatVectorProxy use delayed read by default. Passing a file path to `wbt_source()` will create a SpatVectorProxy with necessary information for use with `wbt()`.

For instance, we can create a reference for a sample ESRI Shapefile via `wbt_source()`. The result is a SpatVectorProxy.

    library(terra)
    shp <- system.file("ex/lux.shp", package = "terra")
    x <- wbt_source(shp)
    x
    #>  class       : SpatVectorProxy
    #>  geometry    : polygons
    #>  dimensions  : 12, 6  (geometries, attributes)
    #>  extent      : 5.74414, 6.528252, 49.44781, 50.18162  (xmin, xmax, ymin, ymax)
    #>  source      : lux.shp
    #>  coord. ref. : lon/lat WGS 84 (EPSG:4326)
    #>  names       :  ID_1 NAME_1  ID_2 NAME_2  AREA   POP
    #>  type        : <num>  <chr> <num>  <chr> <num> <num>

The SpatVectorProxy provides a light-weight means to create an R object reference to a large file; allowing the “heavy” lifting for that file to be done primarily by WhiteboxTools (or GDAL via {terra}).

At time of writing this document, WhiteboxTools does not support vector data inputs other than Shapefile. If we have, for example, a GeoPackage, `wbt_source()` will convert the input to a temporary shapefile and return a SpatVectorProxy that references that file. This object can then be used as input to `wbt()`.

    # load the data
    x2 <- query(x)

    # remove area column
    x2$AREA <- NULL

    # create a GeoPackage
    terra::writeVector(x2, filename = file.path(tempdir(), "lux.gpkg"), overwrite = TRUE)

    # now the source is a temporary .shp
    x3 <- wbt_source(file.path(tempdir(), "lux.gpkg"))

    wbt("polygon_area", input = x3)
    #> PolygonArea - Elapsed Time: 0.0s
    #> <wbt_result>
    #> --- PARAMETERS (PolygonArea) ---
    #> input    : '/tmp/RtmpL1Ccs3/lux.gpkg_wbt2de1c455ab05b.shp'
    #> --- RESULT ---
    #> $output
    #>  class       : SpatVectorProxy
    #>  geometry    : polygons
    #>  dimensions  : 12, 6  (geometries, attributes)
    #>  extent      : 5.74414, 6.528252, 49.44781, 50.18162  (xmin, xmax, ymin, ymax)
    #>  source      : lux.gpkg_wbt2de1c455ab05b.shp
    #>  coord. ref. : lon/lat WGS 84 (EPSG:4326)
    #>  names       :  ID_1 NAME_1  ID_2 NAME_2   POP  AREA
    #>  type        : <num>  <chr> <num>  <chr> <num> <num>

# Running Sequences of Tools

When you call `wbt()` and the first argument is a `wbt_result` the `output` from the input `wbt_result` is passed as the first `input` to the new tool run.

This general “pass the output as the first input” works even if the first `input` parameter (`"-i"` flag) is something different such as `dem` or `input1`.

This makes it possible to chain different operations together in a sequence.

If all of the required parameters are specified the tool will be run.

Here we run “Slope” on a DEM, then run “Slope” on it again to get a second derivative “curvature”.

    x <- wbt("slope", dem = dem, output = file.path(tempdir(), "slope.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.9s
    x2 <- wbt(x, tool_name = "slope", output = file.path(tempdir(), "curvature.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.4s

## Using pipes

Nested chained operation syntax can be transformed to use pipe operators ({base} R 4.1+ `|>` or {magrittr} `%>%`). This can improve the readability of the code (fewer nested parentheses) and the first argument transfer behavior allows results from each step to be transferred to the input of the next.

    wbt("slope", dem = dem, output = file.path(tempdir(), "slope.tif")) |>
      wbt("slope", output = file.path(tempdir(), "curvature.tif"))

The `wbt_result` output that prints to the console will reflect the input/output parameters of the most recent tool run in a tool chain.

    x2
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> dem  : '//tmp/RtmpL1Ccs3/slope.tif'
    #> output   : '/tmp/RtmpL1Ccs3/curvature.tif'
    #> --- RESULT ---
    #> $output
    #>   File result:  //tmp/RtmpL1Ccs3/curvature.tif exists (Last modified: 2025-10-19 11:36:43.308765)
    #> --- HISTORY ---
    #> Prior results (n=1) for: Slope
    #>  - Slope (output<character>)

`wbt_result()` is the method to get `$result` for single runs or all `$result` in `$history` for chained runs from a `wbt_result` object.

    str(wbt_result(x2), max.level = 1)
    #>  chr [1:2] "//tmp/RtmpL1Ccs3/slope.tif" "//tmp/RtmpL1Ccs3/curvature.tif"

The `$history` element is a list of the `wbt_result` from individual runs.

    str(x2$history, max.level = 1)
    #> List of 2
    #>  $ :List of 5
    #>   ..- attr(*, "class")= chr "wbt_result"
    #>  $ :List of 5
    #>   ..- attr(*, "class")= chr "wbt_result"

If you pass invalid results from one step to the next, you get an informative error message.

    x <- wbt("slope")
    #> Required arguments:
    #> - [ ] dem -- Input raster DEM file.
    #> - [ ] output -- Output raster file.
    #>
    #> Optional arguments:
    #> - [ ] zfactor -- Optional multiplier for when the vertical and horizontal units are not the same.
    #> - [ ] units -- Units of output raster; options include 'degrees', 'radians', 'percent'
    wbt(x, "slope", output = file.path(tempdir(), "foo.tif"))
    #> NOTE: try-error result cannot be used as `dem`
    #> Required arguments:
    #> - [ ] dem -- Input raster DEM file.
    #> - [*] output -- Output raster file.
    #>
    #> Optional arguments:
    #> - [ ] zfactor -- Optional multiplier for when the vertical and horizontal units are not the same.
    #> - [ ] units -- Units of output raster; options include 'degrees', 'radians', 'percent'
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> output   : '/tmp/RtmpL1Ccs3/foo.tif'
    #>
    #>  Slope ERROR: 'dem' required
    #> --- HISTORY ---
    #> Prior results (n=1) for: Slope
    #>  - Slope (<character>)

# Coordinate Reference Systems (CRS)

On top of handling file paths it is necessary for Geographic Information Systems to handle Coordinate Reference System (CRS) information. Many R tools in the GDAL/PROJ sphere require valid CRS to operate.

WhiteboxTools will read the GeoKey information from your GeoTIFF files specified by path and propagate that to the output file result. When you read those files with R, you should find that the original file’s CRS has been transferred.

`wbt()` and `wbt_result` help ensure consistent file and CRS information across sequences of operations and within calls–especially those involving R spatial objects.

If you specified the CRS in R, or made the raster entirely in R, you might need a hand setting up the file-based representation that WhiteboxTools will use. This is easy enough to do with `crs()`, `writeRaster()` and the equivalent but often requires more boilerplate code than just specifying the argument, or having CRS propagate from input.

For inputs that have a file-based representation (e.g. `RasterLayer` and `SpatRaster` objects) `wbt()` provides an interface where R objects can be substituted for file names and vice versa. User and workflow level attributes like CRS and working directory be handled at the same time.

    dem <- sample_dem_data()

    ## equivalent to:
    # dem <- system.file("extdata/DEM.tif", package = "whitebox")

In the process of reading/writing files that may contain CRS information, such as this sample DEM, the CRS can be inspected, modified and propagated to file or R objects.

## Setting the CRS

If the CRS is specified in the input `SpatRaster` object, `wbt()` makes sure it is propagated to the R object result. `wbt()` does not alter or copy the source or output files which may or may not have CRS information.

    araster <- terra::rast(dem)
    wbt("slope", dem = araster, output = file.path(tempdir(), "foo.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.8s
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> dem  : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/foo.tif'
    #> --- RESULT ---
    #> $output
    #> class       : SpatRaster
    #> size        : 188, 237, 1  (nrow, ncol, nlyr)
    #> resolution  : 90, 90  (x, y)
    #> extent      : 664692, 686022, 4878904, 4895824  (xmin, xmax, ymin, ymax)
    #> coord. ref. : NAD83 / UTM zone 18N (EPSG:26918)
    #> source      : foo.tif
    #> name        : foo

Alternately you can specify the `crs` argument to `wbt()` instead of `rast()`. This will directly set the `crs` element of the `wbt_result` of your output.

    wbt("slope", dem = terra::rast(dem), crs = "EPSG:26918", output = file.path(tempdir(), "foo.tif"))
    #> Slope - Elapsed Time (excluding I/O): 0.8s
    #> <wbt_result>
    #> --- PARAMETERS (Slope) ---
    #> dem  : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/foo.tif'
    #> --- RESULT ---
    #> $output
    #> class       : SpatRaster
    #> size        : 188, 237, 1  (nrow, ncol, nlyr)
    #> resolution  : 90, 90  (x, y)
    #> extent      : 664692, 686022, 4878904, 4895824  (xmin, xmax, ymin, ymax)
    #> coord. ref. : NAD83 / UTM zone 18N (EPSG:26918)
    #> source      : foo.tif
    #> name        : foo

In either case wherever you specify the `crs` argument the `crs` is stored in the `wbt_result` and propagated to the relevant output. Again, the source files are unchanged if they had no CRS or invalid CRS (until you write that updated CRS to file).

If two input R objects have different (or no) CRS then the `wbt()` method will provide a message to the user:

    r1 <- terra::rast(dem) # default: EPSG:26918
    r2 <- terra::deepcopy(r1)
    crs(r2) <- "EPSG:26917" # something else/wrong
    wbt("add",
        input1 = r1,
        input2 = r2,
        output = file.path(tempdir(), "foo.tif")
       )
    #> NOTE: Input CRS do not match.
    #> Add - Elapsed Time (excluding I/O): 0.0s
    #> <wbt_result>
    #> --- PARAMETERS (Add) ---
    #> input1   : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> input2   : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/foo.tif'
    #> --- RESULT ---
    #> $output
    #> class       : SpatRaster
    #> size        : 188, 237, 1  (nrow, ncol, nlyr)
    #> resolution  : 90, 90  (x, y)
    #> extent      : 664692, 686022, 4878904, 4895824  (xmin, xmax, ymin, ymax)
    #> coord. ref. : NAD83 / UTM zone 18N (EPSG:26918)
    #> source      : foo.tif
    #> name        : foo

This message is suppressed and output CRS set accordingly if the `crs` argument is specified. This user set CRS is only in the SpatRaster object, and does not necessarily match that of the file used as input or returned as output.

    wbt("add",
        input1 = r1,
        input2 = r2,
        crs = "EPSG:26918",
        output = file.path(tempdir(), "foo.tif")
       )
    #> Add - Elapsed Time (excluding I/O): 0.0s
    #> <wbt_result>
    #> --- PARAMETERS (Add) ---
    #> input1   : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> input2   : '/tmp/RtmpMpihDT/Rinst2dc1fe52c1ed/whitebox/extdata/DEM.tif'
    #> output   : '/tmp/RtmpL1Ccs3/foo.tif'
    #> --- RESULT ---
    #> $output
    #> class       : SpatRaster
    #> size        : 188, 237, 1  (nrow, ncol, nlyr)
    #> resolution  : 90, 90  (x, y)
    #> extent      : 664692, 686022, 4878904, 4895824  (xmin, xmax, ymin, ymax)
    #> coord. ref. : NAD83 / UTM zone 18N (EPSG:26918)
    #> source      : foo.tif
    #> name        : foo