Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vignette on using QGIS expressions with qgisprocess #143

Merged
merged 22 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e561c0f
Vign QGIS expressions: start
florisvdh Mar 22, 2023
2ba7669
Vign QGIS expressions: examples for QGIS expression args
florisvdh Mar 23, 2023
bd696b4
Vign QGIS expressions: use kable() for acceptable values
florisvdh Mar 24, 2023
9829c4a
Vign QGIS expressions: minor updates
florisvdh Mar 24, 2023
8d5e43e
Vign QGIS expressions: add data-defined override example
florisvdh Mar 24, 2023
a78c42e
Vign QGIS expressions: adjust date
florisvdh Mar 24, 2023
f0d53f1
Vign QGIS expressions: rename file
florisvdh Mar 24, 2023
0c90e4c
Vign QGIS expressions: make qgisprocess boldface
florisvdh Mar 24, 2023
3ed0df2
Vign QGIS expressions: move code to child document *
florisvdh Mar 24, 2023
d6b02ce
Merge branch 'main' into vign_qgis_3.30
florisvdh Mar 24, 2023
8c9fe04
Vign QGIS expressions: update chunk options
florisvdh Mar 24, 2023
176fecc
Vign QGIS expressions: use rprojroot for pkgdown GHA workflow
florisvdh Mar 24, 2023
21f9b13
Vign QGIS expressions: fix GHA R-CMD-check on Windows *
florisvdh Mar 24, 2023
e4a6c7e
slightly rewrite intro
jannes-m Mar 26, 2023
32575c3
restructure and slighly clarify
jannes-m Mar 26, 2023
b1e32ec
add missing to
jannes-m Mar 26, 2023
1b34d3b
address minor issues
jannes-m Mar 26, 2023
7a73118
Vign QGIS expressions: clarify 'INPUT geometry'
florisvdh Mar 27, 2023
c74f495
Vign QGIS expressions: show geometry type of buffer object
florisvdh Mar 27, 2023
35958a0
Vign QGIS expressions: minor updates
florisvdh Mar 27, 2023
971e777
Vign QGIS expressions: add @jannes-m as author
florisvdh Mar 27, 2023
90b3e05
Vign QGIS expressions: update date
florisvdh Mar 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ Imports:
URL: https://github.com/r-spatial/qgisprocess
BugReports: https://github.com/r-spatial/qgisprocess/issues
Suggests:
dplyr,
tidyr,
testthat,
sf,
raster,
stars,
terra,
knitr,
rmarkdown,
stringi
stringi,
rprojroot
SystemRequirements: QGIS latest or long-term release (>= 3.28). Older QGIS
releases are not officially supported, but may work since QGIS 3.16.
VignetteBuilder: knitr
145 changes: 145 additions & 0 deletions man/vignette_childs/_qgis_expressions.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Introduction

