Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ jobs:
- uses: taiki-e/install-action@cargo-hack
- name: Check msrv
run: cargo hack check --rust-version --workspace --all-targets --ignore-private
test-r:
name: Test R bindings
runs-on: ubuntu-latest
defaults:
run:
working-directory: crates/r
steps:
- uses: actions/checkout@v6
- uses: Swatinem/rust-cache@v2
- uses: r-lib/actions/setup-r@v2
- uses: r-lib/actions/setup-r-dependencies@v2
with:
working-directory: crates/r
- name: Build and check
run: |
R CMD build .
R CMD check --no-manual rustac_*.tar.gz
doc:
name: Docs
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/extensions",
"crates/io",
"crates/pgstac",
"crates/r/src/rust",
"crates/server",
"crates/validate",
"crates/wasm",
Expand Down
3 changes: 3 additions & 0 deletions crates/r/.Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
^src/rust/target$
^src/rust/.cargo$
^README\.md$
49 changes: 49 additions & 0 deletions crates/r/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# History files
.Rhistory
.Rapp.history

# Session Data files
.RData
.RDataTmp

# User-specific files
.Ruserdata

# Example code in package build process
*-Ex.R

# Output files from R CMD build
/*.tar.gz

# Output files from R CMD check
/*.Rcheck/

# RStudio files
.Rproj.user/

# produced vignettes
vignettes/*.html
vignettes/*.pdf

# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
.httr-oauth

# knitr and R markdown default cache directories
*_cache/
/cache/

# Temporary files created by R markdown
*.utf8.md
*.knit.md

# R Environment Variables
.Renviron

# pkgdown site
docs/

# translation temp files
po/*~

# RStudio Connect folder
rsconnect/
20 changes: 20 additions & 0 deletions crates/r/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Package: rustac
Title: SpatioTemporal Asset Catalog (STAC) Client
Version: 0.1.0
Authors@R:
person("Pete", "Gadomski", , "pete.gadomski@gmail.com", role = c("aut", "cre"))
Description: Read and write SpatioTemporal Asset Catalog (STAC) data in JSON,
NDJSON, and geoparquet formats. Powered by Rust via the 'extendr' framework.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Imports:
arrow,
jsonlite,
sf
Suggests:
geojsonsf,
testthat (>= 3.0.0)
Config/testthat/edition: 3
SystemRequirements: Cargo (Rust's package manager), rustc >= 1.88
23 changes: 23 additions & 0 deletions crates/r/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
4 changes: 4 additions & 0 deletions crates/r/NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
useDynLib(rustac, .registration = TRUE)

export(stac_read)
export(stac_write)
26 changes: 26 additions & 0 deletions crates/r/R/extendr-wrappers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by extendr: Do not edit by hand

# nolint start

#' @docType package
#' @usage NULL
#' @useDynLib rustac, .registration = TRUE
NULL

wrap__stac_read_json <- function(path) {
.Call("wrap__stac_read_json", path, PACKAGE = "rustac")
}

wrap__stac_write_json <- function(json, path) {
.Call("wrap__stac_write_json", json, path, PACKAGE = "rustac")
}

wrap__stac_read_geoparquet <- function(path) {
.Call("wrap__stac_read_geoparquet", path, PACKAGE = "rustac")
}

wrap__stac_write_geoparquet <- function(ipc_bytes, path) {
.Call("wrap__stac_write_geoparquet", ipc_bytes, path, PACKAGE = "rustac")
}

# nolint end
58 changes: 58 additions & 0 deletions crates/r/R/rustac.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#' Read a STAC value from a file or URL
#'
#' Reads STAC data from JSON, NDJSON, or geoparquet formats. The format is
#' inferred from the file extension. Geoparquet files are returned as sf data
#' frames via Arrow; all other types are returned as R lists.
#'
#' @param path Path to a file or a URL.
#' @return An sf data frame (for geoparquet/FeatureCollections) or an R list.
#' @export
stac_read <- function(path) {
if (is_geoparquet(path)) {
ipc_bytes <- wrap__stac_read_geoparquet(path)
table <- arrow::read_ipc_stream(ipc_bytes, as_data_frame = FALSE)
df <- as.data.frame(table)
geom <- sf::st_as_sfc(structure(df$geometry, class = "WKB"), EWKB = TRUE)
df$geometry <- NULL
sf::st_sf(df, geometry = geom)
} else {
json <- wrap__stac_read_json(path)
value <- jsonlite::fromJSON(json, simplifyVector = FALSE)
if (identical(value$type, "FeatureCollection")) {
sf::read_sf(json)
} else {
value
}
}
}

#' Write a STAC value to a file
#'
#' Writes STAC data to JSON, NDJSON, or geoparquet formats. The format is
#' inferred from the file extension. sf data frames are written via Arrow for
#' geoparquet output; all other values are serialized with jsonlite.
#'
#' @param x An sf data frame or an R list representing a STAC value.
#' @param path Output file path.
#' @export
stac_write <- function(x, path) {
if (is_geoparquet(path)) {
table <- arrow::as_arrow_table(sf::st_as_sf(x))
buf <- arrow::write_ipc_stream(table, raw())
invisible(wrap__stac_write_geoparquet(buf, path))
} else {
if (inherits(x, "sf")) {
if (!requireNamespace("geojsonsf", quietly = TRUE)) {
stop("Package 'geojsonsf' is required to write sf objects to non-parquet formats")
}
json <- geojsonsf::sf_geojson(x)
} else {
json <- jsonlite::toJSON(x, auto_unbox = TRUE, null = "null")
}
invisible(wrap__stac_write_json(as.character(json), path))
}
}

is_geoparquet <- function(path) {
grepl("\\.(parquet|geoparquet)$", path, ignore.case = TRUE)
}
3 changes: 3 additions & 0 deletions crates/r/R/zzz.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.onLoad <- function(libname, pkgname) {
library.dynam("rustac", pkgname, libname)
}
84 changes: 84 additions & 0 deletions crates/r/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# rustac

R package for reading and writing [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) data in JSON, NDJSON, and [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet) formats.
Powered by Rust via [extendr](https://extendr.github.io/extendr/extendr_api/).

## Prerequisites

- **Rust** toolchain (rustc >= 1.88): <https://rustup.rs/>

## Install

Install directly from GitHub using [pak](https://pak.r-lib.org/) (recommended):

```r
# install.packages("pak")
pak::pak("stac-utils/rustac/crates/r")
```

Or with `devtools`:

```r
# install.packages("devtools")
devtools::install_github("stac-utils/rustac", subdir = "crates/r")
```

### From source

From the repository root:

```bash
R CMD build crates/r
R CMD INSTALL rustac_0.1.0.tar.gz
```

### Troubleshooting

If `arrow` or `sf` fail to install, try [r-universe](https://r-universe.dev/) binaries first:

```r
install.packages(
c("arrow", "sf"),
repos = c("https://apache.r-universe.dev", "https://r-spatial.r-universe.dev", "https://cloud.r-project.org")
)
```

Then retry the install.

## Development

For iterative development, use `devtools`:

```r
devtools::load_all("crates/r")
devtools::test("crates/r")
```

To run the full R CMD check:

```bash
R CMD build crates/r
R CMD check rustac_0.1.0.tar.gz
```

## Usage

```r
library(rustac)

# Read a STAC item (returns an R list)
item <- stac_read("spec-examples/v1.1.0/simple-item.json")
item$id
#> [1] "20201211_223832_CS2"

# Read stac-geoparquet (returns an sf data frame)
sf <- stac_read("data/extended-item.parquet")
class(sf)
#> [1] "sf" "data.frame"

# Write to JSON
stac_write(item, "output.json")

# Write to stac-geoparquet
stac_write(sf, "output.parquet")
```
19 changes: 19 additions & 0 deletions crates/r/rustac/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Package: rustac
Title: SpatioTemporal Asset Catalog (STAC) Client
Version: 0.1.0
Authors@R:
person("Pete", "Gadomski", , "pete.gadomski@gmail.com", role = c("aut", "cre"))
Description: Read and write SpatioTemporal Asset Catalog (STAC) data in JSON,
NDJSON, and geoparquet formats. Powered by Rust via the 'extendr' framework.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Imports: arrow, jsonlite, sf
Suggests: geojsonsf, testthat (>= 3.0.0)
Config/testthat/edition: 3
SystemRequirements: Cargo (Rust's package manager), rustc >= 1.88
NeedsCompilation: yes
Packaged: 2026-02-27 15:17:25 UTC; gadomski
Author: Pete Gadomski [aut, cre]
Maintainer: Pete Gadomski <pete.gadomski@gmail.com>
23 changes: 23 additions & 0 deletions crates/r/rustac/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
4 changes: 4 additions & 0 deletions crates/r/rustac/NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
useDynLib(rustac, .registration = TRUE)

export(stac_read)
export(stac_write)
26 changes: 26 additions & 0 deletions crates/r/rustac/R/extendr-wrappers.R
Comment thread
gadomski marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by extendr: Do not edit by hand

# nolint start

#' @docType package
#' @usage NULL
#' @useDynLib rustac, .registration = TRUE
NULL

wrap__stac_read_json <- function(path) {
.Call("wrap__stac_read_json", path)
}

wrap__stac_write_json <- function(json, path) {
.Call("wrap__stac_write_json", json, path)
}

wrap__stac_read_geoparquet <- function(path) {
.Call("wrap__stac_read_geoparquet", path)
}

wrap__stac_write_geoparquet <- function(ipc_bytes, path) {
.Call("wrap__stac_write_geoparquet", ipc_bytes, path)
}

# nolint end
Loading
Loading