R Interface to 'DuckDB' Database with Spatial Extension
Overview
Fast and memory-efficient functions to analyze and manipulate large spatial datasets. It leverages the fast analytical capabilities of ‘DuckDB’ and its spatial extension (see https://duckdb.org/docs/stable/core_extensions/spatial/overview) while maintaining compatibility with R’s spatial data ecosystem to work with spatial vector data.
NEW FEATURES
- Updated the
duckdbdependency to v1.5.6. See <https://r.duckdb.org/news/index.html#duckdb-156>. ddbs_expand(): expand the bounding box of geometries.ddbs_default_conn(): creates or returns the default duckdb connection used internally by the package (#154).
PERFORMANCE
- Less fixed overhead on every call with sf input:
ddbs_register_table()no longer runs a call toregister_geoarrow_extensions(), which does not exist in duckdb >= 1.5 and always failed (~45 ms per sf input), and building the DuckDB CRS literal skips a slowsf::st_crs(parameters = TRUE)lookup when the CRS has an EPSG code (~10 ms -> ~4 ms per call).
BUG FIXES
- Functions called with sf or data.frame input no longer leak the registered data: each call left one Arrow registration (or a registered data.frame) and its memory in the connection until the session ended.
ddbs_write_table()is much faster: for sf input about 5x (1 million points: 6.3 s → 1.4 s, half the memory), and for aduckspatial_dfor lazy table from the same connection about 100x (1 million rows: 5.4 s → 0.05 s), because the data no longer makes a round trip through R.ddbs_write_dataset()with sf input is about 2x faster.ddbs_write_table()andddbs_write_dataset()can now write sf objects that mix XYZ, XY and EMPTY geometries (previously "Skipping beyond end of binary data").ddbs_write_table()now accepts a schema-qualified name as a single string (e.g."s1.t"), and a failed write no longer leaves a partial table behind.ddbs_filter(), the predicate functions withmode = "sf"(ddbs_intersects(),ddbs_within(), …) and the dense (sparse = FALSE) predicates in duckspatial mode now put the spatial predicate in a join condition, so DuckDB uses its spatial join instead of a cross product over every pair of features. On 100,000 points and the 100nccounties,ddbs_filter()goes from about 14 s to about 1 s,ddbs_intersects(mode = "sf")from about 15 s to about 1 s and the dense duckspatial predicate from about 29 s to about 2 s. The sparsemode = "sf"result is built from the matching pairs only, so it no longer allocates an n × m matrix in R. Two consequences:ddbs_disjoint()cannot use the spatial join (nearly every pair matches) and is somewhat slower on large inputs; and in the densemode = "sf"matrix, rows whose geometry is SQLNULLare nowFALSEinstead ofNA.
Show all changes in 1.3.0Hide the rest of the changes in 1.3.0
- Registering sf data in DuckDB is 3–4x faster and uses ~4x less memory (e.g.
as_duckspatial_df()on 1 million points: 3.4 s → 0.8 s, 67 MB → 17 MB). This speeds up everyddbs_*()call with sf input.ddbs_register_table()no longer builds a chunk index for data that fits in a single Arrow chunk. - Every
ddbs_*()call that uses the default connection is faster by about 50–100 ms:ddbs_default_conn()no longer re-creates the package's 35 SQL macros on each call. The macros are created once, when the connection is created. If you set the internalduckspatial_connoption to your own connection, runddbs_load(conn)to create the macros on it. as_duckspatial_df(),ddbs_register_table()and other functions that take an sf object no longer fail with "subscript out of bounds" on a 0-row sf. The result keeps the columns and the CRS.- sf data larger than ~500 MB (registered in several Arrow chunks) could only be read once: a second query or
collect()on the same object returned 0 rows. It can now be read any number of times. - Fix
nanoarrow::as_nanoarrow_array_stream(..., native = TRUE)to convert WKB geometry columns to native GeoArrow layouts such asgeoarrow.point. Since the method was introduced, it had incorrectly returnedgeoarrow.wkbunchanged because its target schema was inferred from the existing WKB Arrow column (#121). ddbs_filter()no longer drops rows ofxthat are exact duplicates of another row, and now returns the matching rows in their original order (#156).ddbs_distance(),ddbs_azimuth()and the predicate functions (ddbs_predicate(),ddbs_intersects(), etc.) withmode = "sf"could return matrices with values in the wrong cells, because the cross join results were reshaped without an explicit row order (#155).ddbs_*functions andprint()now respect dplyr verbs that have noduckspatial_dfmethod (e.g.distinct(),semi_join(),anti_join(),union_all()). Previously they silently used the original, unmodified table (#159).ddbs_area(),ddbs_length(),ddbs_perimeter(),ddbs_distance()and thedwithinpredicate (ddbs_is_within_distance(),ddbs_join(),ddbs_filter()) now compute planar results in the CRS's own units for projected CRSs not in metres (e.g. US survey feet), labelled as insf; previously they returnedNaN, mislabelled feet as metres, or found nodwithinmatches. These functions andddbs_buffer()now give an informative error when the input has no CRS, and WGS84 written as"OGC:CRS84"or"WGS 84"no longer triggers an accuracy warning (#161).ddbs_read_meta()now gives an informative error for files GDAL can't open (e.g. GeoParquet) or that don't exist, instead of silently returning an empty tibble.ddbs_write_table()on a.parquetpath no longer emits a spurious "Could not auto-detect CRS from file" warning before its error (#166).ddbs_quadkey()no longer overwrites the input table whenxis not in EPSG:4326, and now returns the correct quadkeys for such input. Previously, a table passed by name was replaced with lat/lon-swapped coordinates and no CRS, and all inputs returned wrong tiles (#164).ddbs_contains()andddbs_crosses()are now exported. They were documented but missing from the package namespace.group_by()groups on aduckspatial_dfare no longer lost aftermutate(),filter(),arrange(),select()and other verbs. Previously a followingsummarise()silently collapsed all groups into a single row (#170).- Column, table and schema names that contain spaces or are SQL reserved words (e.g. a geometry column
"my geom",by = "group",name = "my table", or a table named"order"passed asx) are now quoted in the generated SQL. Previously they failed with a parser error (#168).
From NEWS.md in the release
Install
Health
ERRORre-building of vignette outputsr-devel-linux-x86_64-fedora-clang
Error(s) in re-building vignettes:
--- re-building ‘aw_interpolation.qmd’ using html
[31m
processing file: aw_interpolation.qmd
[39m[31mError in `value[[3L]]()`:
! Failed to load the spatial extension.
✖ DuckDB extension loading (INSTALL / LOAD) is disabled for this driver. ℹ
Recreate the driver with duckdb(allow_extensions = TRUE) to enable it (this
may crash R on a Linux build not compiled with libstdc++; see
https://github.com/duckdb/duckdb-r/issues/1107). ℹ See ?duckdb for details. ℹ
Context: load_extension
ℹ The binary may be corrupt or incompatible with this platform/DuckDB version.
ℹ Try reinstalling with `ddbs_install(extension = 'spatial', upgrade = TRUE)`
Backtrace:
▆
1. └─duckspatial::ddbs_interpolate_aw(...)
2. └─duckspatial:::resolve_spatial_connections(...)
3. └─duckspatial::ddbs_default_conn()
4. └─duckspatial::ddbs_create_conn(dbdir = "memory", ...)
5. └─duckspatial::ddbs_load(conn, quiet = TRUE)
6. └─base::tryC
...[truncated]...
install(extension = 'spatial', upgrade = TRUE)`
---
Backtrace:
▆
1. └─duckspatial::ddbs_join(x = points_sf, y = countries_sf, join = "within")
2. └─duckspatial:::resolve_spatial_connections(...)
3. └─duckspatial::ddbs_default_conn()
4. └─duckspatial::ddbs_create_conn(dbdir = "memory", ...)
5. └─duckspatial::ddbs_load(conn, quiet = TRUE)
6. └─base::tryCatch(...)
7. └─base (local) tryCatchList(expr, classes, parentenv, handlers)
8. └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
9. └─value[[3L]](cond)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Execution halted
[33mWARN: Error encountered when rendering files[39m
Caused by error in `processx::run()`:
! System command 'quarto' failed
--- failed re-building ‘spatial_joins.qmd’
SUMMARY: processing the following files failed:
‘aw_interpolation.qmd’ ‘duckspatial.qmd’ ‘spatial_joins.qmd’
Error: Vignette re-building failed.
Execution haltedERRORtestsr-devel-linux-x86_64-fedora-clang
Running ‘testthat.R’
Running the tests in ‘tests/testthat.R’ failed.
Complete output:
> # This file is part of the standard setup for testthat.
> # It is recommended that you do not modify it.
> #
> # Where should you do additional test configuration?
> # Learn more about the roles of various files in:
> # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
> # * https://testthat.r-lib.org/articles/special-files.html
>
> library(testthat)
> library(duckspatial)
duckspatial 1.3.0 attached
* Compatible with DuckDB >= v1.5.6
Default output has changed on v1.0.0:
duckspatial now returns lazy `duckspatial_df` (dbplyr) objects
instead of `sf` objects.
To restore the previous behaviour:
ddbs_options(mode = 'sf')
> library(duckdb)
Loading required package: DBI
>
> test_check("duckspatial")
Reading layer `countries' from data source
`/data/localhost/ripley/R/packages/tests-clang/duckspatial.Rcheck/duckspatial/spatial/c
...[truncated]...
└─base::eval(exprs, env)
13. └─base::eval(exprs, env)
14. ├─duckspatial::as_duckspatial_df(argentina_sf) at ./setup.R:15:1
15. └─duckspatial:::as_duckspatial_df.sf(argentina_sf)
16. └─duckspatial::ddbs_default_conn()
17. └─duckspatial::ddbs_create_conn(dbdir = "memory", ...)
18. └─duckspatial::ddbs_load(conn, quiet = TRUE)
19. └─base::tryCatch(...)
20. └─base (local) tryCatchList(expr, classes, parentenv, handlers)
21. └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
22. └─value[[3L]](cond)
23. └─cli::cli_abort(...)
24. └─rlang::abort(...)
Execution halted- ERROR12 OK, 1 ERRORNow
- ERROR13 OK, 1 ERROR2026-03-10
Documentation
- Help pages
- 119 (25 internal)includes a package overview page
- Examples that run
- 1%1 run in a plain check · 1 only when donttest examples are enabled (R CMD check --as-cran) · 86 never run
- Exported functions with examples
- 89%
- Documented parameters
- 100%
- Return-value docs
- 100%
- Help pages citing references
- 3%
- Getting started HTML
- Areal Interpolation HTML
- Benchmark HTML
- Spatial joins HTML
Downloads
Repository
- PRs merged (all time)
- 94%
- Median days to close, 50 most recently updated closed issues
- 12
- Median days to close, 50 most recently updated closed PRs
- 2
Stars over time
Forks over time
Releases over time
Issues over time
PRs over time
Commits over time
Commit identities over time
12 development-tooling and community-health practices detected across 6 families in the upstream repository
Checks run against github.com/cidree/duckspatial on 2026-10-11.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
GitHub Pages site last deployed 2026-10-11 · The repository has a site deploy workflow
pkgdown site last built 2026-10-11
Show all practicesHide 2 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-06-04Seen in the repository: .claudeNamed in .Rbuildignore: .claudePull request it wrote: opened by a project memberCommits: at least 35 crediting it in the message
- Claude Sonnet 4.616 commits · Jun 2026
- Claude Opus 4.811 commits · Jun 2026
- Claude Opus 5.58 commits · Oct 2026
Dependencies
Installing duckspatial adds 40 packages (28 with compiled code, 46 MB of source tarballs). Heaviest: stringi 11 MB, sf 9.3 MB, duckdb 4.6 MB.
Code & Tests
People & History
Pick two releases to compare their code metrics.
- 1.3.0Latest2026-07-04 · current release · diff ↗ ·
added 4 exports
ddbs_contains, ddbs_crosses, ddbs_default_conn, ddbs_expand - 1.2.12026-07-04 · diff ↗
- 1.2.02026-06-22 · diff ↗ ·
added 14 exports
ddbs_as_mvt_geom, ddbs_extension_info, ddbs_geom_from_geojson, ddbs_geom_from_hexewkb, ddbs_geom_from_hexwkb, ddbs_geom_from_text, ddbs_geom_from_wkb, ddbs_get_ninterior_rings, ddbs_intersection_agg, ddbs_line_node, ddbs_normalize, ddbs_reduce_precision, ddbs_reverse, ddbs_write_mbtiles - 1.1.22026-06-22 · diff ↗ ·
added 14 exports
ddbs_azimuth, ddbs_dimension, ddbs_line_locate_point, ddbs_mmax, ddbs_mmin, ddbs_point, ddbs_shortest_line, ddbs_vertices, ddbs_xmax, ddbs_xmin, ddbs_ymax, ddbs_ymin, ddbs_zmax, ddbs_zmin - 1.1.1
- 1.1.02026-05-17 · diff ↗ ·
added 18 exports
ddbs_crop, ddbs_dump, ddbs_line_endpoint, ddbs_line_interpolate, ddbs_line_merge, ddbs_line_startpoint, ddbs_line_substring, ddbs_locate_along, ddbs_locate_between, ddbs_m, ddbs_make_envelope, ddbs_make_line, ddbs_maximum_inscribed_circle, ddbs_minimum_rotated_rectangle, ddbs_read_meta, ddbs_remove_repeated_points, ddbs_set_crs, ddbs_z - RR 4.6.0 released · 2026-04-24
- 1.0.02026-03-30 · diff ↗ ·
removed 2 exports
ddbs_contains, ddbs_crossesadded 38 exports
as_duckspatial_df, ddbs_as_geojson, ddbs_as_points, ddbs_build_area, ddbs_collect, ddbs_compute, ddbs_drop_geometry, ddbs_endpoint, ddbs_flip_coordinates, ddbs_force_2d, ddbs_force_3d, ddbs_force_4d, ddbs_geom_col, ddbs_geometry_type, ddbs_get_resources, ddbs_has_m, ddbs_has_z, ddbs_is_closed, ddbs_is_empty, ddbs_is_ring, ddbs_multi, ddbs_open_dataset, ddbs_options, ddbs_perimeter, ddbs_polygonize, ddbs_read_table, ddbs_register_table, ddbs_set_resources, ddbs_sitrep, ddbs_startpoint and 8 more - 0.9.02026-01-10 · diff ↗ ·
added 49 exports
ddbs_area, ddbs_as_hexwkb, ddbs_as_text, ddbs_as_wkb, ddbs_bbox, ddbs_boundary, ddbs_combine, ddbs_concave_hull, ddbs_contains, ddbs_contains_properly, ddbs_convex_hull, ddbs_covered_by, ddbs_covers, ddbs_create_conn, ddbs_crosses, ddbs_disjoint, ddbs_distance, ddbs_drivers, ddbs_envelope, ddbs_equals, ddbs_exterior_ring, ddbs_flip, ddbs_generate_points, ddbs_interpolate_aw, ddbs_intersects, ddbs_intersects_extent, ddbs_is_simple, ddbs_is_valid, ddbs_is_within_distance, ddbs_join and 19 more - 0.2.02025-04-29 · diff ↗ ·
added 5 exports
ddbs_buffer, ddbs_centroid, ddbs_difference, ddbs_glimpse, ddbs_list_tables - 0.1.02025-04-19
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2025-04-19
- Total releases
- 10 / 1 yrs
- License
- GPL (>= 3) OSI
- Minimum R
- ≥ 4.1.0
- Download size
- 4.7 MB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("duckspatial")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-18, which the citation names so these numbers can be found later. More on citing and the projects behind them.