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

Adding support for SpatVector (and SpatVectorProxy) #184

Merged
merged 9 commits into from
Dec 15, 2023
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: qgisprocess
Title: Use 'QGIS' Processing Algorithms
Version: 0.1.0.9178
Version: 0.1.0.9179
Authors@R: c(
person("Dewey", "Dunnington", , "[email protected]", role = "aut",
comment = c(ORCID = "0000-0002-9415-4582", affiliation = "Voltron Data")),
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ S3method(as_qgis_argument,RasterBrick)
S3method(as_qgis_argument,RasterLayer)
S3method(as_qgis_argument,SpatExtent)
S3method(as_qgis_argument,SpatRaster)
S3method(as_qgis_argument,SpatVector)
S3method(as_qgis_argument,SpatVectorProxy)
S3method(as_qgis_argument,bbox)
S3method(as_qgis_argument,character)
S3method(as_qgis_argument,crs)
Expand Down Expand Up @@ -41,6 +43,7 @@ S3method(qgis_as_raster,qgis_outputRaster)
S3method(qgis_as_raster,qgis_result)
S3method(qgis_as_terra,qgis_outputLayer)
S3method(qgis_as_terra,qgis_outputRaster)
S3method(qgis_as_terra,qgis_outputVector)
S3method(qgis_as_terra,qgis_result)
S3method(qgis_clean_argument,default)
S3method(qgis_clean_argument,qgis_dict_input)
Expand Down
12 changes: 6 additions & 6 deletions R/compat-sf.R
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ st_as_sf.qgis_result <- function(x, ...) {
#' @rdname st_as_sf
#' @exportS3Method sf::st_as_sf
st_as_sf.qgis_outputVector <- function(x, ...) {
if (grepl("\\|layer", x)) {
output_splitted <- strsplit(x, "\\|layer.*=")[[1]]
sf::read_sf(output_splitted[1], output_splitted[2], ...)
} else {
sf::read_sf(x, ...)
}
qgis_as_sf(x, ...)
}

#' @rdname st_as_sf
#' @exportS3Method sf::st_as_sf
st_as_sf.qgis_outputLayer <- function(x, ...) {
qgis_as_sf(x, ...)
}

#' @keywords internal
qgis_as_sf <- function(x, ...) {
if (grepl("\\|layer", x)) {
output_splitted <- strsplit(x, "\\|layer.*=")[[1]]
sf::read_sf(output_splitted[1], output_splitted[2], ...)
Expand Down
178 changes: 164 additions & 14 deletions R/compat-terra.R
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
#' Convert a qgis_result object or one of its elements to a terra object
#'
#' This function performs coercion to one of the terra classes
#' `SpatRaster`, `SpatVector` or `SpatVectorProxy` (add `proxy = TRUE` for the
#' latter).
#' The distinction between `SpatRaster` and `SpatVector` is based on the
#' output type.
#'
#' @family topics about coercing processing output
#' @family topics about accessing or managing processing results
#'
#' @param ... Arguments passed to [terra::rast()].
#' @param ... Arguments passed to [terra::rast()] or [terra::vect()], depending
#' on the output type of `x` (or one of its elements, if `x` is a
#' `qgis_result`).
#' @inheritParams qgis_as_raster
#'
#' @returns A `SpatRaster` or a `SpatVector` object.
#' @returns A `SpatRaster`, `SpatVector` or `SpatVectorProxy` object.
#'
#' @examplesIf has_qgis() && requireNamespace("terra", quietly = TRUE)
#' \donttest{
Expand All @@ -23,6 +31,20 @@
#' # if you need more control, extract the needed output element first:
#' output_raster <- qgis_extract_output(result, "OUTPUT")
#' qgis_as_terra(output_raster)
#'
#' # Same holds for coercion to SpatVector
#' result2 <- qgis_run_algorithm(
#' "native:buffer",
#' INPUT = system.file("longlake/longlake.gpkg", package = "qgisprocess"),
#' DISTANCE = 100
#' )
#'
#' qgis_as_terra(result2)
#' output_vector <- qgis_extract_output(result2, "OUTPUT")
#' qgis_as_terra(output_vector)
#'
#' # SpatVectorProxy:
#' qgis_as_terra(result2, proxy = TRUE)
#' }
#'
#' @name qgis_as_terra
Expand All @@ -42,27 +64,52 @@
#' @rdname qgis_as_terra
#' @export
qgis_as_terra.qgis_outputLayer <- function(x, ...) {
terra::rast(unclass(x), ...)
tryCatch(
terra::rast(unclass(x), ...),
error = function(e) {
qgis_as_spatvector(x, ...)

Check warning on line 70 in R/compat-terra.R

View check run for this annotation

Codecov / codecov/patch

R/compat-terra.R#L70

Added line #L70 was not covered by tests
},
warning = function(w) {
if (!grepl("not recognized as a supported file format", w)) {
warning(w)

Check warning on line 74 in R/compat-terra.R

View check run for this annotation

Codecov / codecov/patch

R/compat-terra.R#L74

Added line #L74 was not covered by tests
}
qgis_as_spatvector(x, ...)
}
)
}

#' @rdname qgis_as_terra
#' @export
qgis_as_terra.qgis_outputVector <- function(x, ...) {
qgis_as_spatvector(x, ...)
}

#' @rdname qgis_as_terra
#' @export
qgis_as_terra.qgis_result <- function(x, ...) {
result <- qgis_extract_output_by_class(x, c("qgis_outputRaster", "qgis_outputLayer"))
terra::rast(unclass(result), ...)
result <- qgis_extract_output_by_class(
x,
c("qgis_outputRaster", "qgis_outputVector", "qgis_outputLayer")
)
qgis_as_terra(result, ...)
}

#' @keywords internal
qgis_as_spatvector <- function(x, ...) {
if (grepl("\\|layer", unclass(x))) {
output_splitted <- strsplit(unclass(x), "\\|layer.*=")[[1]]
terra::vect(output_splitted[1], output_splitted[2], ...)
} else {
terra::vect(unclass(x), ...)
}
}


# @param x A [terra::rast()].
#' @keywords internal
#' @export
as_qgis_argument.SpatRaster <- function(x, spec = qgis_argument_spec(),
use_json_input = FALSE) {
as_qgis_argument_terra(x, spec, use_json_input)
}

#' @keywords internal
as_qgis_argument_terra <- function(x, spec = qgis_argument_spec(),
use_json_input = FALSE) {
if (!isTRUE(spec$qgis_type %in% c("raster", "layer", "multilayer"))) {
abort(glue("Can't convert '{ class(x)[1] }' object to QGIS type '{ spec$qgis_type }'"))
}
Expand All @@ -83,13 +130,20 @@
sources <- sources$source
}

if (!identical(sources, "") && length(sources) == 1) {
if (!identical(sources, "") && identical(length(sources), 1L)) {
accepted_ext <- c("grd", "asc", "sdat", "rst", "nc", "tif", "tiff", "gtiff", "envi", "bil", "img")
file_ext <- stringr::str_to_lower(tools::file_ext(sources))
if (file_ext %in% accepted_ext) {
names_match <- identical(names(x), names(terra::rast(sources)))
if (names_match) {
reread <- terra::rast(sources)
names_match <- identical(names(x), names(reread))
crs_match <- identical(terra::crs(x), terra::crs(reread))
if (names_match && crs_match) {
return(sources)
} else if (!crs_match) {
message(glue(
"Rewriting the SpatRaster object as a temporary file before passing to QGIS, since ",
"its CRS has been set to another value than that in the source file '{ sources }'."
))
} else if (terra::nlyr(x) > 1L) {
message(glue(
"Rewriting the multi-band SpatRaster object as a temporary file before passing to QGIS, since ",
Expand All @@ -110,6 +164,102 @@
structure(path, class = "qgis_tempfile_arg")
}



#' @keywords internal
#' @export
as_qgis_argument.SpatVector <- function(x, spec = qgis_argument_spec(),
use_json_input = FALSE) {
as_qgis_argument_terra_vector(x, spec, use_json_input)
}


#' @keywords internal
#' @export
as_qgis_argument.SpatVectorProxy <- function(x, spec = qgis_argument_spec(),
use_json_input = FALSE) {
as_qgis_argument_terra_vector(x, spec, use_json_input)
}


#' @keywords internal
as_qgis_argument_terra_vector <- function(x,
spec = qgis_argument_spec(),
use_json_input = FALSE) {
class <- class(x)[1]

if (!isTRUE(spec$qgis_type %in% c("source", "layer", "vector", "multilayer", "point"))) {
abort(glue("Can't convert '{ class }' object to QGIS type '{ spec$qgis_type }'"))
}

# try to use a filename if present
sources <- terra::sources(x)
if (!is.character(sources)) {
sources <- sources$source

Check warning on line 198 in R/compat-terra.R

View check run for this annotation

Codecov / codecov/patch

R/compat-terra.R#L198

Added line #L198 was not covered by tests
}
if (
!identical(sources, "") &&
identical(length(sources), 1L) &&
!identical(spec$qgis_type, "point")
) {
# rewrite if attribute names differ from source:
if (grepl("::", sources)) {
chunks <- strsplit(sources, "::")[[1]]
proxy <- terra::vect(chunks[1], chunks[2], proxy = TRUE)
source_names <- names(proxy)
} else {
proxy <- terra::vect(sources, proxy = TRUE)
source_names <- names(proxy)
}
if (!identical(names(x), source_names)) {
message(glue(
"Rewriting the {class} object as a temporary file before passing to QGIS, since ",
"its attribute names (including order, selection) differ from those in the source file '{ sources }'."
))
# rewrite if CRS differs (terra source reference is kept if CRS is reset,
# not if data is transformed):
} else if (!identical(terra::crs(x), terra::crs(proxy))) {
message(glue(
"Rewriting the {class} object as a temporary file before passing to QGIS, since ",
"its CRS has been set to another value than that in the source file '{ sources }'."
))
} else {
return(sub("::", "|layername=", sources))
}
}

if (identical(spec$qgis_type, "point")) {
assert_that(
identical(terra::geomtype(x), "points"), # is.points() not defined for proxy
identical(nrow(x), 1),
msg = glue(
"QGIS argument type 'point' can take a {class} object, but it must ",
"have exactly one row and the geometry must be a point."
)
)
crs_code <- as.character(terra::crs(x, describe = TRUE)[, c("authority", "code")])
if (inherits(x, "SpatVectorProxy")) {
x <- terra::query(x, n = 1)
}
coord <- terra::geom(x)[1, c("x", "y")]
if (!any(is.na(crs_code))) {
return(glue("{coord[1]},{coord[2]}[{crs_code[1]}:{crs_code[2]}]"))
} else {
return(glue("{coord[1]},{coord[2]}"))
}
}

# (re)write to file
if (inherits(x, "SpatVectorProxy")) {
x <- terra::query(x)

Check warning on line 254 in R/compat-terra.R

View check run for this annotation

Codecov / codecov/patch

R/compat-terra.R#L254

Added line #L254 was not covered by tests
}
path <- qgis_tmp_vector()
terra::writeVector(x, path)
structure(path, class = "qgis_tempfile_arg")
}



#' @keywords internal
#' @export
as_qgis_argument.SpatExtent <- function(x, spec = qgis_argument_spec(),
Expand Down
29 changes: 26 additions & 3 deletions man/qgis_as_terra.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading