# .center[Interfacing QGIS spatial processing algorithms from R] <img src="images/qgisprocess.svg" width="35%" style="display: block; margin: auto;" /> .pull-left[.footnote[ <img src="images/useR Salzburg 2024 logo.png" width="20%" hspace="20vw"/> <img src="images/flanders.png" width="10%" hspace="20vw"/> <img src="images/inbo.jpg" width="15%"/> ]] ## .pull-right[.footnote[.right[Floris Vanderhaeghe<br/>Dewey Dunnington<br/>Nyall Dawson<br/>Jan Caha<br/>Jannes Muenchow<br/>Jakub Nowosad<br/>Robin Lovelace<br/><br/>[html slides](https://florisvdh.github.io/user-2024-qgisprocess/)<br/>[
GitHub
](https://github.com/florisvdh) [
Mastodon
](https://fosstodon.org/@florisvdh) [
ORCID
](https://orcid.org/0000-0002-6378-6229)]]] --- class: inverse, middle, center # About QGIS --- layout: false # QGIS -- Popular open-source geographic information system (GIS) program to: -- - view geospatial data interactively -- - create and edit spatial data layers -- - perform advanced geoprocessing with algorithms of QGIS, GRASS GIS, GDAL ... -- - make sophisticated maps, atlases and reports --- <img src="images/qgis_general.png" width="100%" style="display: block; margin: auto;" /> --- class: inverse, middle, center # Why interface QGIS processing from R? --- layout: false # Main advantages -- - Expand geospatial processing abilities in R -- - Still have all other processing capabilities of R -- - Reproducibility -- - No QGIS project needed -- - Unified interface to QGIS, GRASS GIS, SAGA, GDAL and other processing providers (1000+ geospatial algorithms) <br/> .center[ <img src="images/qgis.svg" width="10%" hspace="15vw"/> <img src="images/grass.svg" width="10%" hspace="15vw"/> <img src="images/saga.png" width="10%" hspace="15vw"/> <img src="images/gdal.svg" width="10%"/> ] --- layout: true # Interfacing QGIS processing from R --- - Formerly: packages **RQGIS** and **RQGIS3** (Muenchow & Schratz) for QGIS 2 and 3 - use the QGIS Python API - set QGIS environment variables - **RQGIS3** was hard to maintain wrt provider changes in QGIS; also, crashes were observed in RStudio IDE - no longer developed --- - Since QGIS 3.16: packages **qgisprocess** & **qgis** - use the recent standalone `qgis_process` shell command from QGIS = a unified entry to all providers and algorithms! - no more QGIS environment variables needed - actively developed --- ``` $ qgis_process QGIS Processing Executor - 3.38.0-Grenoble 'Grenoble' (3.38.0-Grenoble) Usage: /usr/bin/qgis_process.bin [--help] [--version] [--json] [--verbose] [--no-python] [--skip-loading-plugins] [command] [algorithm id, path to model file, or path to Python script] [parameters] ... ``` `$ qgis_process run <algorithm> [parameters]` `$ qgis_process run <algorithm> -` `$ qgis_process plugins` --- layout: false # Current R-packages - **qgisprocess** (Dewey Dunnington et al.): direct interface to `qgis_process` - https://r-spatial.github.io/qgisprocess/ ``` r install.packages("qgisprocess") ``` - **qgis** (Jan Caha): functions for each algorithm; uses **qgisprocess** package - https://jancaha.github.io/r_package_qgis/ ``` r remotes::install_github("jancaha/qgis") ``` --- class: inverse, center, middle # A simple example --- layout: true # Basic usage --- <img src="index_files/figure-html/seine-map-1.png" style="display: block; margin: auto;" /> --- Load the R package: ``` r library(qgisprocess) ``` ``` #> Attempting to load the package cache ... Success! #> QGIS version: 3.38.0-Grenoble #> Having access to 2059 algorithms from 18 QGIS processing providers. #> Run `qgis_configure(use_cached_data = TRUE)` to reload cache and get more details. ``` --- ``` r seine_path <- "data/seine.gpkg" ``` ``` r (seine <- sf::read_sf(seine_path)) ``` ``` #> Simple feature collection with 3 features and 1 field #> Geometry type: MULTILINESTRING #> Dimension: XY #> Bounding box: xmin: 518344.7 ymin: 6660431 xmax: 879955.3 ymax: 6938864 #> Projected CRS: RGF93 v1 / Lambert-93 #> # A tibble: 3 × 2 #> name geom #> <chr> <MULTILINESTRING [m]> #> 1 Marne ((879955.3 6755725, 878440.9 6755688, 876653.8 6756227, 874212.2 675791… #> 2 Seine ((828893.6 6713873, 828216.3 6715450, 827937.9 6716999, 828199.2 671851… #> 3 Yonne ((773482.1 6660431, 771342.9 6665712, 771043 6667566, 770931.7 6669151,… ``` --- Run algorithm: ``` r result <- qgis_run_algorithm( algorithm = "native:pointsalonglines", INPUT = seine_path, DISTANCE = 1e4 ) #> Argument `START_OFFSET` is unspecified (using QGIS default value). #> Argument `END_OFFSET` is unspecified (using QGIS default value). #> Using `OUTPUT = qgis_tmp_vector()` ``` -- or: ``` r result <- qgis::qgis_pointsalonglines( INPUT = seine_path, DISTANCE = 1e4 ) ``` --- Extract output (defaults to the `"OUTPUT"` element of `result`). ``` r qgis_extract_output(result) ``` ``` #> [1] "/tmp/RtmpUtuVpc/file5288578598f1/file52883dccf462.gpkg" #> attr(,"class") #> [1] "qgis_outputVector" ``` --- <img src="index_files/figure-html/seine-points-map-1.png" style="display: block; margin: auto;" /> --- layout: false class: inverse, center, middle # Convenient functions --- layout: true # Finding algorithms --- ``` r qgis_search_algorithms(algorithm = "point.*line") ``` <table> <thead> <tr> <th style="text-align:left;"> provider </th> <th style="text-align:left;"> algorithm </th> <th style="text-align:left;"> algorithm_title </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> gdal </td> <td style="text-align:left;"> gdal:pointsalonglines </td> <td style="text-align:left;"> Points along lines </td> </tr> <tr> <td style="text-align:left;"> native </td> <td style="text-align:left;"> native:interpolatepoint </td> <td style="text-align:left;"> Interpolate point on line </td> </tr> <tr> <td style="text-align:left;"> native </td> <td style="text-align:left;"> native:pointsalonglines </td> <td style="text-align:left;"> Points along geometry </td> </tr> <tr> <td style="text-align:left;"> native </td> <td style="text-align:left;"> native:randompointsonlines </td> <td style="text-align:left;"> Random points on lines </td> </tr> <tr> <td style="text-align:left;"> qgis </td> <td style="text-align:left;"> qgis:generatepointspixelcentroidsalongline </td> <td style="text-align:left;"> Generate points (pixel centroids) along line </td> </tr> <tr> <td style="text-align:left;"> qgis </td> <td style="text-align:left;"> qgis:randompointsalongline </td> <td style="text-align:left;"> Random points along line </td> </tr> <tr> <td style="text-align:left;"> sagang </td> <td style="text-align:left;"> sagang:convertpointstolines </td> <td style="text-align:left;"> Convert points to line(s) </td> </tr> <tr> <td style="text-align:left;"> sagang </td> <td style="text-align:left;"> sagang:pointtolinedistances </td> <td style="text-align:left;"> Point to line distances </td> </tr> <tr> <td style="text-align:left;"> sagang </td> <td style="text-align:left;"> sagang:snappointstolines </td> <td style="text-align:left;"> Snap points to lines </td> </tr> </tbody> </table> --- ``` r qgis_providers() ``` ``` #> # A tibble: 18 × 3 #> provider provider_title algorithm_count #> <chr> <chr> <int> #> 1 cartographytools Cartography tools 5 #> 2 gdal GDAL 56 #> 3 grass GRASS 307 #> 4 latlontools Lat Lon tools 10 #> 5 model Models 3 #> 6 NetworkGT NetworkGT 33 #> 7 Networks Networks 48 #> 8 pcraster PCRaster 102 #> 9 qgis QGIS 42 #> 10 3d QGIS (3D) 1 #> 11 native QGIS (native c++) 263 #> 12 pdal QGIS (PDAL) 17 #> 13 qneat3 QNEAT3 - Qgis Network Analysis Toolbox 14 #> 14 sagang SAGA Next Gen 587 #> 15 script Scripts 1 #> 16 Valhalla Valhalla 20 #> 17 visibility Visibility analysis 4 #> 18 wbt WhiteboxTools 546 ``` --- ``` r qgis_plugins() ``` ``` #> # A tibble: 12 × 2 #> name enabled #> <chr> <lgl> #> 1 QNEAT3 TRUE #> 2 ViewshedAnalysis TRUE #> 3 cartography_tools TRUE #> 4 grassprovider TRUE #> 5 latlontools TRUE #> 6 network_gt TRUE #> 7 networks TRUE #> 8 pcraster_tools TRUE #> 9 processing TRUE #> 10 processing_saga_nextgen TRUE #> 11 valhalla TRUE #> 12 wbt_for_qgis TRUE ``` --- layout:false # Algorithm documentation ``` r qgis_show_help("native:pointsalonglines") ``` ``` r ## Points along geometry (native:pointsalonglines) ## ## ---------------- ## Description ## ---------------- ## Creates regularly spaced points along line features. ## This algorithm creates a points layer, with points distributed along the lines of an input vector layer. The distance between points (measured along the line) is defined as a parameter. ## ## Start and end offset distances can be defined, so the first and last point will not fall exactly on the line's first and last nodes. These start and end offsets are defined as distances, measured along the line from the first and last nodes of the lines. ## ## ---------------- ## Arguments ## ---------------- ## ## INPUT: Input layer ## Argument type: source ## Acceptable values: ## - Path to a vector layer ## DISTANCE: Distance ## Default value: 1 ## ..... ``` --- layout: true # Supports various R objects as input arguments --- Spatial QGIS argument types <br/> input argument | R object :------ | :--- vector layer | **sf** or **terra** raster layer | **stars**, **terra** and **raster** multilayer | list (preferrably as `qgis_list_input()`) extent | various 'bounding box' and 'extent' objects crs | various CRS objects --- Non-spatial QGIS argument types: <br/> input argument | R object :------ | :--- expression | string enum | integer or character range | vector matrix | matrix or dataframe color | R color string hierarchical types (e.g. aggregates) | nested list ... | ... --- ``` r library(terra) elev <- rast(system.file("ex/elev.tif", package = "terra")) class(elev) ``` ``` #> [1] "SpatRaster" #> attr(,"package") #> [1] "terra" ``` -- ``` r qgis_run_algorithm("native:rasterlayerstatistics", INPUT = elev, BAND = 1) ``` ``` #> <Result of `qgis_run_algorithm("native:rasterlayerstatistics", ...)`> #> List of 9 #> $ COUNT : num 4608 #> $ MAX : num 547 #> $ MEAN : num 348 #> $ MIN : num 141 #> $ OUTPUT_HTML_FILE: 'qgis_outputHtml' chr "/tmp/RtmpUtuVpc/file5288578598f1/file52887ef8bb06" #> $ RANGE : num 406 #> $ STD_DEV : num 80.2 #> $ SUM : num 1605135 #> $ SUM_OF_SQUARES : num 29646349 ``` --- layout: true # Result handling --- ## Extracting elements from the result By default, the `OUTPUT` element is selected by `qgis_extract_output()`. - typically contains a file path --- ## Extracting elements from the result Which output elements are generated by an algorithm? ``` r qgis_get_output_specs("grass:r.flow") ``` ``` #> # A tibble: 3 × 3 #> name description qgis_output_type #> <chr> <chr> <chr> #> 1 flowaccumulation Flow accumulation outputRaster #> 2 flowlength Flow path length outputRaster #> 3 flowline Flow line outputVector ``` -- So, sometimes you need to specify the output name: ``` r qgis_extract_output(result, "flowline") ``` --- ## Coercing output to R objects Result object or output element can be coerced to an R object: ``` r sf::st_as_sf(result) # takes OUTPUT by default ``` -- ``` r result |> qgis_extract_output("flowline") |> sf::st_as_sf() ``` -- ``` r qgis_as_terra(result) ``` ``` r qgis_as_raster(result) ``` ``` r stars::st_as_stars(result) ``` --- layout:false class: inverse, center, middle # Extra!<br/><br/>Extra! --- # Algorithm piping ``` r seine_points_buffer <- seine |> qgis_run_algorithm_p("native:pointsalonglines", DISTANCE = 1e4) |> qgis_run_algorithm_p("native:buffer", DISTANCE = 1000, OUTPUT = "data/buffer.gpkg") seine_points_buffer ``` ``` #> <Result of `qgis_run_algorithm("native:buffer", ...)`> #> List of 1 #> $ OUTPUT: 'qgis_outputVector' chr "data/buffer.gpkg" ``` -- Alternative with **qgis** package: ``` r seine |> qgis::qgis_pointsalonglines(DISTANCE = 1e4) |> qgis_extract_output() |> qgis::qgis_buffer(DISTANCE = 1000) ``` ``` #> <Result of `qgis_run_algorithm("native:buffer", ...)`> #> List of 1 #> $ OUTPUT: 'qgis_outputVector' chr "/tmp/RtmpUtuVpc/file5288578598f1/file528896a5b20.gpkg" ``` --- layout:true # Taking parameters from the QGIS GUI --- <img src="images/qgis_copy_json.png" width="80%" style="display: block; margin: auto;" /> --- ``` r jsonlite::prettify(json_from_qgis) ``` ``` #> { #> "area_units": "m2", #> "distance_units": "meters", #> "ellipsoid": "EPSG:7030", #> "inputs": { #> "COLUMN_PREFIX": "dem_", #> "INPUT": "/home/floris/git_repositories/foss4g-2023-qgisprocess/data/buffer.gpkg|layername=buffer", #> "INPUT_RASTER": "/home/floris/git_repositories/foss4g-2023-qgisprocess/data/dem_clip.tif", #> "OUTPUT": "ogr:dbname='/home/floris/git_repositories/foss4g-2023-qgisprocess/data/zonal_stats.gpkg' table=\"seine_zonal\" (geom)", #> "RASTER_BAND": 1, #> "STATISTICS": [ #> 2, #> 3, #> 4, #> 5, #> 6 #> ] #> } #> } #> ``` --- ``` r zonal_stats_result <- qgis_run_algorithm( "native:zonalstatisticsfb", * .raw_json_input = json_from_qgis ) ``` ``` r zonal_stats_result ``` ``` #> <Result of `qgis_run_algorithm("native:zonalstatisticsfb", ...)`> #> List of 1 #> $ OUTPUT: 'qgis_outputVector' chr "/home/floris/git_repositories/foss4g-2023-qgisprocess/data/zonal_stats.gpkg|layername=seine_zonal" ``` --- layout:false # Result ``` r zonal_stats <- sf::st_as_sf(zonal_stats_result) |> sf::st_drop_geometry() |> mutate(distance = set_units(distance, "m") |> set_units("km")) zonal_stats ``` ``` #> # A tibble: 123 × 8 #> name distance angle dem_mean dem_median dem_stdev dem_min dem_max #> <chr> [km] <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> #> 1 Marne 0 269. 372. 366. 22.0 344. 412. #> 2 Marne 10 322. 340. 338 21.9 309. 416. #> 3 Marne 20 327. 331. 334. 23.9 287. 382. #> 4 Marne 30 332. 296. 280. 29.5 264. 359. #> 5 Marne 40 5.76 286. 295. 24.3 238. 317. #> 6 Marne 50 358. 269. 254. 45.4 221. 378. #> 7 Marne 60 22.4 235. 220. 32.7 201. 312. #> 8 Marne 70 350. 202. 188. 24.8 183. 306. #> 9 Marne 80 335. 196. 184. 26.9 167. 276 #> 10 Marne 90 334. 182. 184. 19.6 150. 209. #> # ℹ 113 more rows ``` --- class: inverse, center, middle # Online documentation --- class: center # <https://r-spatial.github.io/qgisprocess> <iframe src="https://r-spatial.github.io/qgisprocess/reference/index.html" width="100%" height="460px" data-external="1"></iframe> --- class: center # <https://jancaha.github.io/r_package_qgis> <iframe src="https://jancaha.github.io/r_package_qgis/reference/index.html" width="100%" height="460px" data-external="1"></iframe> --- class: center # Cheat sheet ! <img src="https://r-spatial.github.io/qgisprocess/articles/img/qgisprocess_en.png" width="65%" /> --- class: center # Section in book 'Geospatial Computation with R' <iframe src="https://r.geocompx.org/gis#rqgis" width="100%" height="400px" data-external="1"></iframe> .footnote[### <https://r.geocompx.org>] --- class: inverse, center, middle # Recent developments --- # Recent developments - Speed-up since QGIS 3.36 & **qgisprocess** 0.4.0 -- - Added vector support for R package **terra** -- - Vignettes: - which R objects are accepted as algorithm arguments? - how to configure behaviour with options or environment variables? - how to pass QGIS expressions? -- - Improved handling of deprecated algorithms --- class: inverse, center, middle # Questions?<br/><br/>Ideas? ### .pull-right-wide[.footnote[.right[Slides: <https://florisvdh.github.io/user-2024-qgisprocess/>]]]