Many QGIS processing algorithms provide the possibility to use [QGIS expressions](https://docs.qgis.org/latest/en/docs/user_manual/expressions/index.html).
If an algorithm argument expects a QGIS expression, this is typically marked by a button in the QGIS processing dialog that opens the QGIS expression builder (e.g. in `native:extractbyexpression`), or by a directly integrated QGIS expression builder (e.g. in `native:fieldcalculator`).
Such arguments are of type `expression`, as seen in the output of `qgis_arguments()`.

```{r}
qgis_arguments("native:fieldcalculator") |> subset(name == "FORMULA")
```

Secondly, one can use expressions for _data-defined_ overriding.
This means, that an algorithm argument that is usually a fixed (numeric or boolean) value can _also_ take on the value of another field or the result of an expression.
In the QGIS processing dialog, such arguments have a '[data-defined override](https://docs.qgis.org/latest/en/docs/user_manual/introduction/general_tools.html#data-defined)' button.
An example is provided by the `DISTANCE` argument of `native:buffer`, for which we query the acceptable values below.

```{r}
qgis_arguments("native:buffer") |>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With QGIS 3.28.2 this results it:

|acceptable_values |
|:-----------------|
|A numeric value   |

Not sure, if this is the desired result

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, later on you say that this is only possible with QGIS 3.30 onwards, ok

subset(name == "DISTANCE") |>
dplyr::select(acceptable_values) |>
tidyr::unnest(cols = acceptable_values) |>
knitr::kable()
```

# Examples where the argument expects a QGIS expression

In these cases, provide the expression as a string in R.

As example data, we use a lake polygon and a set of points that have lake depth as attribute.

```{r}
longlake_path <- system.file("longlake/longlake.gpkg", package = "qgisprocess")
longlake_depth_path <- system.file("longlake/longlake_depth.gpkg", package = "qgisprocess")
```

```{r}
nrow(read_sf(longlake_depth_path))
```

Using a QGIS expression to filter points by depth:

```{r}
qgis_run_algorithm(
"native:extractbyexpression",
INPUT = longlake_depth_path,
EXPRESSION = '"DEPTH_M" > 1',
.quiet = TRUE
) |>
st_as_sf()
```

More often, you will want to use QGIS functions in expressions, and look at the relationship between geometries or create new geometries.

Let's calculate the distance between the points and the lake border, and add it as an attribute to the points.
For that we will use the `native:fieldcalculator` algorithm.

We first create the lake border:

```{r}
lake_border_path <- qgis_run_algorithm(
"native:polygonstolines",
INPUT = longlake_path,
.quiet = TRUE
) |>
qgis_output("OUTPUT")
```

Next, build the QGIS expression.
Referring the `INPUT` geometry is done with the `@geometry` variable.

```{r}
expr <- glue::glue("distance(
@geometry,
geometry(
get_feature_by_id(
load_layer('{lake_border_path}', 'ogr'),
1
)
)
)")
```

Referring the lake border geometry in the expression is a bit more involved, since it needs several QGIS functions.
The layer can be loaded from a filepath with the `load_layer()` function, then the first (and only) feature is selected with `get_feature_by_id()`, and the geometry of that feature is referred by `geometry()`.
These steps are needed because the `distance()` function needs geometries to work on, not features, layers or filepaths.

Use the QGIS expression builder to look up function documentation, or consult the online [QGIS function documentation](https://docs.qgis.org/latest/en/docs/user_manual/expressions/functions_list.html).

Note: the `load_layer()` function is only available since QGIS 3.30.0!
In earlier versions, you needed to refer the layer's name as present in an _existing_ QGIS project, and refer the project path in `qgis_run_algorithm()` with the special `PROJECT_PATH` argument.
The `load_layer()` approach since QGIS 3.30.0 avoids the need for a QGIS project.

Now we can run the algorithm:

```{r eval=(package_version(strsplit(qgis_version(), "-")[[1]][1]) >= "3.30.0") && !is_windows()}
qgis_run_algorithm(
"native:fieldcalculator",
INPUT = longlake_depth_path,
FIELD_NAME = "distance",
FORMULA = expr
) |>
st_as_sf()
```

# Example applying a data-defined override

Suppose that we want to create a buffer around the points with a dynamic radius expressed as a function of `DEPTH_M`, e.g. 10 times the depth at each point.
We will use `native:buffer` for that purpose.
Note: applying a data-defined override with **qgisprocess** is only possible since since QGIS 3.30.0!

Because `DISTANCE` by default expects a numeric value, you have to use the prefix `expression:` if you want to pass an expression string.
Double quotes are used in QGIS expressions to denote fields (attributes), but you can also omit them.

Let's try:

```{r}
buffer <- qgis_run_algorithm(
"native:buffer",
INPUT = longlake_depth_path,
DISTANCE = 'expression: "DEPTH_M" * 10',
.quiet = TRUE
) |>
st_as_sf()
```

Plot the result:

```{r}
par(mar = rep(0.1, 4))
plot(read_sf(lake_border_path) |> st_geometry())
plot(buffer[, "DEPTH_M"], add = TRUE)
```

If you just want to refer the value of another attribute, then you can also use the `field:` prefix instead, _without_ double quotes around the attribute name and _without_ spaces:

```{r eval=FALSE}
qgis_run_algorithm(
"native:buffer",
INPUT = longlake_depth_path,
DISTANCE = "field:DEPTH_M"
) |>
st_as_sf()
```



31 changes: 31 additions & 0 deletions vignettes/qgis_expressions.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: "How to use QGIS expressions in qgisprocess?"
author: "Floris Vanderhaeghe"
date: "Last updated: 2023-03-24"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{How to use QGIS expressions in qgisprocess?}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```

```{r setup, message=FALSE}
library(qgisprocess)
library(sf)
```

```{r child=if (has_qgis()) file.path(rprojroot::find_root(rprojroot::is_r_package), "man/vignette_childs/_qgis_expressions.Rmd")}
```

```{r eval=!has_qgis(), echo=FALSE, results="asis"}
cat("This vignette has been built in absence of a QGIS installation.\n\n")
cat("Read it online at <https://r-spatial.github.io/qgisprocess/articles/qgis_expressions.html>.")
```