Using terra with geotargets

# Using `terra` with `geotargets`

    library(targets)
    library(geotargets)

The `geotargets` package extends `targets` to work with geospatial data formats, such as rasters and vectors (e.g., shape files). In particular, `geotargets` aims to support use of the `terra` package, which tend to cause problems if used in targets created with `tar_target()` . If you are new to `targets`, you should start by looking at the targets manual to get a handle on the basics.

The design of `geotargets` is to specify target factories like so: `tar_<pkg>_<type>`.

In this vignette we will demonstrate the use of the `terra` R package, and we will demonstrate how to build raster (`rast`), vector (`vect`), raster collection (`sprc`), and raster dataset (`sds`) targets with:

- `tar_terra_rast()`
- `tar_terra_vect()`
- `tar_terra_sprc()`
- `tar_terra_sds()`

# How to run targets examples from vignettes

The example code in this vignette is designed for you to be able to just copy and paste into the R console. However, this is not a typical way to run a targets workflow.

The examples make use of `targets::tar_script()`, which creates or overwrites a `_targets.R` file (See for example, the `_targets.R` file in the demo-geotargets repo). In these examples, everything inside of `tar_script({})` is what would go inside a `_targets.R` file defining a workflow. When running the `tar_script` code, it will ask you each time if you want to overwrite the `_targets.R` file. This means if you are exploring these examples and copying the entire examples, it is worthwhile doing this in a separate directory/project/repository to avoid overwriting your own `_targets.R` file.

So, when building your own targets workflow it is not recommended to use `tar_script()`, but instead to create `_targets.R` and edit `_targets.R` directly.

## `tar_terra_rast()`: targets with `terra` rasters

    targets::tar_script({
      library(targets)
      library(geotargets)
      tar_option_set(packages = "terra")
      geotargets_option_set(gdal_raster_driver = "COG")
      list(
        tar_target(
          tif_file,
          system.file("ex/elev.tif", package = "terra"),
          format = "file"
        ),
        tar_terra_rast(
          r,
          {
            rast <- rast(tif_file)
            units(rast) <- "m"
            rast
          }
        ),
        tar_terra_rast(
          r_agg,
          aggregate(r, 2)
        )
      )
    })

Above is a basic example showing the use of `tar_terra_rast()` in a targets pipeline. The command for `tar_terra_rast()` can be any function that returns a `SpatRaster` object.

In this example, we’ve set the output to a cloud optimized geotiff (“COG”), but any GDAL driver that works with `terra::writeRaster()` should also work here. By default, we use “GTiff”. You can also set this option on a target-by-target basis with the `filetype` argument to `tar_terra_rast()`.

Running the pipeline:

    tar_make()
    #> + tif_file dispatched
    #> terra 1.8.42
    #> ✔ tif_file completed [5ms, 7.99 kB]
    #> + r dispatched
    #> ✔ r completed [1ms, 10.47 kB]
    #> + r_agg dispatched
    #> ✔ r_agg completed [1ms, 6.30 kB]
    #> ✔ ended pipeline [100ms, 3 completed, 0 skipped]
    tar_read(r)
    #> class       : SpatRaster
    #> dimensions  : 90, 95, 1  (nrow, ncol, nlyr)
    #> resolution  : 0.008333333, 0.008333333  (x, y)
    #> extent      : 5.741667, 6.533333, 49.44167, 50.19167  (xmin, xmax, ymin, ymax)
    #> coord. ref. : lon/lat WGS 84 (EPSG:4326)
    #> source      : r
    #> name        : elevation
    #> min value   :       141
    #> max value   :       547
    #> unit        :         m
    tar_read(r_agg)
    #> class       : SpatRaster
    #> dimensions  : 45, 48, 1  (nrow, ncol, nlyr)
    #> resolution  : 0.01666667, 0.01666667  (x, y)
    #> extent      : 5.741667, 6.541667, 49.44167, 50.19167  (xmin, xmax, ymin, ymax)
    #> coord. ref. : lon/lat WGS 84 (EPSG:4326)
    #> source      : r_agg
    #> name        : elevation
    #> min value   :    166.75
    #> max value   :    529.50

### Raster metadata

You may have noticed the units for the `r` target above have gone missing. This is due to limitations of `terra` and `targets`—`terra` saves some metadata in “sidecar” aux.json files and `targets` enforces a strict one file per target rule.

You can get around this by setting `preserve_metadata = "zip"` in `tar_terra_rast()` to save the output files, including the metadata, as a minimally compressed zip archive.

You can also set this for all raster targets with `geotargets_option_set(terra_preserve_metadata = "zip")`.

Note: there are likely performance costs associated with this option.
As an alternative, you can encode information in the layer names by setting `names(r) <-` which are retained even with the default `preserve_metadata = "drop"`.

    targets::tar_script({
      # contents of _targets.R:
      library(targets)
      library(geotargets)
      tar_option_set(packages = "terra")
      geotargets_option_set(gdal_raster_driver = "COG")
      list(
        tar_target(
          tif_file,
          system.file("ex/elev.tif", package = "terra"),
          format = "file"
        ),
        tar_terra_rast(
          r,
          {
            rast <- rast(tif_file)
            units(rast) <- "m"
            rast
          },
          preserve_metadata = "zip"
        )
      )
    })

    tar_make()
    #> + r dispatched
    #> terra 1.8.42
    #> ✔ r completed [2ms, 10.26 kB]
    #> ✔ ended pipeline [83ms, 1 completed, 1 skipped]
    terra::units(tar_read(r))
    #> [1] "m"

## `tar_terra_vect()`: targets with `terra` vectors

