--- title: "Coercion between object formats" author: "Roger Bivand" output: html_document: toc: true toc_float: collapsed: false smooth_scroll: false toc_depth: 2 bibliography: refs.bib vignette: > %\VignetteIndexEntry{Coercion between object formats} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE, paged.print = FALSE) ``` ## Introduction The original R-GRASS interface [@bivand:00; @neteler+mitasova:08] was designed to move raster and later vector data between R and GRASS GIS. To do this, use was made of intermediate files, often using the external GDAL library on both sides. On the R side, the **rgdal** now archived package was used, interfacing GDAL and PROJ as GRASS GIS also did. The GRASS commands `r.in.gdal`, `r.out.gdal`, `v.in.ogr` and `v.out.ogr` were matched by **rgdal** functions using the same underlying external libraries: ```{r, out.width=500, echo=FALSE} knitr::include_graphics("fig1.png") ``` GDAL was supplemented for raster data by simply reading and writing uncompressed binary files using `r.in.bin` and `r.out.bin`, with custom functions on the R side. As then written, the R-GRASS interface used **sp** classes for both raster and vector data, supplemented more recently with **sf** classes for vector data only. The current version of the R-GRASS interface has been simplified to use the **terra** package because it, like **sf** and **rgdal** before it, links to the important external libraries. The workhorse driver is known as `RRASTER`, and has been widely used in **raster** and **terra** (see also (https://rspatial.org)). It uses GDAL but writes a flat uncompressed binary file. Using `terra::rast()` also appears to preserve category names and colour tables, but needs further testing (see (https://github.com/rsbivand/rgrass/issues/42)). ```{r, out.width=500, echo=FALSE} knitr::include_graphics("fig2_p7_RRASTER_GRASS.png") ``` From GDAL 3.5.0, the `RRASTER` driver also supports WKT2_2019 CRS representations; in earlier versions of GDAL, the driver only supported the proj-string representation (https://github.com/rsbivand/rgrass/issues/51). These changes mean that users transferring data between R and GRASS will need to coerce between **terra** classes `SpatVector` and `SpatRaster` and the class system of choice. In addition, `SpatRaster` is only read into memory from file when this is required, so requiring some care. ## Loading and attaching packages This vignette is constructed conditioning on the availability of aforementioned R packages, i.e. if some were missing at the time of package building, some code blocks will not be displayed. ```{r include=FALSE, message=FALSE} terra_available <- requireNamespace("terra", quietly = TRUE) sf_available <- requireNamespace("sf", quietly = TRUE) sp_available <- requireNamespace("sp", quietly = TRUE) stars_available <- requireNamespace("stars", quietly = TRUE) && packageVersion("stars") > "0.5.4" raster_available <- requireNamespace("raster", quietly = TRUE) ``` On loading and attaching, **terra** displays its version: ```{r, eval=terra_available} library("terra") ``` ```{r, eval=sf_available} library("sf") ``` ```{r, eval=sp_available} library("sp") ``` ```{r, eval=stars_available} library("stars") ``` ```{r, eval=raster_available} library("raster") ``` `terra::gdal()` tells us the versions of the external libraries being used by **terra**: ```{r, eval=terra_available} gdal(lib = "all") ``` When using CRAN binary packages built static for Windows and macOS, the R packages will use the same versions of the external libraries, but not necessarily the same versions as those against which GRASS was installed. ## `"SpatVector"` coercion In the **terra** package [@terra], vector data are held in `"SpatVector"` objects. This means that when `read_VECT()` is used, a `"SpatVector"` object is returned, and the same class of object is needed for `write_VECT()` for writing to GRASS. ```{r, eval=terra_available} fv <- system.file("ex/lux.shp", package = "terra") (v <- vect(fv)) ``` These objects are always held in memory, so there is no `inMemory()` method: ```{r, , eval=terra_available} try(inMemory(v)) ``` The coordinate reference system is expressed in WKT2-2019 form: ```{r, , eval=terra_available} cat(crs(v), "\n") ``` ### `"sf"` Most new work should use vector classes defined in the **sf** package [@sf; @sf-rj]. In this case, coercion uses `st_as_sf()`: ```{r, eval=(terra_available && sf_available)} v_sf <- st_as_sf(v) v_sf ``` and the `vect()` method to get from **sf** to **terra**: ```{r, eval=(terra_available && sf_available)} v_sf_rt <- vect(v_sf) v_sf_rt ``` ```{r, eval=(terra_available && sf_available)} all.equal(v_sf_rt, v, check.attributes = FALSE) ``` ### `"Spatial"` To coerce to and from vector classes defined in the **sp** package [@asdar], methods in **raster** are used as an intermediate step: ```{r, eval=(terra_available && raster_available && sp_available)} v_sp <- as(v, "Spatial") print(summary(v_sp)) ``` ```{r, eval=(terra_available && sf_available && sp_available)} v_sp_rt <- vect(st_as_sf(v_sp)) all.equal(v_sp_rt, v, check.attributes = FALSE) ``` ## `"SpatRaster"` coercion In the **terra** package, raster data are held in `"SpatRaster"` objects. This means that when `read_RAST()` is used, a `"SpatRaster"` object is returned, and the same class of object is needed for `write_RAST()` for writing to GRASS. ```{r, eval=terra_available} fr <- system.file("ex/elev.tif", package = "terra") (r <- rast(fr)) ``` In general, `"SpatRaster"` objects are files, rather than data held in memory: ```{r, eval=terra_available} try(inMemory(r)) ``` ### `"stars"` The **stars** package [@stars] uses GDAL through **sf**. A coercion method is provided from `"SpatRaster"` to `"stars"`: ```{r, eval=(terra_available && stars_available)} r_stars <- st_as_stars(r) print(r_stars) ``` which round-trips in memory. ```{r, eval=(terra_available && stars_available)} (r_stars_rt <- rast(r_stars)) ``` When coercing to `"stars_proxy"` the same applies: ```{r, eval=(terra_available && stars_available)} (r_stars_p <- st_as_stars(r, proxy = TRUE)) ``` with coercion from `"stars_proxy"` also not reading data into memory: ```{r, eval=(terra_available && stars_available)} (r_stars_p_rt <- rast(r_stars_p)) ``` ### `"RasterLayer"` From version 3.6-3 the **raster** package [@raster] uses **terra** for all GDAL operations. Because of this, coercing a `"SpatRaster"` object to a `"RasterLayer"` object is simple: ```{r, eval=(terra_available && raster_available)} (r_RL <- raster(r)) ``` ```{r, eval=(terra_available && raster_available)} inMemory(r_RL) ``` The WKT2-2019 CRS representation is present but not shown by default: ```{r, eval=(terra_available && raster_available)} cat(wkt(r_RL), "\n") ``` This object (held on file rather than in memory) can be round-tripped: ```{r, eval=(terra_available && raster_available)} (r_RL_rt <- rast(r_RL)) ``` ### `"Spatial"` `"RasterLayer"` objects can be used for coercion from a `"SpatRaster"` object to a `"SpatialGridDataFrame"` object: ```{r, eval=(terra_available && raster_available && sp_available)} r_sp_RL <- as(r_RL, "SpatialGridDataFrame") summary(r_sp_RL) ``` The WKT2-2019 CRS representation is present but not shown by default: ```{r, eval=(terra_available && raster_available && sp_available)} cat(wkt(r_sp_RL), "\n") ``` This object can be round-tripped, but use of **raster** forefronts the Proj.4 string CRS representation: ```{r, eval=(terra_available && raster_available && sp_available)} (r_sp_RL_rt <- raster(r_sp_RL)) cat(wkt(r_sp_RL_rt), "\n") ``` ```{r, eval=(terra_available && raster_available && sp_available)} (r_sp_rt <- rast(r_sp_RL_rt)) ``` ```{r, eval=(terra_available && raster_available && sp_available)} crs(r_sp_RL_rt) ``` Coercion to the **sp** `"SpatialGridDataFrame"` representation is also provided by **stars**: ```{r, eval=(terra_available && stars_available && sp_available)} r_sp_stars <- as(r_stars, "Spatial") summary(r_sp_stars) ``` ```{r, eval=(terra_available && stars_available && sp_available)} cat(wkt(r_sp_stars), "\n") ``` and can be round-tripped: ```{r, eval=(terra_available && stars_available && sp_available)} (r_sp_stars_rt <- rast(st_as_stars(r_sp_stars))) ``` `` ```{r, eval=(terra_available && stars_available && sp_available)} cat(crs(r_sp_rt), "\n") ``` ## References