Nothing
#' Creates a buffer around geometries
#'
#' Computes a polygon that represents all locations within a specified distance from the
#' original geometry
#'
#' @template x
#' @param distance a numeric value specifying the buffer distance. Units correspond to
#' the coordinate system of the geometry (e.g. degrees or meters)
#' @param num_triangles an integer representing how many triangles will be produced to
#' approximate a quarter circle. The larger the number, the smoother the resulting geometry.
#' Default is 8.
#' @param cap_style a character string specifying the cap style. Must be one of
#' "CAP_ROUND" (default), "CAP_FLAT", or "CAP_SQUARE". Case-insensitive.
#' @param join_style a character string specifying the join style. Must be one of
#' "JOIN_ROUND" (default), "JOIN_MITRE", or "JOIN_BEVEL". Case-insensitive.
#' @param mitre_limit a numeric value specifying the mitre limit ratio. Only applies when
#' \code{join_style} is "JOIN_MITRE". It is the ratio of the distance from the corner to
#' the mitre point to the corner radius. Default is 1.0.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, argentina_ddbs, "argentina")
#'
#' ## basic buffer
#' ddbs_buffer(conn = conn, "argentina", distance = 1)
#'
#' ## buffer with custom parameters
#' ddbs_buffer(conn = conn, "argentina", distance = 1,
#' num_triangles = 16, cap_style = "CAP_SQUARE")
#'
#' ## buffer without using a connection
#' ddbs_buffer(argentina_ddbs, distance = 1)
#' }
ddbs_buffer <- function(
x,
distance,
num_triangles = 8L,
cap_style = "CAP_ROUND",
join_style = "JOIN_ROUND",
mitre_limit = 1.0,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific errors
assert_numeric(distance, "distance")
assert_integer_scalar(num_triangles, "num_triangles")
assert_character_scalar(cap_style, "cap_style")
assert_character_scalar(join_style, "join_style")
assert_numeric(mitre_limit, "mitre_limit")
## Validate cap_style
valid_cap_styles <- c("CAP_ROUND", "CAP_FLAT", "CAP_SQUARE")
if (!toupper(cap_style) %in% valid_cap_styles) {
cli::cli_abort("{.arg cap_style} must be one of: {.val {paste0(valid_cap_styles, collapse = ', ')}}.")
}
## Validate join_style
valid_join_styles <- c("JOIN_ROUND", "JOIN_MITRE", "JOIN_BEVEL")
if (!toupper(join_style) %in% valid_join_styles) {
cli::cli_abort("{.arg join_style} must be one of: {.val {paste0(valid_join_styles, collapse = ', ')}}.")
}
## Check num_triangles is positive
if (num_triangles < 1) cli::cli_abort("{.arg num_triangles} must be a positive integer")
## Check mitre_limit is striclty positive
if (mitre_limit <= 0) cli::cli_abort("{.arg mitre_limit} must be a positive number")
# 1. Build ST_Buffer parameters string
buffer_args <- glue::glue("{distance}, {as.integer(num_triangles)}, '{toupper(cap_style)}', '{toupper(join_style)}', {mitre_limit}")
# 2. Pass to template
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_Buffer",
other_args = buffer_args
)
}
#' Calculates the centroid of geometries
#'
#' Returns the geometric center (centroid) of a geometry as a point,
#' representing its average position.
#'
#' @template x
#' @param method Character string specifying the method to calculate the
#' centroid. Must be one of "centroid" (default) or "surface". "centroid"
#' calculates the default centroid, which may fall outside the geometry
#' for certain shapes (e.g., donuts). "surface" calculates a point guaranteed
#' to fall within the geometry.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, argentina_ddbs, "argentina")
#'
#' ## centroid
#' ddbs_centroid("argentina", conn)
#'
#' ## centroid without using a connection
#' ddbs_centroid(argentina_ddbs)
#' }
ddbs_centroid <- function(
x,
method = "centroid",
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
st_fun <- switch(
method,
centroid = "ST_Centroid",
surface = "ST_PointOnSurface",
cli::cli_abort("Invalid method. Must be one of 'centroid', or 'surface'.")
)
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = st_fun,
other_args = NULL
)
}
#' Make invalid geometries valid
#'
#' Attempts to correct invalid geometries so they conform to the rules of well-formed
#' geometries (e.g., fixing self-intersections or improper rings) and returns the
#' corrected geometries.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, countries_ddbs, "countries")
#'
#' ## make valid
#' ddbs_make_valid("countries", conn)
#'
#' ## make valid without using a connection
#' ddbs_make_valid(countries_ddbs)
#' }
ddbs_make_valid <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_MakeValid",
other_args = NULL
)
}
#' Simplify geometries
#'
#' Reduces the complexity of geometries by removing unnecessary vertices while preserving
#' the overall shape.
#'
#' @template x
#' @param tolerance Tolerance distance for simplification. Larger values result in more
#' simplified geometries.
#' @param preserve_topology If FALSE, uses the Douglas-Peucker algorithm, which reduces
#' the vertices by removing points that are within a given distance. If TRUE, uses a
#' topology-preserving variant of Douglas-Peucker that guarantees the output geometry
#' remains valid (slower).
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, countries_ddbs, "countries")
#'
#' ## simplify with tolerance of 0.01
#' ddbs_simplify("countries", tolerance = 0.01, conn = conn)
#'
#' ## simplify without using a connection
#' ddbs_simplify(countries_ddbs, tolerance = 0.01)
#' }
ddbs_simplify <- function(
x,
tolerance = 0,
preserve_topology = FALSE,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific errors
assert_positive_numeric(tolerance, "tolerance")
assert_logic(preserve_topology, "preserve_topology")
# 1. Build ST_Simplify parameters string
## 1.1. Choose the right function
st_simplify_fun <-
if (isTRUE(preserve_topology)) "ST_SimplifyPreserveTopology" else "ST_Simplify"
## 1.2. Build tolerance parameter
simplify_args <- tolerance
# 2. Pass to template
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = st_simplify_fun,
other_args = simplify_args
)
}
#' Extract the exterior ring of polygons
#'
#' Returns the outer boundary (exterior ring) of polygon geometries. For multi-polygons,
#' returns the exterior ring of each individual polygon.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, countries_ddbs, "countries")
#'
#' ## extract exterior ring
#' ddbs_exterior_ring(conn = conn, "countries")
#'
#' ## extract exterior ring without using a connection
#' ddbs_exterior_ring(countries_ddbs)
#' }
ddbs_exterior_ring <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific error
assert_geom_type(x = x, conn = conn, geom = "POLYGON", multi = FALSE)
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_ExteriorRing",
other_args = NULL
)
}
#' Compute the concave hull of geometries
#'
#' Returns the concave hull that tightly encloses the geometry, capturing its overall
#' shape more closely than a convex hull.
#'
#' @template x
#' @param ratio Numeric. The ratio parameter dictates the level of concavity; `1`
#' returns the convex hull, while `0` indicates to return the most concave
#' hull possible. Defaults to `0.5`.
#' @param allow_holes Boolean. If `TRUE` (the default), it allows the output to
#' contain holes.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#' library(sf)
#'
#' # create points data
#' n <- 5
#' points_ddbs <- data.frame(
#' id = 1,
#' x = runif(n, min = -180, max = 180),
#' y = runif(n, min = -90, max = 90)
#' ) |>
#' ddbs_as_spatial(coords = c("x", "y")) |>
#' ddbs_combine()
#'
#' # option 1: passing ddbs or sf objects
#' output1 <- duckspatial::ddbs_concave_hull(points_ddbs, mode = "sf")
#'
#' plot(output1)
#'
#'
#' # option 2: passing the name of a table in a duckdb db
#'
#' # creates a duckdb
#' conn <- duckspatial::ddbs_create_conn()
#'
#' # write sf to duckdb
#' ddbs_write_vector(conn, points_ddbs, "points_tbl")
#'
#' # spatial join
#' output2 <- duckspatial::ddbs_concave_hull(
#' conn = conn,
#' x = "points_tbl",
#' mode = "sf"
#' )
#'
#' plot(output2)
#'
#' }
ddbs_concave_hull <- function(
x,
ratio = 0.5,
allow_holes = TRUE,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific errors
assert_numeric_interval(ratio, 0, 1, "ratio")
assert_logic(allow_holes, "allow_holes")
# 1. Build ST_ConcaveHull parameters string
concavehull_args <- glue::glue("{ratio}, {toupper(allow_holes)}")
# 2. Pass to template
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_ConcaveHull",
other_args = concavehull_args
)
}
#' Compute the convex hull of geometries
#'
#' Returns the convex hull that encloses the geometry, forming the smallest convex
#' polygon that contains all points of the geometry.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#' library(sf)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' # read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' # option 1: passing sf objects
#' output1 <- duckspatial::ddbs_convex_hull(x = argentina_ddbs, mode = "sf")
#'
#' plot(output1["CNTR_NAME"])#' # store in duckdb
#'
#' # option 2: passing the name of a table in a duckdb db
#'
#' # creates a duckdb
#' conn <- duckspatial::ddbs_create_conn()
#'
#' # write sf to duckdb
#' ddbs_write_vector(conn, argentina_ddbs, "argentina_tbl")
#'
#' # spatial join
#' output2 <- duckspatial::ddbs_convex_hull(
#' conn = conn,
#' x = "argentina_tbl",
#' mode = "sf"
#' )
#'
#' plot(output2["CNTR_NAME"])
#' }
ddbs_convex_hull <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_ConvexHull",
other_args = NULL
)
}
#' Get the geometry type of features
#'
#' Returns the type of each geometry (e.g., POINT, LINESTRING, POLYGON) in the
#' input features.
#'
#' @template x
#' @template by_feature
#' @template conn_null
#'
#' @returns A factor with geometry type(s)
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' # option 1: passing sf objects
#' # Get geometry type for each feature
#' ddbs_geometry_type(countries_ddbs)
#'
#' # Get overall geometry type
#' ddbs_geometry_type(countries_ddbs, by_feature = FALSE)
#' }
ddbs_geometry_type <- function(
x,
by_feature = TRUE,
conn = NULL) {
# 0. Validate inputs
assert_xy(x, "x")
assert_conn_character(conn, x)
assert_logic(by_feature, "by_feature")
# 1. Prepare inputs
## 1.1. Normalize inputs (coerce tbl_duckdb_connection to duckspatial_df,
## validate character table names)
x <- normalize_spatial_input(x, conn)
## 1.2. Pre-extract attributes
crs_x <- ddbs_crs(x, conn)
sf_col_x <- attr(x, "sf_column")
## 1.3. Resolve spatial connections and handle imports
resolve_conn <- resolve_spatial_connections(x, y = NULL, conn = conn, quiet = TRUE)
target_conn <- resolve_conn$conn
x <- resolve_conn$x
## register cleanup of the connection
on.exit(resolve_conn$cleanup(), add = TRUE)
## 1.4. Get list with query names for the input data
x_list <- get_query_list(x, target_conn)
on.exit(x_list$cleanup(), add = TRUE)
# 2. Prepare the query
## 2.1. Get the geometry column name (try to extract from attributes, if not
## available get it from the database)
x_geom <- sf_col_x %||% get_geom_name(target_conn, x_list$query_name)
assert_geometry_column(x_geom, x_list)
## 2.3. Create the query
if (isTRUE(by_feature)) {
tmp.query <- glue::glue("
SELECT ST_GeometryType({x_geom}) as geometry
FROM {x_list$query_name};
")
} else {
tmp.query <- glue::glue("
SELECT DISTINCT ST_GeometryType({x_geom}) as geometry
FROM {x_list$query_name};
")
}
## 2.4 Return a factor vector
data_tbl <- DBI::dbGetQuery(target_conn, tmp.query)
return(data_tbl$geometry)
}
#' Computes a Voronoi diagram from point geometries
#'
#' Returns a Voronoi diagram (Thiessen polygons) from a collection of points.
#' Each polygon represents the region closer to one point than to any other
#' point in the set. This function only works with MULTIPOINT geometries.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## create some points, and combine them to MULTIPOINT
#' set.seed(42)
#' n <- 1000
#' points_ddbs <- data.frame(
#' id = 1:n,
#' x = runif(n, min = -20, max = 20),
#' y = runif(n, min = -20, max = 02)
#' ) |>
#' ddbs_as_spatial(coords = c("x", "y"), crs = 4326) |>
#' ddbs_combine()
#'
#' ## create voronoi diagrama
#' ddbs_voronoi(points_ddbs)
#' }
ddbs_voronoi <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific errors
assert_geom_type(x, conn, geom = "MULTIPOINT")
# 1. Run the template
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_VoronoiDiagram",
other_args = NULL
)
}
#' Flips the X and Y coordinates of geometries
#'
#' Returns a geometry with the X and Y coordinates swapped. This is useful
#' for correcting geometries where longitude and latitude are in the wrong
#' order, or for converting between coordinate systems with different axis
#' orders.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' # create a duckdb database in memory (with spatial extension)
#' conn <- ddbs_create_conn(dbdir = "memory")
#'
#' ## read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' ## store in duckdb
#' ddbs_write_vector(conn, argentina_ddbs, "argentina")
#'
#' ## flip coordinates
#' ddbs_flip_coordinates("argentina", conn)
#'
#' ## flip coordinates without using a connection
#' ddbs_flip_coordinates(argentina_ddbs)
#' }
ddbs_flip_coordinates <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_FlipCoordinates",
other_args = NULL
)
}
#' Drop geometry column from a duckspatial_df object
#'
#' Removes the geometry column from a \code{duckspatial_df} object, returning a
#' lazy tibble without spatial information.
#'
#' @template x
#'
#' @return A lazy tibble backed by dbplyr
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' ## drop geometry column
#' countries_tbl <- ddbs_drop_geometry(countries_ddbs)
#' }
ddbs_drop_geometry <- function(x) {
## Get geometry column name
geometry_col <- attr(x, "sf_column")
## Drop duckspatial_df class
class(x) <- setdiff(class(x), c("duckspatial_df"))
## Unselect geometry column
dplyr::select(x, -dplyr::all_of(geometry_col))
}
#' Convert geometries to multi-type
#'
#' Converts single geometries to their multi-type equivalent (e.g.,
#' \code{POLYGON} to \code{MULTIPOLYGON}). Geometries that are already multi-type are
#' returned unchanged.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' countries_ddbs <- ddbs_open_dataset(
#' system.file("spatial/countries.geojson",
#' package = "duckspatial")
#' )
#'
#' ## convert to multi-type
#' ddbs_multi(countries_ddbs)
#' }
ddbs_multi <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_Multi",
other_args = NULL
)
}
#' Computes the maximum inscribed circle of a geometry
#'
#' Returns the largest circle that fits inside the input geometry. The result
#' is derived from a struct containing the circle's center point, the nearest
#' point on the geometry boundary to that center, and the circle's radius.
#'
#' @template x
#' @param geom a character string specifying which component of the inscribed
#' circle to return. Must be one of \code{"center"} (default) or
#' \code{"nearest"}. \code{"center"} returns the center point of the maximum
#' inscribed circle; \code{"nearest"} returns the closest point on the
#' geometry boundary to that center.
#' @param tolerance a numeric value specifying the tolerance used when
#' computing the inscribed circle. If \code{NULL} (default), tolerance is
#' computed automatically as \code{max(width, height) / 1000}.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' ## return the center point of the maximum inscribed circle
#' ddbs_maximum_inscribed_circle(argentina_ddbs)
#'
#' ## return the nearest boundary point instead
#' ddbs_maximum_inscribed_circle(argentina_ddbs, geom = "nearest")
#'
#' ## use a custom tolerance
#' ddbs_maximum_inscribed_circle(argentina_ddbs, tolerance = 0.01)
#'
#' ## without a connection
#' ddbs_maximum_inscribed_circle(argentina_ddbs)
#' }
ddbs_maximum_inscribed_circle <- function(
x,
geom = "center",
tolerance = NULL,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Validate inputs
assert_xy(x, "x")
assert_conn_x_name(conn, x, name)
assert_conn_character(conn, x)
assert_name(name)
assert_name(mode, "mode")
assert_logic(overwrite, "overwrite")
assert_logic(quiet, "quiet")
# 1. Prepare inputs
## 1.1. Normalize inputs (coerce tbl_duckdb_connection to duckspatial_df,
## validate character table names)
x <- normalize_spatial_input(x, conn)
## 1.2. Pre-extract attributes
crs_x <- ddbs_crs(x, conn)
sf_col_x <- attr(x, "sf_column")
mode <- get_mode(mode, name)
## 1.3. Resolve spatial connections and handle imports
resolve_conn <- resolve_spatial_connections(x, y = NULL, conn = conn, quiet = quiet)
target_conn <- resolve_conn$conn
x <- resolve_conn$x
## register cleanup of the connection
on.exit(resolve_conn$cleanup(), add = TRUE)
## 1.4. Get list with query names for the input data
x_list <- get_query_list(x, target_conn)
on.exit(x_list$cleanup(), add = TRUE)
# 2. Prepare the query
## 2.1. Get the geometry column name (try to extract from attributes, if not
## available get it from the database)
x_geom <- sf_col_x %||% get_geom_name(target_conn, x_list$query_name)
assert_geometry_column(x_geom, x_list)
## 2.2. Build the base query (depends on the output type - sf, duckspatial_df, table)
### Add tolerance parameter if provided
if (is.null(tolerance)) {
st_function <- glue::glue("ST_MaximumInscribedCircle({x_geom})")
} else {
st_function <- glue::glue("ST_MaximumInscribedCircle({x_geom}, {tolerance})")
}
### This function returns a data frame column. We select the radius, and the one of
### the two geometry columns (nearest or center)
if (geom == "nearest") {
nearest_function <- glue::glue("{st_function}.nearest")
geom_function <- glue::glue("
{build_geom_query(nearest_function, name, crs_x, mode)} AS {x_geom}
")
} else if (geom == "center") {
center_function <- glue::glue("{st_function}.center")
geom_function <- glue::glue("
{build_geom_query(center_function, name, crs_x, mode)} AS {x_geom}
")
}
### Finally, build the query
base.query <- glue::glue("
SELECT * EXCLUDE ({x_geom}),
{st_function}.radius AS geom_radius,
{geom_function}
FROM {x_list$query_name};
")
# 3. Table creation if name is provided, or
# create duckspatial_df or sf object if name is NULL
if (!is.null(name)) {
create_duckdb_table(
conn = target_conn,
name = name,
query = base.query,
overwrite = overwrite,
quiet = quiet
)
} else {
ddbs_handle_query(
query = base.query,
conn = target_conn,
mode = mode,
crs = crs_x,
x_geom = x_geom
)
}
}
#' Remove repeated points from a geometry
#'
#' Removes duplicate consecutive vertices from geometries, optionally within
#' a tolerance distance.
#'
#' @template x
#' @param tolerance A numeric value specifying the minimum distance between
#' consecutive vertices. Vertices closer than this threshold are considered
#' repeated and removed. Default is \code{0}, which removes only exact
#' duplicates.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#' library(sf)
#'
#' ## Create a polygon with repeated points
#' poly <- st_polygon(list(matrix(
#' c(0, 0,
#' 1, 0,
#' 1, 0, # repeated point
#' 1, 1,
#' 0, 1,
#' 0, 0),
#' ncol = 2, byrow = TRUE
#' )))
#'
#' poly_sf <- st_as_sf(st_sfc(poly))
#'
#' ## remove exact duplicate consecutive vertices
#' ddbs_remove_repeated_points(poly_sf)
#'
#' ## remove vertices within a tolerance of 1 unit
#' ddbs_remove_repeated_points(poly_sf, tolerance = 1)
#' }
ddbs_remove_repeated_points <- function(
x,
tolerance = 0,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Handle function-specific errors
assert_positive_numeric(tolerance, "tolerance")
# 2. Pass to template
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_RemoveRepeatedPoints",
other_args = tolerance
)
}
#' Create lines from point geometries
#'
#' Aggregates point geometries into a single LINESTRING by connecting them in
#' their original order. Optionally, lines can be created per group using the
#' \code{by} argument.
#'
#' @template x
#' @param by A character vector of column names to group by before aggregating
#' points into lines. If \code{NULL} (default), all points are combined into a
#' single line.
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @details
#' Connects input POINT geometries into a LINESTRING in row order (row 1 → row 2 → …).
#' To control the connection order, sort the data beforehand with [dplyr::arrange()].
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' points_ddbs <- ddbs_open_dataset(
#' system.file("spatial/points.gpkg", package = "duckspatial")
#' )
#'
#' ## create a single line from all points
#' ddbs_make_line(points_ddbs)
#'
#' ## create lines grouped by a column
#' ddbs_make_line(points_ddbs, by = "type")
#'
#' ## return as sf object
#' ddbs_make_line(points_ddbs, by = "type", mode = "sf")
#'
#' ## screate lines groupping by 2 columns
#' ddbs_make_line(points_ddbs, by = c("type", "class"))
#' }
ddbs_make_line <- function(
x,
by = NULL,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
# 0. Validate inputs
assert_xy(x, "x")
assert_conn_x_name(conn, x, name)
assert_conn_character(conn, x)
assert_name(name)
assert_name(mode, "mode")
assert_logic(overwrite, "overwrite")
assert_logic(quiet, "quiet")
assert_geom_type(x = x, conn = conn, geom = "POINT", multi = FALSE)
# 1. Prepare inputs
## 1.1. Normalize inputs (coerce tbl_duckdb_connection to duckspatial_df,
## validate character table names)
x <- normalize_spatial_input(x, conn)
## 1.2. Pre-extract attributes
crs_x <- ddbs_crs(x, conn)
sf_col_x <- attr(x, "sf_column")
mode <- get_mode(mode, name)
## 1.3. Resolve spatial connections and handle imports
resolve_conn <- resolve_spatial_connections(x, y = NULL, conn = conn, quiet = quiet)
target_conn <- resolve_conn$conn
x <- resolve_conn$x
## register cleanup of the connection
on.exit(resolve_conn$cleanup(), add = TRUE)
## 1.4. Get list with query names for the input data
x_list <- get_query_list(x, target_conn)
on.exit(x_list$cleanup(), add = TRUE)
# 2. Prepare the query
## 2.1. Get the geometry column name (try to extract from attributes, if not
## available get it from the database)
x_geom <- sf_col_x %||% get_geom_name(target_conn, x_list$query_name)
assert_geometry_column(x_geom, x_list)
## 2.2. Build the base query (depends on the output type - sf, duckspatial_df, table)
st_function <- glue::glue("ST_MakeLine(LIST({x_geom}))")
## Add groups if specified
if (!is.null(by)) {
grps <- paste0(by, collapse = ", ")
base.query <- glue::glue("
SELECT
{grps},
{build_geom_query(st_function, name, crs_x, mode)} AS {x_geom}
FROM {x_list$query_name}
GROUP BY {grps};
")
} else {
base.query <- glue::glue("
SELECT {build_geom_query(st_function, name, crs_x, mode)} AS {x_geom}
FROM {x_list$query_name};
")
}
# 3. Table creation if name is provided, or
# create duckspatial_df or sf object if name is NULL
if (!is.null(name)) {
create_duckdb_table(
conn = target_conn,
name = name,
query = base.query,
overwrite = overwrite,
quiet = quiet
)
} else {
ddbs_handle_query(
query = base.query,
conn = target_conn,
mode = mode,
crs = crs_x,
x_geom = x_geom
)
}
}
#' Collect all vertices of a geometry into a MULTIPOINT
#'
#' Returns a MULTIPOINT containing all vertices of the input geometry. This
#' works for any geometry type: points, linestrings, polygons, and their
#' multi- and collection variants.
#'
#' @template x
#' @template conn_null
#' @template name
#' @template mode
#' @template overwrite
#' @template quiet
#'
#' @template returns_mode
#' @export
#'
#' @examples
#' \dontrun{
#' ## load package
#' library(duckspatial)
#'
#' ## read data
#' argentina_ddbs <- ddbs_open_dataset(
#' system.file("spatial/argentina.geojson",
#' package = "duckspatial")
#' )
#'
#' ## collect vertices
#' ddbs_vertices(argentina_ddbs)
#'
#' ## collect vertices and return as sf
#' ddbs_vertices(argentina_ddbs, mode = "sf")
#' }
ddbs_vertices <- function(
x,
conn = NULL,
name = NULL,
mode = NULL,
overwrite = FALSE,
quiet = FALSE) {
template_unary_ops(
x = x,
conn = conn,
name = name,
mode = mode,
overwrite = overwrite,
quiet = quiet,
fun = "ST_Points",
other_args = NULL
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.