For `terra` `SpatVector` objects, use `tar_terra_vect()` in the pipeline. You can set vector specific options with `geotargets_option_set()` or with the `filetype` and `gdal` arguments to individual `tar_terra_vect()` calls.

    targets::tar_script({
      # contents of _targets.R:
      library(targets)
      library(geotargets)
      geotargets_option_set(gdal_vector_driver = "GeoJSON")
      list(
        tar_target(
          vect_file,
          system.file("ex", "lux.shp", package = "terra"),
          format = "file"
        ),
        tar_terra_vect(
          v,
          terra::vect(vect_file)
        ),
        tar_terra_vect(
          v_proj,
          terra::project(v, "EPSG:2196")
        )
      )
    })

    tar_make()
    #> + vect_file dispatched
    #> ✔ vect_file completed [0ms, 64.69 kB]
    #> + v dispatched
    #> ✔ v completed [8ms, 117.63 kB]
    #> + v_proj dispatched
    #> ✔ v_proj completed [20ms, 213.55 kB]
    #> ✔ ended pipeline [128ms, 3 completed, 0 skipped]
    tar_read(v)
    #>  class       : SpatVector
    #>  geometry    : polygons
    #>  dimensions  : 12, 6  (geometries, attributes)
    #>  extent      : 5.74414, 6.528252, 49.44781, 50.18162  (xmin, xmax, ymin, ymax)
    #>  source      : v
    #>  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>
    #>  values      :     1 Diekirch     1 Clervaux   312 1.808e+04
    #>                    1 Diekirch     2 Diekirch   218 3.254e+04
    #>                    1 Diekirch     3  Redange   259 1.866e+04
    tar_read(v_proj)
    #>  class       : SpatVector
    #>  geometry    : polygons
    #>  dimensions  : 12, 6  (geometries, attributes)
    #>  extent      : -69990.51, -13879.85, 5484907, 5566555  (xmin, xmax, ymin, ymax)
    #>  source      : v_proj
    #>  coord. ref. : ETRS89 / Kp2000 Jutland (EPSG:2196)
    #>  names       :  ID_1   NAME_1  ID_2   NAME_2  AREA       POP
    #>  type        : <num>    <chr> <num>    <chr> <num>     <num>
    #>  values      :     1 Diekirch     1 Clervaux   312 1.808e+04
    #>                    1 Diekirch     2 Diekirch   218 3.254e+04
    #>                    1 Diekirch     3  Redange   259 1.866e+04

## `tar_terra_sprc()`: targets with `terra` raster collections

Targets that produce a `SpatRasterCollection` can be created with `tar_terra_sprc()`. The various rasters in the collection are saved as subdatasets to adhere to `targets` one file per target rule.

    targets::tar_script({
      # contents of _targets.R:
      library(targets)
      library(geotargets)
      elev_scale <- function(raster, z = 1, projection = "EPSG:4326") {
        terra::project(
          raster * z,
          projection
        )
      }
      tar_option_set(packages = "terra")
      geotargets_option_set(gdal_raster_driver = "GTiff")
      list(
        tar_target(
          elev_file,
          system.file("ex", "elev.tif", package = "terra"),
          format = "file"
        ),
        tar_terra_rast(
          r,
          rast(elev_file)
        ),
        tar_terra_sprc(
          raster_elevs,
          # two rasters, one unaltered, one scaled by factor of 2 and
          # reprojected to interrupted good homolosine
          terra::sprc(list(
            elev_scale(r, 1),
            elev_scale(r, 2, "+proj=igh")
          ))
        )
      )
    })

    tar_make()
    #> + elev_file dispatched
    #> terra 1.8.42
    #> ✔ elev_file completed [5ms, 7.99 kB]
    #> + r dispatched
    #> ✔ r completed [2ms, 8.52 kB]
    #> + raster_elevs dispatched
    #> ✔ raster_elevs completed [14ms, 37.90 kB]
    #> ✔ ended pipeline [136ms, 3 completed, 0 skipped]
    tar_read(raster_elevs)
    #> class       : SpatRasterCollection
    #> length      : 2
    #> nrow        : 90, 115
    #> ncol        : 95, 114
    #> nlyr        :  1,   1
    #> extent      : 5.741667, 1558890, 49.44167, 5556741  (xmin, xmax, ymin, ymax)
    #> crs (first) : lon/lat WGS 84 (EPSG:4326)
    #> names       : raster_elevs, raster_elevs

## `tar_terra_sds()`: targets with `terra` raster datasets

A `terra` `SpatRasterDataset` is very similar to a `SpatRasterCollection` except that all sub-datasets must have the same projection and extent

    targets::tar_script({
      # contents of _targets.R:
      library(targets)
      library(geotargets)
      tar_option_set(packages = "terra")
      list(
        tar_target(
          logo_file,
          system.file("ex/logo.tif", package = "terra"),
          format = "file"
        ),
        tar_terra_sds(
          raster_dataset,
          {
            x <- sds(rast(logo_file), rast(logo_file) / 2)
            names(x) <- c("first", "second")
            x
          }
        )
      )
    })

    tar_make()
    #> + logo_file dispatched
    #> terra 1.8.42
    #> ✔ logo_file completed [5ms, 22.46 kB]
    #> + raster_dataset dispatched
    #> ✔ raster_dataset completed [18ms, 54.73 kB]
    #> ✔ ended pipeline [89ms, 2 completed, 0 skipped]
    tar_read(raster_dataset)
    #> class       : SpatRasterDataset
    #> subdatasets : 2
    #> dimensions  : 77, 101 (nrow, ncol)
    #> nlyr        : 3, 3
    #> resolution  : 1, 1  (x, y)
    #> extent      : 0, 101, 0, 77  (xmin, xmax, ymin, ymax)
    #> coord. ref. : Cartesian (Meter)
    #> source(s)   : raster_dataset
    #> names       : raster_dataset, raster_dataset