|
| 1 | +--- |
| 2 | +title: "Quick comparison: R and Python GRASS interfaces" |
| 3 | +author: "Veronica Andreo" |
| 4 | +date: 2024-04-01 |
| 5 | +date-modified: today |
| 6 | +format: |
| 7 | + html: |
| 8 | + toc: true |
| 9 | + code-tools: true |
| 10 | + code-copy: true |
| 11 | + code-fold: false |
| 12 | +categories: [Python, R, intermediate] |
| 13 | +engine: knitr |
| 14 | +execute: |
| 15 | + eval: false |
| 16 | +--- |
| 17 | + |
| 18 | +{.preview-image width=50%} |
| 19 | + |
| 20 | +In this short tutorial we will highlight the similarities of R and Python GRASS interfaces |
| 21 | +in order to streamline the use of GRASS GIS within R and Python communities. |
| 22 | +As you may know, there's |
| 23 | +an R package called [rgrass](https://github.com/rsbivand/rgrass/) that provides |
| 24 | +basic functionality to read and write data from and into GRASS database as well |
| 25 | +as to execute GRASS tools in either existing or temporary GRASS projects. |
| 26 | +The [GRASS Python API](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html), |
| 27 | +on the other hand, is composed of various packages that provide classes and |
| 28 | +functions for low and high level tasks, including those that can be executed |
| 29 | +with rgrass. |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +There are some parallelisms between the |
| 34 | +**rgrass** and **grass.script**/**grass.jupyter** packages, i.e., |
| 35 | +R and Python interfaces to GRASS GIS. |
| 36 | +Let's review them and go through some examples. |
| 37 | + |
| 38 | + |
| 39 | +| Task | rgrass function | GRASS Python API function | |
| 40 | +|------------------------------------------------------------|--------------------------------|---------------------------------------------------------| |
| 41 | +| Load library | library(rgrass) | import grass.script as gs<br>import grass.jupyter as gj | |
| 42 | +| Start GRASS and set all needed <br>environmental variables | initGRASS() | gs.setup.init() for scripts,<br>gj.init() for notebooks | |
| 43 | +| Execute GRASS commands | execGRASS() | gs.run_command(),<br>gs.read_command(),<br>gs.parse_command() | |
| 44 | +| Read raster and vector data <br>from GRASS | read_RAST(),<br>read_VECT() | gs.array.array(),<br>n/a | |
| 45 | +| Write raster and vector data<br>into GRASS | write_RAST(),<br>write_VECT() | gs.array.write(),<br>n/a | |
| 46 | +| Get raster and vector info | n/a,<br>vInfo() | gs.raster_info(),<br>gs.vector_info() | |
| 47 | +| Close GRASS session | unlink_.gislock() | gs.setup.finish(),<br>gj.finish() | |
| 48 | + |
| 49 | +: R and Python GRASS interfaces compared {.striped .hover} |
| 50 | + |
| 51 | +## Comparison examples |
| 52 | + |
| 53 | +Let's see how usage examples would look like. |
| 54 | + |
| 55 | +1. **Load the library**: We need to |
| 56 | +load the libraries that allow us to interface with GRASS GIS |
| 57 | +functionality and (optionally) data. For the Python case, we first need to add |
| 58 | +the GRASS python package path to our system's path. |
| 59 | + |
| 60 | +::: {.panel-tabset} |
| 61 | + |
| 62 | +## R |
| 63 | + |
| 64 | +```{r} |
| 65 | +library(rgrass) |
| 66 | +``` |
| 67 | + |
| 68 | +## Python |
| 69 | + |
| 70 | +```{python} |
| 71 | +#| python.reticulate: FALSE |
| 72 | +import sys |
| 73 | +import subprocess |
| 74 | +
|
| 75 | +sys.path.append( |
| 76 | + subprocess.check_output(["grass", "--config", "python_path"], text=True).strip() |
| 77 | +) |
| 78 | +
|
| 79 | +import grass.script as gs |
| 80 | +import grass.jupyter as gj |
| 81 | +``` |
| 82 | + |
| 83 | +::: |
| 84 | + |
| 85 | +2. **Start a GRASS session**: Once we loaded or imported the packages, we |
| 86 | +start a GRASS session. We need to pass the path to a |
| 87 | +temporary or existing GRASS project. |
| 88 | +In the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can |
| 89 | +specify the path to the binaries ourselves. |
| 90 | +In the case of Python, it is worth noting that while grass.script and grass.jupyter init functions |
| 91 | +take the same arguments, `gj.init` also sets other environmental variables to |
| 92 | +streamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells |
| 93 | +can be executed multiple times. |
| 94 | + |
| 95 | +::: {.panel-tabset} |
| 96 | + |
| 97 | +## R |
| 98 | + |
| 99 | +```{r} |
| 100 | +session <- initGRASS(gisBase = "/usr/lib/grass84", # where grass binaries live, `grass --config path` |
| 101 | + gisDbase = "/home/user/grassdata", # path to grass database or folder where your project lives |
| 102 | + location = "nc_basic_spm_grass7", # existing project name |
| 103 | + mapset = "PERMANENT" # mapset name |
| 104 | + ) |
| 105 | +``` |
| 106 | + |
| 107 | +## Python |
| 108 | + |
| 109 | +```{python} |
| 110 | +#| python.reticulate: FALSE |
| 111 | +# With grass.script for scripts |
| 112 | +session = gs.setup.init(path="/home/user/grassdata", |
| 113 | + location="nc_basic_spm_grass7", |
| 114 | + mapset="PERMANENT") |
| 115 | +# Optionally, the path to a mapset |
| 116 | +session = gs.setup.init("/home/user/grassdata/nc_basic_spm_grass7/PERMANENT") |
| 117 | +
|
| 118 | +# With grass.jupyter for notebooks |
| 119 | +session = gj.init(path="/home/user/grassdata", |
| 120 | + location="nc_basic_spm_grass7", |
| 121 | + mapset="PERMANENT") |
| 122 | +# Optionally, the path to a mapset |
| 123 | +session = gj.init("~/grassdata/nc_basic_spm_grass7/PERMANENT") |
| 124 | +``` |
| 125 | + |
| 126 | +::: |
| 127 | + |
| 128 | +3. **Execute GRASS commands**: Both interfaces work pretty similarly, the |
| 129 | +first argument is always the GRASS tool name and then we pass the parameters |
| 130 | +and flags. While in R we basically use `execGRASS()` for all GRASS commands, in |
| 131 | +the Python API, we have different wrappers to execute GRASS commands depending |
| 132 | +on the nature of their output. |
| 133 | + |
| 134 | +::: {.panel-tabset} |
| 135 | + |
| 136 | +## R |
| 137 | + |
| 138 | +```{r} |
| 139 | +# Map output |
| 140 | +execGRASS("r.slope.aspect", |
| 141 | + elevation = "elevation", |
| 142 | + slope = "slope", |
| 143 | + aspect = "aspect") |
| 144 | +
|
| 145 | +# Text output |
| 146 | +execGRASS("g.region", |
| 147 | + raster = "elevation", |
| 148 | + flags = "p") |
| 149 | +``` |
| 150 | + |
| 151 | +## Python |
| 152 | + |
| 153 | +```{python} |
| 154 | +#| python.reticulate: FALSE |
| 155 | +# Map output |
| 156 | +gs.run_command("r.slope.aspect", |
| 157 | + elevation="elevation", |
| 158 | + slope="slope", |
| 159 | + aspect="aspect") |
| 160 | +# Text output |
| 161 | +print(gs.read_command("g.region", |
| 162 | + raster="elevation", |
| 163 | + flags="p")) |
| 164 | +# Text output - dictionary |
| 165 | +region = gs.parse_command("g.region", |
| 166 | + raster="elevation", |
| 167 | + flags="g") |
| 168 | +region |
| 169 | +``` |
| 170 | + |
| 171 | +::: |
| 172 | + |
| 173 | +4. **Read raster and vector data into other R or Python formats**: |
| 174 | +*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and |
| 175 | +vector maps into terra's SpatRaster and SpatVector objects within R. |
| 176 | +In the case of Python, GRASS |
| 177 | +raster maps that can be converted into numpy arrays through |
| 178 | +`gs.array.array()`. Vector attribute data can be converted into |
| 179 | +Pandas data frames in various ways. |
| 180 | + |
| 181 | +::: {.panel-tabset} |
| 182 | + |
| 183 | +## R |
| 184 | + |
| 185 | +```{r} |
| 186 | +# Raster |
| 187 | +elevr <- read_RAST("elevation") |
| 188 | +
|
| 189 | +# Vector |
| 190 | +schoolsr <- read_VECT("schools") |
| 191 | +``` |
| 192 | + |
| 193 | +## Python |
| 194 | + |
| 195 | +```{python} |
| 196 | +#| python.reticulate: FALSE |
| 197 | +# Raster into numpy array |
| 198 | +elev = gs.array.array("elevation") |
| 199 | +
|
| 200 | +# Vector attributes |
| 201 | +import pandas as pd |
| 202 | +schools = gs.parse_command("v.db.select", map="schools", format="json") |
| 203 | +pd.DataFrame(schools["records"]) |
| 204 | +
|
| 205 | +# Vector geometry and attributes to GeoJSON |
| 206 | +gs.run_command("v.out.ogr", input="schools", output="schools.geojson", format="GeoJSON") |
| 207 | +``` |
| 208 | + |
| 209 | +::: |
| 210 | + |
| 211 | + |
| 212 | +5. **Write R or Python objects into GRASS raster and vector maps**: R terra's |
| 213 | +SpatRaster and SpatVector objects can be written (back) into GRASS format with |
| 214 | +`write_RAST()` and `write_VECT()` functions. Within the Python environment, |
| 215 | +numpy arrays can also be written (back) into GRASS raster maps with the |
| 216 | +`write()` method. |
| 217 | + |
| 218 | +::: {.panel-tabset} |
| 219 | + |
| 220 | +## R |
| 221 | + |
| 222 | +```{r} |
| 223 | +# Raster |
| 224 | +write_RAST(elevr, "elevation_r") |
| 225 | +
|
| 226 | +# Vector |
| 227 | +write_VECT(schoolsr, "schools_r") |
| 228 | +``` |
| 229 | + |
| 230 | +## Python |
| 231 | + |
| 232 | +```{python} |
| 233 | +#| python.reticulate: FALSE |
| 234 | +# Raster |
| 235 | +elev.write(mapname="elev_np", overwrite=True) |
| 236 | +
|
| 237 | +# GeoJSON into GRASS vector |
| 238 | +gs.run_command("v.in.ogr", input="schools.geojson", output="schools2") |
| 239 | +``` |
| 240 | + |
| 241 | +::: |
| 242 | + |
| 243 | + |
| 244 | +6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well |
| 245 | +as shutting down Jupyter notebook, will clean up and close the GRASS session |
| 246 | +properly. Sometimes, however, especially if the user changed mapset within the |
| 247 | +workflow, it is better to clean up explicitly before closing. |
| 248 | + |
| 249 | +::: {.panel-tabset} |
| 250 | + |
| 251 | +## R |
| 252 | + |
| 253 | +```{r} |
| 254 | +unlink_.gislock() |
| 255 | +``` |
| 256 | + |
| 257 | +## Python |
| 258 | + |
| 259 | +```{python} |
| 260 | +#| python.reticulate: FALSE |
| 261 | +session.finish() |
| 262 | +``` |
| 263 | + |
| 264 | +::: |
| 265 | + |
| 266 | +## Final remarks |
| 267 | + |
| 268 | +The examples and comparisons presented here are intended to facilitate the |
| 269 | +combination of tools and languages as well as the exchange of data and format |
| 270 | +conversions. We hope that's useful as a starting point for the implementation |
| 271 | +of different use cases and workflows that suit the needs of users. |
| 272 | +See R and Python tutorials for more examples: |
| 273 | + |
| 274 | +* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd) |
| 275 | +* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd) |
| 276 | + |
| 277 | +## References |
| 278 | + |
| 279 | +* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html) |
| 280 | +* [rgrass docs](https://rsbivand.github.io/rgrass/) |
| 281 | + |
| 282 | + |
| 283 | +*** |
| 284 | + |
| 285 | +:::{.smaller} |
| 286 | +The development of this tutorial was funded by the US |
| 287 | +[National Science Foundation (NSF)](https://www.nsf.gov/), |
| 288 | +award [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651). |
| 289 | +::: |
0 commit comments