Skip to content

Commit 56bbcf3

Browse files
authored
Add tutorial comparing R and Python APIs (#3)
1 parent cbfdaef commit 56bbcf3

File tree

3 files changed

+305
-0
lines changed

3 files changed

+305
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"hash": "b2f5b1384f95aeb0e0a837650dceb10c",
3+
"result": {
4+
"markdown": "---\ntitle: \"Quick comparison: R and Python GRASS interfaces\"\nauthor: \"Veronica Andreo\"\ndate: 2024-04-01\ndate-modified: today\nformat:\n html:\n toc: true\n code-tools: true\n code-copy: true\n code-fold: false\ncategories: [Python, R, intermediate]\nengine: knitr\nexecute:\n eval: false\n---\n\n\n![](images/R_Python_compare.png){.preview-image width=50%}\n\nIn this short tutorial we will highlight the similarities of R and Python GRASS interfaces\nin order to streamline the use of GRASS GIS within R and Python communities.\nAs you may know, there's\nan R package called [rgrass](https://github.com/rsbivand/rgrass/) that provides\nbasic functionality to read and write data from and into GRASS database as well\nas to execute GRASS tools in either existing or temporary GRASS projects.\nThe [GRASS Python API](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html),\non the other hand, is composed of various packages that provide classes and\nfunctions for low and high level tasks, including those that can be executed\nwith rgrass.\n\n\n\nThere are some parallelisms between the\n**rgrass** and **grass.script**/**grass.jupyter** packages, i.e.,\nR and Python interfaces to GRASS GIS.\nLet's review them and go through some examples.\n\n\n| Task | rgrass function | GRASS Python API function |\n|------------------------------------------------------------|--------------------------------|---------------------------------------------------------|\n| Load library | library(rgrass) | import grass.script as gs<br>import grass.jupyter as gj |\n| Start GRASS and set all needed <br>environmental variables | initGRASS() | gs.setup.init() for scripts,<br>gj.init() for notebooks |\n| Execute GRASS commands | execGRASS() | gs.run_command(),<br>gs.read_command(),<br>gs.parse_command() |\n| Read raster and vector data <br>from GRASS | read_RAST(),<br>read_VECT() | gs.array.array(),<br>n/a |\n| Write raster and vector data<br>into GRASS | write_RAST(),<br>write_VECT() | gs.array.write(),<br>n/a |\n| Get raster and vector info | n/a,<br>vInfo() | gs.raster_info(),<br>gs.vector_info() |\n| Close GRASS session | unlink_.gislock() | gs.setup.finish(),<br>gj.finish() |\n\n: R and Python GRASS interfaces compared {.striped .hover}\n\n## Comparison examples\n\nLet's see how usage examples would look like.\n\n1. **Load the library**: We need to\nload the libraries that allow us to interface with GRASS GIS\nfunctionality and (optionally) data. For the Python case, we first need to add\nthe GRASS python package path to our system's path.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rgrass)\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nimport sys\nimport subprocess\n\nsys.path.append(\n subprocess.check_output([\"grass\", \"--config\", \"python_path\"], text=True).strip()\n)\n\nimport grass.script as gs\nimport grass.jupyter as gj\n```\n:::\n\n\n:::\n\n2. **Start a GRASS session**: Once we loaded or imported the packages, we\nstart a GRASS session. We need to pass the path to a\ntemporary or existing GRASS project.\nIn the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can\nspecify the path to the binaries ourselves.\nIn the case of Python, it is worth noting that while grass.script and grass.jupyter init functions\ntake the same arguments, `gj.init` also sets other environmental variables to\nstreamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells\ncan be executed multiple times.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsession <- initGRASS(gisBase = \"/usr/lib/grass84\", # where grass binaries live, `grass --config path`\n gisDbase = \"/home/user/grassdata\", # path to grass database or folder where your project lives\n location = \"nc_basic_spm_grass7\", # existing project name\n mapset = \"PERMANENT\" # mapset name\n )\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# With grass.script for scripts\nsession = gs.setup.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gs.setup.init(\"/home/user/grassdata/nc_basic_spm_grass7/PERMANENT\")\n\n# With grass.jupyter for notebooks\nsession = gj.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gj.init(\"~/grassdata/nc_basic_spm_grass7/PERMANENT\")\n```\n:::\n\n\n:::\n\n3. **Execute GRASS commands**: Both interfaces work pretty similarly, the\nfirst argument is always the GRASS tool name and then we pass the parameters\nand flags. While in R we basically use `execGRASS()` for all GRASS commands, in\nthe Python API, we have different wrappers to execute GRASS commands depending\non the nature of their output.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Map output\nexecGRASS(\"r.slope.aspect\",\n elevation = \"elevation\",\n slope = \"slope\",\n aspect = \"aspect\")\n\n# Text output\nexecGRASS(\"g.region\",\n raster = \"elevation\",\n flags = \"p\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Map output\ngs.run_command(\"r.slope.aspect\",\n elevation=\"elevation\",\n slope=\"slope\",\n aspect=\"aspect\")\n# Text output\nprint(gs.read_command(\"g.region\",\n raster=\"elevation\",\n flags=\"p\"))\n# Text output - dictionary\nregion = gs.parse_command(\"g.region\",\n raster=\"elevation\",\n flags=\"g\")\nregion\n```\n:::\n\n\n:::\n\n4. **Read raster and vector data into other R or Python formats**:\n*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and\nvector maps into terra's SpatRaster and SpatVector objects within R.\nIn the case of Python, GRASS\nraster maps that can be converted into numpy arrays through\n`gs.array.array()`. Vector attribute data can be converted into\nPandas data frames in various ways.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nelevr <- read_RAST(\"elevation\")\n\n# Vector\nschoolsr <- read_VECT(\"schools\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster into numpy array\nelev = gs.array.array(\"elevation\")\n\n# Vector attributes\nimport pandas as pd\nschools = gs.parse_command(\"v.db.select\", map=\"schools\", format=\"json\")\npd.DataFrame(schools[\"records\"])\n\n# Vector geometry and attributes to GeoJSON\ngs.run_command(\"v.out.ogr\", input=\"schools\", output=\"schools.geojson\", format=\"GeoJSON\")\n```\n:::\n\n\n:::\n\n\n5. **Write R or Python objects into GRASS raster and vector maps**: R terra's\nSpatRaster and SpatVector objects can be written (back) into GRASS format with\n`write_RAST()` and `write_VECT()` functions. Within the Python environment,\nnumpy arrays can also be written (back) into GRASS raster maps with the\n`write()` method.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nwrite_RAST(elevr, \"elevation_r\")\n\n# Vector\nwrite_VECT(schoolsr, \"schools_r\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster\nelev.write(mapname=\"elev_np\", overwrite=True)\n\n# GeoJSON into GRASS vector\ngs.run_command(\"v.in.ogr\", input=\"schools.geojson\", output=\"schools2\")\n```\n:::\n\n\n:::\n\n\n6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well\nas shutting down Jupyter notebook, will clean up and close the GRASS session\nproperly. Sometimes, however, especially if the user changed mapset within the\nworkflow, it is better to clean up explicitly before closing.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nunlink_.gislock()\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nsession.finish()\n```\n:::\n\n\n:::\n\n## Final remarks\n\nThe examples and comparisons presented here are intended to facilitate the\ncombination of tools and languages as well as the exchange of data and format\nconversions. We hope that's useful as a starting point for the implementation\nof different use cases and workflows that suit the needs of users.\nSee R and Python tutorials for more examples:\n\n* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd)\n* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd)\n\n## References\n\n* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html)\n* [rgrass docs](https://rsbivand.github.io/rgrass/)\n\n\n***\n\n:::{.smaller}\nThe development of this tutorial was funded by the US\n[National Science Foundation (NSF)](https://www.nsf.gov/),\naward [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651).\n:::\n",
5+
"supporting": [
6+
"quick_comparison_r_vs_python_grass_interfaces_files"
7+
],
8+
"filters": [
9+
"rmarkdown/pagebreak.lua"
10+
],
11+
"includes": {},
12+
"engineDependencies": {},
13+
"preserve": {},
14+
"postProcess": true
15+
}
16+
}
30 KB
Loading
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
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+
![](images/R_Python_compare.png){.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

Comments
 (0)