├── .python-version
├── CODEOWNERS
├── crates
├── wasm
│ ├── LICENSE-MIT
│ ├── LICENSE-APACHE
│ ├── www
│ │ ├── .gitignore
│ │ ├── bootstrap.js
│ │ ├── index.html
│ │ ├── webpack.config.js
│ │ ├── package.json
│ │ └── index.js
│ ├── Cargo.toml
│ ├── src
│ │ └── lib.rs
│ ├── CHANGELOG.md
│ └── README.md
├── cli
│ ├── examples
│ ├── src
│ │ └── main.rs
│ ├── Cargo.toml
│ ├── data
│ │ └── invalid-item.json
│ └── README.md
├── core
│ ├── examples
│ ├── assets
│ │ ├── dataset.tif
│ │ └── dataset_geo.tif
│ ├── data
│ │ ├── extended-item.parquet
│ │ ├── bands-v1.1.0.json
│ │ ├── items.ndjson
│ │ ├── bands-v1.0.0.json
│ │ ├── invalid-item.json
│ │ └── 20201211_223832_CS2.json
│ ├── src
│ │ ├── geo.rs
│ │ ├── statistics.rs
│ │ ├── api
│ │ │ ├── collections.rs
│ │ │ ├── mod.rs
│ │ │ ├── sort.rs
│ │ │ └── root.rs
│ │ ├── data_type.rs
│ │ ├── version.rs
│ │ ├── json.rs
│ │ ├── datetime.rs
│ │ ├── band.rs
│ │ └── item_asset.rs
│ ├── README.md
│ └── Cargo.toml
├── io
│ ├── examples
│ ├── data
│ │ ├── extended-item.parquet
│ │ └── items.ndjson
│ ├── mocks
│ │ └── not-a-collection.json
│ ├── tests
│ │ └── aws.rs
│ ├── src
│ │ ├── read.rs
│ │ ├── write.rs
│ │ ├── realized_href.rs
│ │ ├── json.rs
│ │ ├── error.rs
│ │ └── geoparquet.rs
│ ├── README.md
│ ├── Cargo.toml
│ └── CHANGELOG.md
├── extensions
│ ├── examples
│ ├── Cargo.toml
│ ├── README.md
│ ├── CHANGELOG.md
│ ├── data
│ │ └── auth
│ │ │ ├── item.json
│ │ │ └── collection.json
│ └── src
│ │ ├── projection.rs
│ │ └── electro_optical.rs
├── validate
│ ├── examples
│ ├── src
│ │ ├── schemas
│ │ │ ├── v1.0.0
│ │ │ │ ├── licensing.json
│ │ │ │ ├── basics.json
│ │ │ │ ├── instrument.json
│ │ │ │ ├── provider.json
│ │ │ │ ├── datetime.json
│ │ │ │ └── catalog.json
│ │ │ └── v1.1.0
│ │ │ │ ├── licensing.json
│ │ │ │ ├── bands.json
│ │ │ │ ├── common.json
│ │ │ │ ├── instrument.json
│ │ │ │ ├── basics.json
│ │ │ │ ├── provider.json
│ │ │ │ ├── datetime.json
│ │ │ │ ├── catalog.json
│ │ │ │ └── data-values.json
│ │ ├── lib.rs
│ │ └── error.rs
│ ├── tests
│ │ ├── migrate.rs
│ │ └── examples.rs
│ ├── Cargo.toml
│ ├── README.md
│ └── CHANGELOG.md
├── server
│ ├── data
│ │ ├── 100-sentinel-2-items.parquet
│ │ └── joplin
│ │ │ ├── collection.json
│ │ │ └── feature.geojson
│ ├── src
│ │ ├── redoc.html
│ │ ├── error.rs
│ │ └── lib.rs
│ ├── Cargo.toml
│ └── README.md
├── duckdb
│ ├── data
│ │ ├── 100-landsat-items.parquet
│ │ └── 100-sentinel-2-items.parquet
│ ├── scripts
│ │ └── generate-test-data
│ ├── src
│ │ ├── extension.rs
│ │ ├── error.rs
│ │ └── lib.rs
│ ├── Cargo.toml
│ └── README.md
├── derive
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
└── pgstac
│ ├── Cargo.toml
│ ├── src
│ └── page.rs
│ └── README.md
├── docs
├── img
│ ├── rustac.svg
│ ├── rustac-notext.svg
│ ├── rustac-small.png
│ ├── stac-ferris-2.png
│ ├── stac-ferris.png
│ ├── stac-ferris-favicon.png
│ └── ferris-holding-stac-small.png
├── pronunciation.md
├── history.md
├── formats.md
├── stylesheets
│ └── extra.css
├── cli.md
└── index.md
├── img
├── rustac.png
├── rustac-small.png
├── stac-ferris.png
├── stac-ferris.xcf
├── stac-ferris-2.png
├── stac-ferris-2.xcf
├── ferris-holding-stac.png
├── ferris-holding-stac.xcf
├── stac-ferris-favicon.png
└── ferris-holding-stac-small.png
├── scripts
├── fixtures
│ └── 1000-sentinel-2-items.parquet
├── load-pgstac-fixtures
├── validate-stac-server
└── validate-stac-geoparquet
├── .gitignore
├── .release-please-manifest.json
├── .markdownlint-cli2.jsonc
├── docker-compose.yml
├── .github
├── workflows
│ ├── pr.yml
│ ├── release-please.yml
│ └── cd.yml
├── dependabot.yml
└── pull_request_template.md
├── RELEASING.md
├── release-please-config.json
├── pyproject.toml
├── .pre-commit-config.yaml
├── LICENSE-MIT
├── spec-examples
├── v1.0.0
│ ├── catalog.json
│ ├── extensions-collection
│ │ └── collection.json
│ ├── simple-item.json
│ ├── collection.json
│ └── core-item.json
├── v1.1.0
│ ├── catalog.json
│ ├── extensions-collection
│ │ └── collection.json
│ ├── simple-item.json
│ ├── collection.json
│ └── core-item.json
└── v1.1.0-beta.1
│ ├── catalog.json
│ ├── extensions-collection
│ └── collection.json
│ ├── simple-item.json
│ ├── collection.json
│ └── core-item.json
├── mkdocs.yml
├── CONTRIBUTING.md
└── Cargo.toml
/.python-version:
--------------------------------------------------------------------------------
1 | 3.12
2 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @gadomski
2 |
--------------------------------------------------------------------------------
/crates/wasm/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | ../../LICENSE-MIT
--------------------------------------------------------------------------------
/docs/img/rustac.svg:
--------------------------------------------------------------------------------
1 | ../../img/rustac.svg
--------------------------------------------------------------------------------
/crates/cli/examples:
--------------------------------------------------------------------------------
1 | ../../spec-examples/v1.1.0
--------------------------------------------------------------------------------
/crates/core/examples:
--------------------------------------------------------------------------------
1 | ../../spec-examples/v1.1.0
--------------------------------------------------------------------------------
/crates/io/examples:
--------------------------------------------------------------------------------
1 | ../../spec-examples/v1.1.0
--------------------------------------------------------------------------------
/crates/wasm/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | ../../LICENSE-APACHE
--------------------------------------------------------------------------------
/crates/extensions/examples:
--------------------------------------------------------------------------------
1 | ../../spec-examples/v1.1.0
--------------------------------------------------------------------------------
/crates/validate/examples:
--------------------------------------------------------------------------------
1 | ../../spec-examples/v1.1.0
--------------------------------------------------------------------------------
/crates/wasm/www/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/docs/img/rustac-notext.svg:
--------------------------------------------------------------------------------
1 | ../../img/rustac-notext.svg
--------------------------------------------------------------------------------
/docs/img/rustac-small.png:
--------------------------------------------------------------------------------
1 | ../../img/rustac-small.png
--------------------------------------------------------------------------------
/docs/img/stac-ferris-2.png:
--------------------------------------------------------------------------------
1 | ../../img/stac-ferris-2.png
--------------------------------------------------------------------------------
/docs/img/stac-ferris.png:
--------------------------------------------------------------------------------
1 | ../../img/stac-ferris.png
--------------------------------------------------------------------------------
/docs/img/stac-ferris-favicon.png:
--------------------------------------------------------------------------------
1 | ../../img/stac-ferris-favicon.png
--------------------------------------------------------------------------------
/docs/img/ferris-holding-stac-small.png:
--------------------------------------------------------------------------------
1 | ../../img/ferris-holding-stac-small.png
--------------------------------------------------------------------------------
/img/rustac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/rustac.png
--------------------------------------------------------------------------------
/docs/pronunciation.md:
--------------------------------------------------------------------------------
1 | # Pronunciation
2 |
3 | We pronounce **rustac** "ruh-stac".
4 |
--------------------------------------------------------------------------------
/crates/server/data/100-sentinel-2-items.parquet:
--------------------------------------------------------------------------------
1 | ../../duckdb/data/100-sentinel-2-items.parquet
--------------------------------------------------------------------------------
/img/rustac-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/rustac-small.png
--------------------------------------------------------------------------------
/img/stac-ferris.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/stac-ferris.png
--------------------------------------------------------------------------------
/img/stac-ferris.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/stac-ferris.xcf
--------------------------------------------------------------------------------
/img/stac-ferris-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/stac-ferris-2.png
--------------------------------------------------------------------------------
/img/stac-ferris-2.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/stac-ferris-2.xcf
--------------------------------------------------------------------------------
/img/ferris-holding-stac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/ferris-holding-stac.png
--------------------------------------------------------------------------------
/img/ferris-holding-stac.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/ferris-holding-stac.xcf
--------------------------------------------------------------------------------
/img/stac-ferris-favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/stac-ferris-favicon.png
--------------------------------------------------------------------------------
/crates/core/assets/dataset.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/core/assets/dataset.tif
--------------------------------------------------------------------------------
/crates/core/assets/dataset_geo.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/core/assets/dataset_geo.tif
--------------------------------------------------------------------------------
/img/ferris-holding-stac-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/img/ferris-holding-stac-small.png
--------------------------------------------------------------------------------
/crates/io/data/extended-item.parquet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/io/data/extended-item.parquet
--------------------------------------------------------------------------------
/crates/core/data/extended-item.parquet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/core/data/extended-item.parquet
--------------------------------------------------------------------------------
/crates/duckdb/data/100-landsat-items.parquet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/duckdb/data/100-landsat-items.parquet
--------------------------------------------------------------------------------
/crates/duckdb/data/100-sentinel-2-items.parquet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/crates/duckdb/data/100-sentinel-2-items.parquet
--------------------------------------------------------------------------------
/scripts/fixtures/1000-sentinel-2-items.parquet:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stac-utils/rustac/HEAD/scripts/fixtures/1000-sentinel-2-items.parquet
--------------------------------------------------------------------------------
/crates/io/mocks/not-a-collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": "NotFoundError",
3 | "description": "No collection with id 'not-a-collection' found!"
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
3 | dist/
4 | pyrightconfig.json
5 | site/
6 | .cache
7 | crates/wasm/www/package-lock.json
8 | node_modules/
9 | package.json
10 | package-lock.json
11 |
--------------------------------------------------------------------------------
/docs/history.md:
--------------------------------------------------------------------------------
1 | # History
2 |
3 | Until 2025-04-17, this repository was named **stac-rs**.
4 | See [this RFC](https://github.com/stac-utils/rustac/issues/641) for context on the name change.
5 |
--------------------------------------------------------------------------------
/crates/wasm/www/bootstrap.js:
--------------------------------------------------------------------------------
1 | // A dependency graph that contains any wasm must all be imported
2 | // asynchronously. This `bootstrap.js` file does the single async import, so
3 | // that no one else needs to worry about it again.
4 | import("./index.js")
5 | .catch(e => console.error("Error importing `index.js`:", e));
6 |
--------------------------------------------------------------------------------
/scripts/load-pgstac-fixtures:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | scripts=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
6 | fixtures="$scripts/fixtures"
7 | dsn=postgresql://username:password@localhost:5432/postgis
8 |
9 | cargo run -- pgstac load "$dsn" "$fixtures/sentinel-2-l2a.json" "$fixtures/1000-sentinel-2-items.parquet"
10 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "crates/cli": "0.2.0",
3 | "crates/core": "0.15.0",
4 | "crates/derive": "0.3.0",
5 | "crates/duckdb": "0.3.0",
6 | "crates/extensions": "0.1.2",
7 | "crates/io": "0.2.0",
8 | "crates/pgstac": "0.4.0",
9 | "crates/server": "0.4.0",
10 | "crates/validate": "0.6.0",
11 | "crates/wasm": "0.1.0"
12 | }
13 |
--------------------------------------------------------------------------------
/.markdownlint-cli2.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "config": {
3 | "MD013": false,
4 | },
5 | "globs": ["**/*.md"],
6 | "gitignore": true,
7 | "ignores": [
8 | "spec-examples/*/README.md",
9 | "crates/*/examples/README.md",
10 | "crates/*/CHANGELOG.md",
11 | "target/**/*.md",
12 | "LICENSE-*",
13 | ".github/pull_request_template.md",
14 | ],
15 | }
16 |
--------------------------------------------------------------------------------
/crates/cli/src/main.rs:
--------------------------------------------------------------------------------
1 | use clap::Parser;
2 | use rustac::Rustac;
3 |
4 | #[tokio::main]
5 | async fn main() {
6 | let args = Rustac::parse();
7 | std::process::exit(match args.run(true).await {
8 | Ok(()) => 0,
9 | Err(err) => {
10 | eprintln!("ERROR: {err}");
11 | 1 // TODO make this more meaningful
12 | }
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/crates/wasm/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stac-wasm
6 |
7 |
8 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.0.0/licensing.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/licensing.json#",
4 | "title": "Licensing Fields",
5 | "type": "object",
6 | "properties": {
7 | "license": {
8 | "type": "string",
9 | "pattern": "^[\\w\\-\\.\\+]+$"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/licensing.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/licensing.json",
4 | "title": "Licensing Fields",
5 | "type": "object",
6 | "properties": {
7 | "license": {
8 | "type": "string",
9 | "pattern": "^[\\w\\-\\.\\+]+$"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | pgstac:
3 | image: ghcr.io/stac-utils/pgstac:${PGSTAC_VERSION:-v0.9.8}
4 | environment:
5 | - POSTGRES_USER=username
6 | - POSTGRES_PASSWORD=password
7 | - POSTGRES_DB=postgis
8 | - PGUSER=username
9 | - PGPASSWORD=password
10 | - PGDATABASE=postgis
11 | ports:
12 | - "5432:5432"
13 | command: postgres -N 500
14 |
--------------------------------------------------------------------------------
/crates/io/tests/aws.rs:
--------------------------------------------------------------------------------
1 | use stac::Catalog;
2 |
3 | #[test]
4 | fn read_from_s3() {
5 | tokio_test::block_on(async {
6 | let (store, path) = stac_io::parse_href_opts(
7 | "s3://nz-elevation/catalog.json",
8 | [("skip_signature", "true"), ("region", "ap-southeast-2")],
9 | )
10 | .unwrap();
11 | let _: Catalog = store.get(path).await.unwrap();
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: PR
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - reopened
9 |
10 | jobs:
11 | lint:
12 | name: Lint
13 | runs-on: ubuntu-latest
14 | permissions:
15 | pull-requests: read
16 | steps:
17 | - uses: amannn/action-semantic-pull-request@v6
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20 |
--------------------------------------------------------------------------------
/crates/wasm/www/webpack.config.js:
--------------------------------------------------------------------------------
1 | const CopyWebpackPlugin = require("copy-webpack-plugin");
2 | const path = require("path");
3 |
4 | module.exports = {
5 | entry: "./bootstrap.js",
6 | output: {
7 | path: path.resolve(__dirname, "dist"),
8 | filename: "bootstrap.js",
9 | },
10 | mode: "development",
11 | plugins: [new CopyWebpackPlugin(["index.html"])],
12 | experiments: {
13 | asyncWebAssembly: true,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | We use [release-please](https://github.com/googleapis/release-please) to manage versioning and creating Github releases.
4 | Look for a release [pull request](https://github.com/stac-utils/rustac/pulls) to see what's queued up.
5 | To release, simply merge that pull request, then:
6 |
7 | ```sh
8 | cargo publish --workspace
9 | ```
10 |
11 | You may need to `--exclude` or `--include` certain packages, depending on what's changed.
12 |
--------------------------------------------------------------------------------
/crates/validate/tests/migrate.rs:
--------------------------------------------------------------------------------
1 | use rstest::rstest;
2 | use stac::{Migrate, Value, Version};
3 | use stac_validate::Validate;
4 | use std::path::PathBuf;
5 |
6 | #[rstest]
7 | #[tokio::test]
8 | async fn v1_0_0_to_v1_1_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
9 | let value: Value = stac::read(path.to_str().unwrap()).unwrap();
10 | let value = value.migrate(&Version::v1_1_0).unwrap();
11 | value.validate().await.unwrap();
12 | }
13 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["cargo-workspace"],
3 | "release-type": "rust",
4 | "bump-minor-pre-major": true,
5 | "bump-patch-for-minor-pre-major": true,
6 | "packages": {
7 | "crates/cli": {},
8 | "crates/core": {},
9 | "crates/derive": {},
10 | "crates/duckdb": {},
11 | "crates/extensions": {},
12 | "crates/io": {},
13 | "crates/pgstac": {},
14 | "crates/server": {},
15 | "crates/validate": {},
16 | "crates/wasm": {}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/crates/derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-derive"
3 | description = "Proc macros for deriving STAC traits. Should usually not be used directly."
4 | version = "0.3.0"
5 | authors.workspace = true
6 | edition.workspace = true
7 | homepage.workspace = true
8 | repository.workspace = true
9 | license.workspace = true
10 | categories.workspace = true
11 | rust-version.workspace = true
12 |
13 | [lib]
14 | proc-macro = true
15 |
16 | [dependencies]
17 | quote.workspace = true
18 | syn.workspace = true
19 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "rustac"
3 | version = "0.0.0"
4 | description = "This package should never be released, it's just for uv."
5 | requires-python = ">=3.12"
6 | dependencies = []
7 |
8 | [dependency-groups]
9 | docs = ["mkdocs-material[imaging]>=9.5.40", "mkdocs-redirects>=1.2.2"]
10 | stac-geoparquet = [
11 | "deepdiff>=8.0.1",
12 | "pyarrow>=17.0.0",
13 | "stac-geoparquet>=0.6.0",
14 | ]
15 | stac-api-validator = ["setuptools>=75.1.0", "stac-api-validator>=0.6.3"]
16 |
17 | [tool.uv]
18 | default-groups = ["docs", "stac-geoparquet", "stac-api-validator"]
19 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | labels: []
8 | - package-ecosystem: "cargo"
9 | directory: "/"
10 | schedule:
11 | interval: "weekly"
12 | labels: []
13 | groups:
14 | geoarrow:
15 | patterns:
16 | - "geoarrow-*"
17 | - "geoparquet"
18 | - "arrow-*"
19 | - "parquet"
20 | - package-ecosystem: "pip"
21 | directory: "/"
22 | schedule:
23 | interval: "weekly"
24 | labels: []
25 |
--------------------------------------------------------------------------------
/crates/wasm/www/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stac-wasm-test",
3 | "version": "0.0.0",
4 | "description": "STAC WASM test",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.config.js",
8 | "start": "webpack-dev-server"
9 | },
10 | "keywords": [],
11 | "license": "(MIT OR Apache-2.0)",
12 | "devDependencies": {
13 | "stac-wasm": "file:../pkg",
14 | "@duckdb/duckdb-wasm": "^1.28.0",
15 | "webpack": "^5.99.8",
16 | "webpack-cli": "^6.0.1",
17 | "webpack-dev-server": "^5.2.1",
18 | "copy-webpack-plugin": "^5.0.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/docs/formats.md:
--------------------------------------------------------------------------------
1 | # Formats
2 |
3 | **rustac** "speaks" three forms of STAC:
4 |
5 | - **JSON**: STAC is derived from [GeoJSON](https://geojson.org/)
6 | - **Newline-delimited JSON (ndjson)**: One JSON [item](https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md) per line, often used for bulk item loading and storage
7 | - **stac-geoparquet**: A newer [specification](https://github.com/radiantearth/stac-geoparquet-spec) for storing STAC items, and optionally collections
8 |
9 | We also have interfaces to other storage backends, e.g. Postgres via [pgstac](https://github.com/stac-utils/pgstac).
10 |
--------------------------------------------------------------------------------
/crates/io/src/read.rs:
--------------------------------------------------------------------------------
1 | use crate::{Format, Readable, Result};
2 | use stac::SelfHref;
3 |
4 | /// Reads a STAC value from an href.
5 | ///
6 | /// The format will be inferred from the href's extension. If you want to
7 | /// specify the format, use [Format::read].
8 | ///
9 | /// # Examples
10 | ///
11 | /// ```
12 | /// let item: stac::Item = stac_io::read("examples/simple-item.json").unwrap();
13 | /// ```
14 | pub fn read(href: impl ToString) -> Result {
15 | let href = href.to_string();
16 | let format = Format::infer_from_href(&href).unwrap_or_default();
17 | format.read(href)
18 | }
19 |
--------------------------------------------------------------------------------
/crates/validate/tests/examples.rs:
--------------------------------------------------------------------------------
1 | use rstest::rstest;
2 | use stac::Value;
3 | use stac_validate::Validate;
4 | use std::path::PathBuf;
5 |
6 | #[rstest]
7 | #[tokio::test]
8 | async fn v1_0_0(#[files("../../spec-examples/v1.0.0/**/*.json")] path: PathBuf) {
9 | let value: Value = stac::read(path.to_str().unwrap()).unwrap();
10 | value.validate().await.unwrap();
11 | }
12 |
13 | #[rstest]
14 | #[tokio::test]
15 | async fn v1_1_0(#[files("../../spec-examples/v1.1.0/**/*.json")] path: PathBuf) {
16 | let value: Value = stac::read(path.to_str().unwrap()).unwrap();
17 | value.validate().await.unwrap();
18 | }
19 |
--------------------------------------------------------------------------------
/crates/extensions/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-extensions"
3 | description = "Manage STAC extensions (https://stac-extensions.github.io/)"
4 | version = "0.1.2"
5 | keywords = ["geospatial", "stac", "extensions"]
6 | authors.workspace = true
7 | edition.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | rust-version.workspace = true
13 |
14 | [dependencies]
15 | geojson.workspace = true
16 | indexmap.workspace = true
17 | serde.workspace = true
18 | serde_json.workspace = true
19 | stac = { version = "0.15.0", path = "../core" }
20 |
--------------------------------------------------------------------------------
/docs/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --md-primary-fg-color: rgb(55, 108, 129);
3 | --md-primary-fg-color--light: rgb(228, 246, 251);
4 | --md-primary-fg-color--dark: rgb(26, 78, 99);
5 | --md-accent-fg-color: rgb(78, 180, 174);
6 | --md-accent-fg-color--transparent: rgba(78, 180, 174, 0.5);
7 | }
8 |
9 | [data-md-color-scheme="stac"] {
10 | --md-primary-fg-color: rgb(55, 108, 129);
11 | --md-primary-fg-color--light: rgb(228, 246, 251);
12 | --md-primary-fg-color--dark: rgb(26, 78, 99);
13 | --md-accent-fg-color: rgb(78, 180, 174);
14 | --md-accent-fg-color--transparent: rgba(78, 180, 174, 0.5);
15 | }
16 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.0.0/basics.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/basics.json#",
4 | "title": "Basic Descriptive Fields",
5 | "type": "object",
6 | "properties": {
7 | "title": {
8 | "title": "Item Title",
9 | "description": "A human-readable title describing the Item.",
10 | "type": "string"
11 | },
12 | "description": {
13 | "title": "Item Description",
14 | "description": "Detailed multi-line description to fully explain the Item.",
15 | "type": "string"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/bands.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/bands.json",
4 | "title": "Bands Field",
5 | "type": "object",
6 | "properties": {
7 | "bands": {
8 | "type": "array",
9 | "items": {
10 | "type": "object",
11 | "properties": {
12 | "name": {
13 | "type": "string"
14 | }
15 | },
16 | "allOf": [
17 | {
18 | "$ref": "common.json"
19 | }
20 | ]
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 |
6 | permissions:
7 | contents: write
8 | issues: write
9 | pull-requests: write
10 |
11 | name: release-please
12 |
13 | jobs:
14 | release-please:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/create-github-app-token@v2
18 | id: generate-token
19 | with:
20 | app-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
21 | private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
22 | - uses: googleapis/release-please-action@v4
23 | with:
24 | token: ${{ steps.generate-token.outputs.token }}
25 |
--------------------------------------------------------------------------------
/crates/server/src/redoc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | stac-server API documentation
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 | Description of the changes, including any "sidecar" changes that came along (e.g. small bugfixes you found along the way).
4 |
5 | ### Related issues
6 |
7 | - List any issues that this pull request closes or is related to
8 | - Delete this section if it is not applicable
9 |
10 | ### Checklist
11 |
12 | Delete any checklist items that do not apply (e.g. if your change is minor, it may not require documentation updates).
13 |
14 | - [ ] Unit tests
15 | - [ ] Documentation, including doctests
16 | - [ ] Pull request title follows [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
17 | - [ ] Pre-commit hooks pass (`prek run --all-files`)
18 |
--------------------------------------------------------------------------------
/crates/io/src/write.rs:
--------------------------------------------------------------------------------
1 | use crate::{Format, Result, Writeable};
2 | use std::path::Path;
3 |
4 | /// Writes a STAC value to a path.
5 | ///
6 | /// The format will be inferred from the href's extension. If you want to
7 | /// specify the format, use [Format::write].
8 | ///
9 | /// # Examples
10 | ///
11 | /// ```no_run
12 | /// use stac::Item;
13 | ///
14 | /// let item = Item::new("an-id");
15 | /// stac_io::write("an-id.json", item).unwrap();
16 | /// ```
17 | pub fn write(path: impl AsRef, value: T) -> Result<()> {
18 | let path = path.as_ref();
19 | let format = path
20 | .to_str()
21 | .and_then(Format::infer_from_href)
22 | .unwrap_or_default();
23 | format.write(path, value)
24 | }
25 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/commonjson",
4 | "title": "STAC Common Metadata",
5 | "type": "object",
6 | "description": "This schema includes all common metadata fields.",
7 | "allOf": [
8 | {
9 | "$ref": "basics.json"
10 | },
11 | {
12 | "$ref": "bands.json"
13 | },
14 | {
15 | "$ref": "datetime.json"
16 | },
17 | {
18 | "$ref": "data-values.json"
19 | },
20 | {
21 | "$ref": "instrument.json"
22 | },
23 | {
24 | "$ref": "licensing.json"
25 | },
26 | {
27 | "$ref": "provider.json"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v6.0.0
4 | hooks:
5 | - id: check-yaml
6 | - id: end-of-file-fixer
7 | - repo: https://github.com/DavidAnson/markdownlint-cli2
8 | rev: v0.19.0
9 | hooks:
10 | - id: markdownlint-cli2
11 | pass_filenames: false
12 | - repo: local
13 | hooks:
14 | - id: cargo fmt
15 | name: cargo fmt
16 | entry: cargo
17 | language: system
18 | pass_filenames: false
19 | args: ["fmt", "--all"]
20 | - id: cargo clippy
21 | name: cargo clippy
22 | entry: cargo
23 | language: system
24 | pass_filenames: false
25 | args: ["clippy", "--workspace"]
26 |
--------------------------------------------------------------------------------
/crates/duckdb/scripts/generate-test-data:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | scripts=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
6 | duckdb=$(dirname $scripts)
7 | data="$duckdb/data"
8 |
9 | cargo run -- \
10 | search https://planetarycomputer.microsoft.com/api/stac/v1 "$data/100-sentinel-2-items.parquet" \
11 | -c sentinel-2-l2a \
12 | --max-items 100 \
13 | --sortby=-datetime \
14 | --intersects '{"type":"Point","coordinates":[-105.1019,40.1672]}'
15 |
16 | cargo run -- \
17 | search https://planetarycomputer.microsoft.com/api/stac/v1 "$data/100-landsat-items.parquet" \
18 | -c landsat-c2-l2 \
19 | --max-items 100 \
20 | --sortby=-datetime \
21 | --intersects '{"type":"Point","coordinates":[-105.1019,40.1672]}'
22 |
23 |
--------------------------------------------------------------------------------
/crates/io/src/realized_href.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use url::Url;
3 |
4 | /// An href that has been realized to a path or a url.
5 | #[derive(Debug)]
6 | pub enum RealizedHref {
7 | /// A path buf
8 | PathBuf(PathBuf),
9 |
10 | /// A url
11 | Url(Url),
12 | }
13 |
14 | impl From<&str> for RealizedHref {
15 | fn from(s: &str) -> RealizedHref {
16 | if let Ok(url) = Url::parse(s) {
17 | if url.scheme() == "file" {
18 | url.to_file_path()
19 | .map(RealizedHref::PathBuf)
20 | .unwrap_or_else(|_| RealizedHref::Url(url))
21 | } else {
22 | RealizedHref::Url(url)
23 | }
24 | } else {
25 | RealizedHref::PathBuf(PathBuf::from(s))
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/crates/core/src/geo.rs:
--------------------------------------------------------------------------------
1 | //! Geometry utilities, enabled by the `geo` feature.
2 |
3 | use crate::{Error, Result};
4 | use geo::{Rect, coord};
5 |
6 | /// Creates a two-dimensional rectangle from four coordinates.
7 | ///
8 | /// # Examples
9 | ///
10 | /// ```
11 | /// let bbox = stac::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap();
12 | /// ```
13 | pub fn bbox(coordinates: &[f64]) -> Result {
14 | if coordinates.len() == 4 {
15 | Ok(Rect::new(
16 | coord! { x: coordinates[0], y: coordinates[1] },
17 | coord! { x: coordinates[2], y: coordinates[3] },
18 | ))
19 | } else {
20 | // TODO support three dimensional
21 | Err(Error::InvalidBbox(
22 | coordinates.to_vec(),
23 | "unsupported 3D bbox",
24 | ))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/crates/extensions/README.md:
--------------------------------------------------------------------------------
1 | # stac-extensions
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/stac-extensions/latest/stac_extensions/)
5 | [](https://crates.io/crates/stac-extensions)
6 | 
7 |
8 | Rudimentary support for [STAC extensions](https://stac-extensions.github.io/).
9 |
10 | ## Other info
11 |
12 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
13 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.0.0/instrument.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/instrument.json#",
4 | "title": "Instrument Fields",
5 | "type": "object",
6 | "properties": {
7 | "platform": {
8 | "title": "Platform",
9 | "type": "string"
10 | },
11 | "instruments": {
12 | "title": "Instruments",
13 | "type": "array",
14 | "items": {
15 | "type": "string"
16 | }
17 | },
18 | "constellation": {
19 | "title": "Constellation",
20 | "type": "string"
21 | },
22 | "mission": {
23 | "title": "Mission",
24 | "type": "string"
25 | },
26 | "gsd": {
27 | "title": "Ground Sample Distance",
28 | "type": "number",
29 | "exclusiveMinimum": 0
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/instrument.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/instrument.json",
4 | "title": "Instrument Fields",
5 | "type": "object",
6 | "properties": {
7 | "platform": {
8 | "title": "Platform",
9 | "type": "string"
10 | },
11 | "instruments": {
12 | "title": "Instruments",
13 | "type": "array",
14 | "items": {
15 | "type": "string"
16 | }
17 | },
18 | "constellation": {
19 | "title": "Constellation",
20 | "type": "string"
21 | },
22 | "mission": {
23 | "title": "Mission",
24 | "type": "string"
25 | },
26 | "gsd": {
27 | "title": "Ground Sample Distance",
28 | "type": "number",
29 | "exclusiveMinimum": 0
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/crates/wasm/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-wasm"
3 | version = "0.1.0"
4 | readme = "README.md"
5 | description = "Converts Arrow arrays to STAC items, via WASM"
6 | authors.workspace = true
7 | edition.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | rust-version.workspace = true
13 | publish = false
14 |
15 | [lib]
16 | crate-type = ["cdylib", "rlib"]
17 |
18 | [dependencies]
19 | arrow-array.workspace = true
20 | arrow-schema.workspace = true
21 | arrow-wasm = { git = "https://github.com/kylebarron/arrow-wasm", rev = "6da94ef0a1522a244984a7d3d58a0339d0851d96" }
22 | serde.workspace = true
23 | serde-wasm-bindgen = "0.6.5"
24 | stac = { version = "0.15.0", path = "../core", features = ["geoparquet"] }
25 | thiserror.workspace = true
26 | wasm-bindgen = "0.2.84"
27 |
28 | [dev-dependencies]
29 | wasm-bindgen-test = "0.3.34"
30 |
--------------------------------------------------------------------------------
/crates/duckdb/src/extension.rs:
--------------------------------------------------------------------------------
1 | /// A DuckDB extension
2 | // TODO implement aliases ... I don't know how vectors work yet 😢
3 | #[derive(Debug)]
4 | pub struct Extension {
5 | /// The extension name.
6 | pub name: String,
7 |
8 | /// Is the extension loaded?
9 | pub loaded: bool,
10 |
11 | /// Is the extension installed?
12 | pub installed: bool,
13 |
14 | /// The path to the extension.
15 | ///
16 | /// This might be `(BUILT-IN)` for the core extensions.
17 | pub install_path: Option,
18 |
19 | /// The extension description.
20 | pub description: String,
21 |
22 | /// The extension version.
23 | pub version: Option,
24 |
25 | /// The install mode.
26 | ///
27 | /// We don't bother making this an enum, yet.
28 | pub install_mode: Option,
29 |
30 | /// Where the extension was installed from.
31 | pub installed_from: Option,
32 | }
33 |
--------------------------------------------------------------------------------
/crates/pgstac/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pgstac"
3 | description = "Rust interface for pgstac"
4 | version = "0.4.0"
5 | keywords = ["geospatial", "stac", "metadata", "raster", "database"]
6 | categories = ["database", "data-structures", "science"]
7 | authors.workspace = true
8 | edition.workspace = true
9 | homepage.workspace = true
10 | repository.workspace = true
11 | license.workspace = true
12 | rust-version.workspace = true
13 |
14 | [dependencies]
15 | serde.workspace = true
16 | serde_json.workspace = true
17 | stac = { version = "0.15.0", path = "../core" }
18 | thiserror.workspace = true
19 | tokio-postgres = { workspace = true, features = ["with-serde_json-1"] }
20 |
21 | [dev-dependencies]
22 | geojson.workspace = true
23 | rstest.workspace = true
24 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
25 | tokio-test.workspace = true
26 |
27 | [package.metadata.docs.rs]
28 | all-features = true
29 | rustdoc-args = ["--cfg", "docsrs"]
30 |
--------------------------------------------------------------------------------
/crates/core/src/statistics.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | /// Statistics of all pixels in the band.
4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5 | pub struct Statistics {
6 | /// Mean value of all the pixels in the band
7 | #[serde(skip_serializing_if = "Option::is_none")]
8 | pub mean: Option,
9 |
10 | /// Minimum value of all the pixels in the band
11 | #[serde(skip_serializing_if = "Option::is_none")]
12 | pub minimum: Option,
13 |
14 | /// Maximum value of all the pixels in the band
15 | #[serde(skip_serializing_if = "Option::is_none")]
16 | pub maximum: Option,
17 |
18 | /// Standard deviation value of all the pixels in the band
19 | #[serde(skip_serializing_if = "Option::is_none")]
20 | pub stddev: Option,
21 |
22 | /// Percentage of valid (not nodata) pixel
23 | #[serde(skip_serializing_if = "Option::is_none")]
24 | pub valid_percent: Option,
25 | }
26 |
--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: The rustac command-line interface (CLI)
3 | ---
4 |
5 | # Command-line interface (CLI)
6 |
7 | The **rustac** command-line interface can be installed two ways.
8 | If you have Rust, use `cargo`:
9 |
10 | ```sh
11 | cargo install rustac # to use libduckdb on your system
12 | # or
13 | cargo install rustac -F duckdb-bundled # to build libduckdb on install (slow)
14 | ```
15 |
16 | The CLI is called **rustac**:
17 |
18 | ```shell
19 | rustac --help
20 | ```
21 |
22 | If you don't have DuckDB on your system, you can also use the Python wheel, which includes **libduckdb**:
23 |
24 | ```shell
25 | python -m pip install rustac
26 | ```
27 |
28 | To get shell completions, use:
29 |
30 | ```shell
31 | rustac generate-completions >
32 | ```
33 |
34 | ## History
35 |
36 | The CLI was announced at [@gadomski's](https://github.com/gadomski/) [2024 FOSS4G-NA presentation](https://www.gadom.ski/2024-09-FOSS4G-NA-rustac/).
37 |
--------------------------------------------------------------------------------
/crates/validate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-validate"
3 | version = "0.6.0"
4 | readme = "README.md"
5 | description = "json-schema validation for the Rust implementation of the STAC specification"
6 | authors.workspace = true
7 | edition.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | rust-version.workspace = true
13 |
14 | [dependencies]
15 | fluent-uri.workspace = true
16 | jsonschema.workspace = true
17 | reqwest = { workspace = true, features = ["blocking", "json"] }
18 | serde.workspace = true
19 | serde_json.workspace = true
20 | stac = { version = "0.15.0", path = "../core" }
21 | thiserror.workspace = true
22 | async-trait.workspace = true
23 | referencing.workspace = true
24 | async-recursion.workspace = true
25 |
26 | [dev-dependencies]
27 | stac-io = { version = "0.2.0", path = "../io" }
28 | rstest.workspace = true
29 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
30 |
--------------------------------------------------------------------------------
/crates/core/src/api/collections.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use serde_json::{Map, Value};
3 | use stac::{Collection, Link};
4 | use stac_derive::{Links, SelfHref};
5 |
6 | /// Object containing an array of collections and an array of links.
7 | #[derive(Debug, Serialize, Deserialize, SelfHref, Links)]
8 | pub struct Collections {
9 | /// The [Collection] objects in the [stac::Catalog].
10 | pub collections: Vec,
11 |
12 | /// The [stac::Link] relations.
13 | pub links: Vec,
14 |
15 | /// Additional fields.
16 | #[serde(flatten)]
17 | pub additional_fields: Map,
18 |
19 | #[serde(skip)]
20 | self_href: Option,
21 | }
22 |
23 | impl From> for Collections {
24 | fn from(collections: Vec) -> Collections {
25 | Collections {
26 | collections,
27 | links: Vec::new(),
28 | additional_fields: Map::new(),
29 | self_href: None,
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/crates/extensions/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [0.1.2](https://github.com/stac-utils/rustac/compare/stac-extensions-v0.1.1...stac-extensions-v0.1.2) (2025-12-01)
8 |
9 |
10 | ### Dependencies
11 |
12 | * The following workspace dependencies were updated
13 | * dependencies
14 | * stac bumped from 0.14.0 to 0.15.0
15 |
16 | ## [0.1.1] - 2025-11-14
17 |
18 | Update **stac** dependency.
19 |
20 | ## [0.1.0] - 2025-01-31
21 |
22 | Initial release.
23 |
24 | [Unreleased]: https://github.com/stac-utils/rustac/compare/stac-extensions-v0.1.1...main
25 | [0.1.1]: https://github.com/stac-utils/rustac/compare/stac-extensions-v0.1.0...stac-extensions-v0.1.1
26 | [0.1.0]: https://github.com/stac-utils/rustac/releases/tag/v0.1.0
27 |
28 |
29 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/basics.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/basics.json",
4 | "title": "Basic Descriptive Fields",
5 | "type": "object",
6 | "properties": {
7 | "title": {
8 | "title": "Title",
9 | "description": "A human-readable title describing the entity.",
10 | "type": "string"
11 | },
12 | "description": {
13 | "title": "Description",
14 | "description": "Detailed multi-line description to fully explain the entity.",
15 | "type": "string",
16 | "minLength": 1
17 | },
18 | "keywords": {
19 | "title": "Keywords",
20 | "description": "List of keywords describing the entity.",
21 | "type": "array",
22 | "items": {
23 | "type": "string"
24 | }
25 | },
26 | "roles": {
27 | "title": "Roles",
28 | "type": "array",
29 | "items": {
30 | "type": "string"
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/crates/validate/README.md:
--------------------------------------------------------------------------------
1 | # stac-validate
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/stac-validate/latest/stac_validate/)
5 | [](https://crates.io/crates/stac-validate)
6 | 
7 | [](./CODE_OF_CONDUCT)
8 |
9 | [json-schema](https://json-schema.org/) validation for the Rust implementation of the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) specification.
10 |
11 | ## Other info
12 |
13 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
14 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/crates/io/README.md:
--------------------------------------------------------------------------------
1 | # stac-io
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/stac-io/latest/stac_io/)
5 | [](https://crates.io/crates/stac-io)
6 | 
7 | [](./CODE_OF_CONDUCT)
8 |
9 | Input and output (I/O) for the Rust implementation of the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) specification.
10 |
11 | ## Usage
12 |
13 | To use the library in your project:
14 |
15 | ```toml
16 | [dependencies]
17 | stac-io = "0.1"
18 | ```
19 |
20 | ## Other info
21 |
22 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
23 |
--------------------------------------------------------------------------------
/crates/core/data/bands-v1.1.0.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Feature",
3 | "stac_version": "1.1.0",
4 | "id": "bands-migration",
5 | "geometry": null,
6 | "properties": {
7 | "datetime": "2024-08-11T16:07:32.766181Z"
8 | },
9 | "links": [],
10 | "assets": {
11 | "example": {
12 | "href": "example.tif",
13 | "data_type": "uint16",
14 | "raster:sampling": "area",
15 | "raster:spatial_resolution": 10,
16 | "bands": [
17 | {
18 | "name": "r",
19 | "eo:common_name": "red"
20 | },
21 | {
22 | "name": "g",
23 | "eo:common_name": "green"
24 | },
25 | {
26 | "name": "b",
27 | "eo:common_name": "blue"
28 | },
29 | {
30 | "name": "nir",
31 | "eo:common_name": "nir",
32 | "raster:spatial_resolution": 30
33 | }
34 | ]
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/crates/duckdb/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-duckdb"
3 | description = "Client for querying stac-geoparquet using DuckDB"
4 | version = "0.3.0"
5 | keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
6 | authors.workspace = true
7 | edition.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | rust-version.workspace = true
13 |
14 | [features]
15 | default = []
16 | bundled = ["duckdb/bundled"]
17 |
18 | [dependencies]
19 | arrow-array.workspace = true
20 | arrow-schema.workspace = true
21 | chrono.workspace = true
22 | cql2.workspace = true
23 | duckdb.workspace = true
24 | geo.workspace = true
25 | geoarrow-schema = { workspace = true }
26 | geojson.workspace = true
27 | getrandom.workspace = true
28 | log.workspace = true
29 | serde_json.workspace = true
30 | stac = { version = "0.15.0", path = "../core", features = ["geoarrow", "geo"] }
31 | thiserror.workspace = true
32 |
33 | [dev-dependencies]
34 | geo.workspace = true
35 | rstest.workspace = true
36 | stac-validate = { version = "0.6.0", path = "../validate" }
37 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
38 |
--------------------------------------------------------------------------------
/crates/core/src/data_type.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | /// The data type gives information about the values in the file.
4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5 | #[serde(rename_all = "lowercase")]
6 | pub enum DataType {
7 | /// 8-bit integer
8 | Int8,
9 |
10 | /// 16-bit integer
11 | Int16,
12 |
13 | /// 32-bit integer
14 | Int32,
15 |
16 | /// 64-bit integer
17 | Int64,
18 |
19 | /// Unsigned 8-bit integer (common for 8-bit RGB PNG's)
20 | UInt8,
21 |
22 | /// Unsigned 16-bit integer
23 | UInt16,
24 |
25 | /// Unsigned 32-bit integer
26 | UInt32,
27 |
28 | /// Unsigned 64-bit integer
29 | UInt64,
30 |
31 | /// 16-bit float
32 | Float16,
33 |
34 | /// 32-bit float
35 | Float32,
36 |
37 | /// 64-bit float
38 | Float64,
39 |
40 | /// 16-bit complex integer
41 | CInt16,
42 |
43 | /// 32-bit complex integer
44 | CInt32,
45 |
46 | /// 32-bit complex float
47 | CFloat32,
48 |
49 | /// 64-bit complex float
50 | CFloat64,
51 |
52 | /// Other data type than the ones listed above (e.g. boolean, string, higher precision numbers)
53 | Other,
54 | }
55 |
--------------------------------------------------------------------------------
/crates/duckdb/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | /// A crate-specific error enum.
4 | #[derive(Debug, Error)]
5 | #[non_exhaustive]
6 | pub enum Error {
7 | /// [chrono::format::ParseError]
8 | #[error(transparent)]
9 | ChronoParse(#[from] chrono::format::ParseError),
10 |
11 | /// [cql2::Error]
12 | #[error(transparent)]
13 | Cql2(#[from] Box),
14 |
15 | /// [duckdb::Error]
16 | #[error(transparent)]
17 | DuckDB(#[from] duckdb::Error),
18 |
19 | /// [geoarrow_schema::error::GeoArrowError]
20 | #[error(transparent)]
21 | GeoArrow(#[from] geoarrow_schema::error::GeoArrowError),
22 |
23 | /// [serde_json::Error]
24 | #[error(transparent)]
25 | SerdeJson(#[from] serde_json::Error),
26 |
27 | /// [geojson::Error]
28 | #[error(transparent)]
29 | GeoJSON(#[from] Box),
30 |
31 | /// [stac::Error]
32 | #[error(transparent)]
33 | Stac(#[from] stac::Error),
34 |
35 | /// The query search extension is not implemented.
36 | #[error("query is not implemented")]
37 | QueryNotImplemented,
38 |
39 | /// [std::num::TryFromIntError]
40 | #[error(transparent)]
41 | TryFromInt(#[from] std::num::TryFromIntError),
42 | }
43 |
--------------------------------------------------------------------------------
/crates/duckdb/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Use [duckdb](https://duckdb.org/) with [STAC](https://stacspec.org).
2 |
3 | #![warn(unused_crate_dependencies)]
4 |
5 | mod client;
6 | mod error;
7 | mod extension;
8 |
9 | pub use {client::Client, error::Error, extension::Extension};
10 |
11 | use getrandom as _;
12 |
13 | /// Searches a stac-geoparquet file.
14 | ///
15 | /// # Examples
16 | ///
17 | /// ```
18 | /// let item_collection = stac_duckdb::search("data/100-sentinel-2-items.parquet", Default::default(), None).unwrap();
19 | /// ```
20 | pub fn search(
21 | href: &str,
22 | mut search: stac::api::Search,
23 | max_items: Option,
24 | ) -> Result {
25 | if let Some(max_items) = max_items {
26 | search.limit = Some(max_items.try_into()?);
27 | } else {
28 | search.limit = None;
29 | };
30 | let client = Client::new()?;
31 | client.search(href, search)
32 | }
33 |
34 | /// A crate-specific result type.
35 | pub type Result = std::result::Result;
36 |
37 | /// Return this crate's version.
38 | ///
39 | /// # Examples
40 | ///
41 | /// ```
42 | /// println!("{}", stac_duckdb::version());
43 | /// ```
44 | pub fn version() -> &'static str {
45 | env!("CARGO_PKG_VERSION")
46 | }
47 |
--------------------------------------------------------------------------------
/scripts/validate-stac-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | #
3 | # Validate stac server using stac-api-validator.
4 | #
5 | # To use this script on macos, you'll need `timeout`, which is provided by Homebrew's coreutils:
6 | #
7 | # brew install coreutils
8 |
9 | set -e
10 |
11 | args="crates/server/data/sentinel-2/*"
12 | build_args="--no-default-features"
13 | conformance="--conformance core --conformance features --conformance item-search"
14 |
15 | if [ $# -eq 1 ]; then
16 | if [ "$1" = "--pgstac" ]; then
17 | args="$args --pgstac postgres://username:password@localhost/postgis"
18 | build_args="$build_args -F pgstac"
19 | conformance="$conformance --conformance filter"
20 | else
21 | echo "Unknown argument: $1"
22 | exit 1
23 | fi
24 | fi
25 |
26 | cargo build -p rustac $build_args
27 | cargo run -p rustac $build_args -- serve $args &
28 | server_pid=$!
29 | echo "server_pid=$server_pid"
30 | set +e
31 | scripts/wait-for-it.sh localhost:7822 && \
32 | stac-api-validator \
33 | --root-url http://localhost:7822 \
34 | $conformance \
35 | --collection sentinel-2-c1-l2a \
36 | --geometry '{"type":"Point","coordinates":[-105.07,40.08]}'
37 | status=$?
38 | set -e
39 | kill $server_pid
40 | exit $status
41 |
--------------------------------------------------------------------------------
/crates/server/data/joplin/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "joplin",
3 | "description": "This imagery was acquired by the NOAA Remote Sensing Division to support NOAA national security and emergency response requirements. In addition, it will be used for ongoing research efforts for testing and developing standards for airborne digital imagery. Individual images have been combined into a larger mosaic and tiled for distribution. The approximate ground sample distance (GSD) for each pixel is 35 cm (1.14 feet).",
4 | "stac_version": "1.0.0",
5 | "license": "public-domain",
6 | "links": [
7 | {
8 | "rel": "license",
9 | "href": "https://creativecommons.org/licenses/publicdomain/",
10 | "title": "public domain"
11 | }
12 | ],
13 | "type": "Collection",
14 | "extent": {
15 | "spatial": {
16 | "bbox": [
17 | [
18 | -94.6911621,
19 | 37.0332547,
20 | -94.402771,
21 | 37.1077651
22 | ]
23 | ]
24 | },
25 | "temporal": {
26 | "interval": [
27 | [
28 | "2000-02-01T00:00:00Z",
29 | "2000-02-12T00:00:00Z"
30 | ]
31 | ]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.0.0/provider.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/provider.json#",
4 | "title": "Provider Fields",
5 | "type": "object",
6 | "properties": {
7 | "providers": {
8 | "title": "Providers",
9 | "type": "array",
10 | "items": {
11 | "type": "object",
12 | "required": [
13 | "name"
14 | ],
15 | "properties": {
16 | "name": {
17 | "title": "Organization name",
18 | "type": "string",
19 | "minLength": 1
20 | },
21 | "description": {
22 | "title": "Organization description",
23 | "type": "string"
24 | },
25 | "roles": {
26 | "title": "Organization roles",
27 | "type": "array",
28 | "items": {
29 | "type": "string",
30 | "enum": [
31 | "producer",
32 | "licensor",
33 | "processor",
34 | "host"
35 | ]
36 | }
37 | },
38 | "url": {
39 | "title": "Organization homepage",
40 | "type": "string",
41 | "format": "iri"
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/provider.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/provider.json",
4 | "title": "Provider Fields",
5 | "type": "object",
6 | "properties": {
7 | "providers": {
8 | "title": "Providers",
9 | "type": "array",
10 | "items": {
11 | "type": "object",
12 | "required": [
13 | "name"
14 | ],
15 | "properties": {
16 | "name": {
17 | "title": "Organization name",
18 | "type": "string",
19 | "minLength": 1
20 | },
21 | "description": {
22 | "title": "Organization description",
23 | "type": "string"
24 | },
25 | "roles": {
26 | "title": "Organization roles",
27 | "type": "array",
28 | "items": {
29 | "type": "string",
30 | "enum": [
31 | "producer",
32 | "licensor",
33 | "processor",
34 | "host"
35 | ]
36 | }
37 | },
38 | "url": {
39 | "title": "Organization homepage",
40 | "type": "string",
41 | "format": "iri"
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/crates/wasm/src/lib.rs:
--------------------------------------------------------------------------------
1 | use arrow_array::RecordBatchIterator;
2 | use arrow_schema::ArrowError;
3 | use arrow_wasm::{Table, arrow_js::table::JSTable, error::WasmResult};
4 | use serde::Serialize;
5 | use serde_wasm_bindgen::Serializer;
6 | use stac::Item;
7 | use std::io::Cursor;
8 | use thiserror::Error;
9 | use wasm_bindgen::prelude::*;
10 |
11 | #[derive(Debug, Error)]
12 | #[non_exhaustive]
13 | pub enum Error {
14 | #[error(transparent)]
15 | Arrow(#[from] ArrowError),
16 | }
17 |
18 | #[wasm_bindgen(js_name = arrowToStacJson)]
19 | pub fn arrow_to_stac_json(table: JSTable) -> WasmResult {
20 | let table = Table::from_js(&table)?;
21 | let reader = RecordBatchIterator::new(
22 | table.record_batches().into_iter().map(From::from).map(Ok),
23 | table.schema().into(),
24 | );
25 | let items = stac::geoarrow::json::from_record_batch_reader(reader)?;
26 | let serializer = Serializer::json_compatible();
27 | let items = items.serialize(&serializer)?;
28 | Ok(items)
29 | }
30 |
31 | #[wasm_bindgen(js_name = stacJsonToParquet)]
32 | pub fn stac_json_to_parquet(value: JsValue) -> Result, JsError> {
33 | let items: Vec- = serde_wasm_bindgen::from_value(value)?;
34 | let mut cursor = Cursor::new(Vec::new());
35 | stac::geoparquet::into_writer(&mut cursor, items)?;
36 | Ok(cursor.into_inner())
37 | }
38 |
--------------------------------------------------------------------------------
/crates/wasm/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.1.0](https://github.com/stac-utils/rustac/compare/stac-wasm-v0.0.4...stac-wasm-v0.1.0) (2025-12-01)
4 |
5 |
6 | ### ⚠ BREAKING CHANGES
7 |
8 | * remove unused error enums ([#868](https://github.com/stac-utils/rustac/issues/868))
9 |
10 | ### Features
11 |
12 | * stac_wasm.stacJsonToParquet ([#786](https://github.com/stac-utils/rustac/issues/786)) ([6b1971a](https://github.com/stac-utils/rustac/commit/6b1971ae26aa8b80e1a68166cc180f2a6ae7f8ce))
13 | * wasm ([#744](https://github.com/stac-utils/rustac/issues/744)) ([db5cd21](https://github.com/stac-utils/rustac/commit/db5cd210d769ea04225c8bbbc08173663f584c36))
14 |
15 |
16 | ### Bug Fixes
17 |
18 | * pin arrow-wasm ([#785](https://github.com/stac-utils/rustac/issues/785)) ([8f9c28b](https://github.com/stac-utils/rustac/commit/8f9c28bb44d8372db8189d3adbf82238d14865fd))
19 | * remove the package lock ([#745](https://github.com/stac-utils/rustac/issues/745)) ([b3337f6](https://github.com/stac-utils/rustac/commit/b3337f63d3d4402550b0dfef05698f48fafd4077))
20 | * remove unused error enums ([#868](https://github.com/stac-utils/rustac/issues/868)) ([cf0e815](https://github.com/stac-utils/rustac/commit/cf0e815e03433e8ef219a79a67161174f3e99e84))
21 |
22 |
23 | ### Dependencies
24 |
25 | * The following workspace dependencies were updated
26 | * dependencies
27 | * stac bumped from 0.14.0 to 0.15.0
28 |
--------------------------------------------------------------------------------
/crates/io/data/items.ndjson:
--------------------------------------------------------------------------------
1 | {"type":"Feature","stac_version":"1.0.0","stac_extensions":["https://stac-extensions.github.io/projection/v1.1.0/schema.json","https://stac-extensions.github.io/raster/v1.1.0/schema.json"],"id":"dataset","geometry":null,"properties":{"datetime":"2024-09-07T14:41:31.917359Z","proj:shape":[2667,2658]},"links":[],"assets":{"data":{"href":"/Users/gadomski/Code/rustac/core/assets/dataset.tif","roles":["data"],"raster:bands":[{"data_type":"uint16"}]}}}
2 | {"type":"Feature","stac_version":"1.0.0","stac_extensions":["https://stac-extensions.github.io/projection/v1.1.0/schema.json","https://stac-extensions.github.io/raster/v1.1.0/schema.json"],"id":"dataset_geo","geometry":{"type":"Polygon","coordinates":[[[-61.2876244,72.229798],[-52.3015987,72.229798],[-52.3015987,90.0],[-61.2876244,90.0],[-61.2876244,72.229798]]],"bbox":[-61.2876244,72.229798,-52.3015987,90.0]},"bbox":[-61.2876244,72.229798,-52.3015987,90.0],"properties":{"datetime":"2024-09-07T14:41:31.917358Z","proj:epsg":32621,"proj:bbox":[373185.0,8019284.949381611,639014.9492102272,8286015.0],"proj:centroid":{"lat":73.4675736,"lon":-56.8079473},"proj:shape":[2667,2658],"proj:transform":[100.01126757344893,0.0,373185.0,0.0,-100.01126757344893,8286015.0]},"links":[],"assets":{"data":{"href":"/Users/gadomski/Code/rustac/core/assets/dataset_geo.tif","roles":["data"],"raster:bands":[{"data_type":"uint16","spatial_resolution":100.01126757344893}]}}}
3 |
--------------------------------------------------------------------------------
/crates/core/data/items.ndjson:
--------------------------------------------------------------------------------
1 | {"type":"Feature","stac_version":"1.0.0","stac_extensions":["https://stac-extensions.github.io/projection/v1.1.0/schema.json","https://stac-extensions.github.io/raster/v1.1.0/schema.json"],"id":"dataset","geometry":null,"properties":{"datetime":"2024-09-07T14:41:31.917359Z","proj:shape":[2667,2658]},"links":[],"assets":{"data":{"href":"/Users/gadomski/Code/rustac/core/assets/dataset.tif","roles":["data"],"raster:bands":[{"data_type":"uint16"}]}}}
2 | {"type":"Feature","stac_version":"1.0.0","stac_extensions":["https://stac-extensions.github.io/projection/v1.1.0/schema.json","https://stac-extensions.github.io/raster/v1.1.0/schema.json"],"id":"dataset_geo","geometry":{"type":"Polygon","coordinates":[[[-61.2876244,72.229798],[-52.3015987,72.229798],[-52.3015987,90.0],[-61.2876244,90.0],[-61.2876244,72.229798]]],"bbox":[-61.2876244,72.229798,-52.3015987,90.0]},"bbox":[-61.2876244,72.229798,-52.3015987,90.0],"properties":{"datetime":"2024-09-07T14:41:31.917358Z","proj:epsg":32621,"proj:bbox":[373185.0,8019284.949381611,639014.9492102272,8286015.0],"proj:centroid":{"lat":73.4675736,"lon":-56.8079473},"proj:shape":[2667,2658],"proj:transform":[100.01126757344893,0.0,373185.0,0.0,-100.01126757344893,8286015.0]},"links":[],"assets":{"data":{"href":"/Users/gadomski/Code/rustac/core/assets/dataset_geo.tif","roles":["data"],"raster:bands":[{"data_type":"uint16","spatial_resolution":100.01126757344893}]}}}
3 |
--------------------------------------------------------------------------------
/crates/core/README.md:
--------------------------------------------------------------------------------
1 | # stac
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/stac/latest/stac/)
5 | [](https://crates.io/crates/stac)
6 | 
7 | [](./CODE_OF_CONDUCT)
8 |
9 | Rust implementation of the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) specification.
10 |
11 | ## Usage
12 |
13 | To use the library in your project:
14 |
15 | ```toml
16 | [dependencies]
17 | stac = "0.13"
18 | ```
19 |
20 | ## Examples
21 |
22 | ```rust
23 | use stac::Item;
24 |
25 | // Creates an item from scratch.
26 | let item = Item::new("an-id");
27 |
28 | // Reads an item from the filesystem.
29 | let item: Item = stac::read("examples/simple-item.json").unwrap();
30 | ```
31 |
32 | Please see the [documentation](https://docs.rs/stac) for more usage examples.
33 |
34 | ## Other info
35 |
36 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
37 |
--------------------------------------------------------------------------------
/spec-examples/v1.0.0/catalog.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "examples",
3 | "type": "Catalog",
4 | "title": "Example Catalog",
5 | "stac_version": "1.0.0",
6 | "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.",
7 | "links": [
8 | {
9 | "rel": "root",
10 | "href": "./catalog.json",
11 | "type": "application/json"
12 | },
13 | {
14 | "rel": "child",
15 | "href": "./extensions-collection/collection.json",
16 | "type": "application/json",
17 | "title": "Collection Demonstrating STAC Extensions"
18 | },
19 | {
20 | "rel": "child",
21 | "href": "./collection-only/collection.json",
22 | "type": "application/json",
23 | "title": "Collection with no items (standalone)"
24 | },
25 | {
26 | "rel": "child",
27 | "href": "./collection-only/collection-with-schemas.json",
28 | "type": "application/json",
29 | "title": "Collection with no items (standalone with JSON Schemas)"
30 | },
31 | {
32 | "rel": "item",
33 | "href": "./collectionless-item.json",
34 | "type": "application/json",
35 | "title": "Collection with no items (standalone)"
36 | },
37 | {
38 | "rel": "self",
39 | "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/catalog.json",
40 | "type": "application/json"
41 | }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/spec-examples/v1.1.0/catalog.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "examples",
3 | "type": "Catalog",
4 | "title": "Example Catalog",
5 | "stac_version": "1.1.0",
6 | "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.",
7 | "links": [
8 | {
9 | "rel": "root",
10 | "href": "./catalog.json",
11 | "type": "application/json"
12 | },
13 | {
14 | "rel": "child",
15 | "href": "./extensions-collection/collection.json",
16 | "type": "application/json",
17 | "title": "Collection Demonstrating STAC Extensions"
18 | },
19 | {
20 | "rel": "child",
21 | "href": "./collection-only/collection.json",
22 | "type": "application/json",
23 | "title": "Collection with no items (standalone)"
24 | },
25 | {
26 | "rel": "child",
27 | "href": "./collection-only/collection-with-schemas.json",
28 | "type": "application/json",
29 | "title": "Collection with no items (standalone with JSON Schemas)"
30 | },
31 | {
32 | "rel": "item",
33 | "href": "./collectionless-item.json",
34 | "type": "application/json",
35 | "title": "Item that does not have a collection (not recommended, but allowed by the spec)"
36 | },
37 | {
38 | "rel": "self",
39 | "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.1.0/examples/catalog.json",
40 | "type": "application/json"
41 | }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build-docs:
10 | name: Build docs
11 | runs-on: ubuntu-latest
12 | env:
13 | GIT_COMMITTER_NAME: ci-bot
14 | GIT_COMMITTER_EMAIL: ci-bot@example.com
15 | steps:
16 | - uses: actions/checkout@v6
17 | - uses: astral-sh/setup-uv@v7
18 | - name: Sync
19 | run: uv sync --group docs
20 | - name: Build
21 | run: uv run mkdocs build
22 | - uses: actions/upload-pages-artifact@v4
23 | id: deployment
24 | with:
25 | path: site/
26 | deploy-docs:
27 | needs: build-docs
28 | name: Deploy docs
29 | permissions:
30 | pages: write
31 | id-token: write
32 | environment:
33 | name: github-pages
34 | url: ${{ steps.deployment.outputs.page_url }}
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/deploy-pages@v4
38 | id: deployment
39 | coverage:
40 | name: Coverage
41 | runs-on: ubuntu-latest
42 | steps:
43 | - uses: actions/checkout@v6
44 | - uses: Swatinem/rust-cache@v2
45 | - name: Install tarpaulin
46 | run: cargo install cargo-tarpaulin
47 | - name: Test w/ coverage
48 | run: cargo tarpaulin -p stac --all-features --out xml
49 | - uses: codecov/codecov-action@v5
50 | with:
51 | files: ./cobertura.xml
52 | token: ${{ secrets.CODECOV_TOKEN }}
53 | fail_ci_if_error: true
54 |
--------------------------------------------------------------------------------
/spec-examples/v1.1.0-beta.1/catalog.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "examples",
3 | "type": "Catalog",
4 | "title": "Example Catalog",
5 | "stac_version": "1.1.0-beta.1",
6 | "description": "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items.",
7 | "links": [
8 | {
9 | "rel": "root",
10 | "href": "./catalog.json",
11 | "type": "application/json"
12 | },
13 | {
14 | "rel": "child",
15 | "href": "./extensions-collection/collection.json",
16 | "type": "application/json",
17 | "title": "Collection Demonstrating STAC Extensions"
18 | },
19 | {
20 | "rel": "child",
21 | "href": "./collection-only/collection.json",
22 | "type": "application/json",
23 | "title": "Collection with no items (standalone)"
24 | },
25 | {
26 | "rel": "child",
27 | "href": "./collection-only/collection-with-schemas.json",
28 | "type": "application/json",
29 | "title": "Collection with no items (standalone with JSON Schemas)"
30 | },
31 | {
32 | "rel": "item",
33 | "href": "./collectionless-item.json",
34 | "type": "application/json",
35 | "title": "Item that does not have a collection (not recommended, but allowed by the spec)"
36 | },
37 | {
38 | "rel": "self",
39 | "href": "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.1.0-beta.1/examples/catalog.json",
40 | "type": "application/json"
41 | }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/crates/wasm/README.md:
--------------------------------------------------------------------------------
1 | # stac-wasm
2 |
3 | Converts [Arrow](https://arrow.apache.org/) arrays to [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org/) items, via [WebAssembly (WASM)](https://webassembly.org/).
4 |
5 | > [!WARNING]
6 | > This package is in an "alpha" state and will likely break and change a lot.
7 |
8 | ## Usage
9 |
10 | ```shell
11 | npm i stac-wasm
12 | ```
13 |
14 | We give you two functions:
15 |
16 | ```javascript
17 | import * as stac_wasm from "stac-wasm";
18 |
19 | const table = loadArrowTable(); // e.g. from DuckDB
20 | const items = stac_wasm.arrowToStacJson(table);
21 | const bytes = stac_wasm.stacJsonToParquet(items);
22 | ```
23 |
24 | ## Tests
25 |
26 | We don't have automated tests.
27 | If you want to play with the function, modify `www/index.js` and then:
28 |
29 | ```shell
30 | cd www
31 | npm run start
32 | ```
33 |
34 | This should open a page at that you can use to test out the WASM library.
35 |
36 | ## Contributing
37 |
38 | **stac-wasm** is part of [rustac](https://github.com/stac-utils/rustac), a monorepo that includes the Rust code used to build the WASM module.
39 | See [CONTRIBUTING.md](https://github.com/stac-utils/rustac/blob/main/CONTRIBUTING.md) for instructions on contributing to the monorepo.
40 | If your on MacOS, you might have to use **llvm** as described [in this comment](https://github.com/briansmith/ring/issues/1824#issuecomment-2059955073).
41 |
42 | ## Releasing
43 |
44 | ```shell
45 | wasm-pack build
46 | wasm-pack login
47 | cd pkg
48 | npm publish
49 | ```
50 |
--------------------------------------------------------------------------------
/crates/duckdb/README.md:
--------------------------------------------------------------------------------
1 | # pgstac
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/stac-duckdb/latest/stac_duckdb/)
5 | [](https://crates.io/crates/stac-duckdb)
6 | [](./CODE_OF_CONDUCT)
7 |
8 | Use [DuckDB](https://duckdb.org/) to search [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet).
9 |
10 | ## Usage
11 |
12 | ```shell
13 | cargo add stac-duckdb
14 | ```
15 |
16 | See the [documentation](https://docs.rs/stac-duckdb) for more.
17 |
18 | ## Bundling
19 |
20 | By default, DuckDB looks for its shared library on your system.
21 | Use `DUCKDB_LIB_DIR` and `DUCKDB_INCLUDE_DIR` to help it find those resources.
22 | If you want to build the DuckDB library as a part of this (or a downstream's) crate's build process, use the `bundled` feature.
23 | E.g. to test this crate if you don't have DuckDB locally:
24 |
25 | ```shell
26 | cargo test -p stac-duckdb -F bundled
27 | ```
28 |
29 | See [the duckdb-rs docs](https://github.com/duckdb/duckdb-rs?tab=readme-ov-file#notes-on-building-duckdb-and-libduckdb-sys) for more.
30 |
31 | ## Other info
32 |
33 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
34 |
--------------------------------------------------------------------------------
/spec-examples/v1.1.0/extensions-collection/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "extensions-collection",
3 | "type": "Collection",
4 | "stac_version": "1.1.0",
5 | "description": "A heterogeneous collection containing deeper examples of various extensions",
6 | "links": [
7 | {
8 | "rel": "parent",
9 | "href": "../catalog.json",
10 | "type": "application/json",
11 | "title": "Example Catalog"
12 | },
13 | {
14 | "rel": "root",
15 | "href": "../catalog.json",
16 | "type": "application/json",
17 | "title": "Example Catalog"
18 | },
19 | {
20 | "rel": "item",
21 | "href": "./proj-example/proj-example.json",
22 | "title": "Proj extension example"
23 | },
24 | {
25 | "rel": "license",
26 | "href": "https://remotedata.io/license.html",
27 | "title": "Remote Data License Terms"
28 | }
29 | ],
30 | "stac_extensions": [],
31 | "title": "Collection of Extension Items",
32 | "keywords": [
33 | "examples",
34 | "sar",
35 | "projection"
36 | ],
37 | "providers": [
38 | {
39 | "name": "Remote Data, Inc.",
40 | "roles": [
41 | "producer",
42 | "licensor"
43 | ],
44 | "url": "https://remotedata.io"
45 | }
46 | ],
47 | "extent": {
48 | "spatial": {
49 | "bbox": [
50 | [
51 | -180,
52 | -56,
53 | 180,
54 | 83
55 | ]
56 | ]
57 | },
58 | "temporal": {
59 | "interval": [
60 | [
61 | "2009-05-20T02:40:01.042784Z",
62 | "2018-11-03T23:59:55.112875Z"
63 | ]
64 | ]
65 | }
66 | },
67 | "license": "other"
68 | }
69 |
--------------------------------------------------------------------------------
/spec-examples/v1.0.0/extensions-collection/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "extensions-collection",
3 | "type": "Collection",
4 | "stac_version": "1.0.0",
5 | "description": "A heterogenous collection containing deeper examples of various extensions",
6 | "links": [
7 | {
8 | "rel": "parent",
9 | "href": "../catalog.json",
10 | "type": "application/json",
11 | "title": "Example Catalog"
12 | },
13 | {
14 | "rel": "root",
15 | "href": "../catalog.json",
16 | "type": "application/json",
17 | "title": "Example Catalog"
18 | },
19 | {
20 | "rel": "item",
21 | "href": "./proj-example/proj-example.json",
22 | "title": "Proj extension example"
23 | },
24 | {
25 | "rel": "license",
26 | "href": "https://remotedata.io/license.html",
27 | "title": "Remote Data License Terms"
28 | }
29 | ],
30 | "stac_extensions": [],
31 | "title": "Collection of Extension Items",
32 | "keywords": [
33 | "examples",
34 | "sar",
35 | "projection"
36 | ],
37 | "providers": [
38 | {
39 | "name": "Remote Data, Inc.",
40 | "roles": [
41 | "producer",
42 | "licensor"
43 | ],
44 | "url": "https://remotedata.io"
45 | }
46 | ],
47 | "extent": {
48 | "spatial": {
49 | "bbox": [
50 | [
51 | -180.0,
52 | -56.0,
53 | 180.0,
54 | 83.0
55 | ]
56 | ]
57 | },
58 | "temporal": {
59 | "interval": [
60 | [
61 | "2009-05-20T02:40:01.042784Z",
62 | "2018-11-03T23:59:55.112875Z"
63 | ]
64 | ]
65 | }
66 | },
67 | "license": "PDDL-1.0"
68 | }
69 |
--------------------------------------------------------------------------------
/spec-examples/v1.1.0-beta.1/extensions-collection/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "extensions-collection",
3 | "type": "Collection",
4 | "stac_version": "1.1.0-beta.1",
5 | "description": "A heterogeneous collection containing deeper examples of various extensions",
6 | "links": [
7 | {
8 | "rel": "parent",
9 | "href": "../catalog.json",
10 | "type": "application/json",
11 | "title": "Example Catalog"
12 | },
13 | {
14 | "rel": "root",
15 | "href": "../catalog.json",
16 | "type": "application/json",
17 | "title": "Example Catalog"
18 | },
19 | {
20 | "rel": "item",
21 | "href": "./proj-example/proj-example.json",
22 | "title": "Proj extension example"
23 | },
24 | {
25 | "rel": "license",
26 | "href": "https://remotedata.io/license.html",
27 | "title": "Remote Data License Terms"
28 | }
29 | ],
30 | "stac_extensions": [],
31 | "title": "Collection of Extension Items",
32 | "keywords": [
33 | "examples",
34 | "sar",
35 | "projection"
36 | ],
37 | "providers": [
38 | {
39 | "name": "Remote Data, Inc.",
40 | "roles": [
41 | "producer",
42 | "licensor"
43 | ],
44 | "url": "https://remotedata.io"
45 | }
46 | ],
47 | "extent": {
48 | "spatial": {
49 | "bbox": [
50 | [
51 | -180,
52 | -56,
53 | 180,
54 | 83
55 | ]
56 | ]
57 | },
58 | "temporal": {
59 | "interval": [
60 | [
61 | "2009-05-20T02:40:01.042784Z",
62 | "2018-11-03T23:59:55.112875Z"
63 | ]
64 | ]
65 | }
66 | },
67 | "license": "other"
68 | }
69 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/datetime.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/item-spec/json-schema/datetime.json",
4 | "title": "Date and Time Fields",
5 | "type": "object",
6 | "dependencies": {
7 | "start_datetime": {
8 | "required": [
9 | "end_datetime"
10 | ]
11 | },
12 | "end_datetime": {
13 | "required": [
14 | "start_datetime"
15 | ]
16 | }
17 | },
18 | "properties": {
19 | "datetime": {
20 | "title": "Date and Time",
21 | "description": "The searchable date/time of the data, in UTC (Formatted in RFC 3339) ",
22 | "type": ["string", "null"],
23 | "format": "date-time",
24 | "pattern": "(\\+00:00|Z)$"
25 | },
26 | "start_datetime": {
27 | "title": "Start Date and Time",
28 | "description": "The searchable start date/time of the data, in UTC (Formatted in RFC 3339) ",
29 | "type": "string",
30 | "format": "date-time",
31 | "pattern": "(\\+00:00|Z)$"
32 | },
33 | "end_datetime": {
34 | "title": "End Date and Time",
35 | "description": "The searchable end date/time of the data, in UTC (Formatted in RFC 3339) ",
36 | "type": "string",
37 | "format": "date-time",
38 | "pattern": "(\\+00:00|Z)$"
39 | },
40 | "created": {
41 | "title": "Creation Time",
42 | "type": "string",
43 | "format": "date-time",
44 | "pattern": "(\\+00:00|Z)$"
45 | },
46 | "updated": {
47 | "title": "Last Update Time",
48 | "type": "string",
49 | "format": "date-time",
50 | "pattern": "(\\+00:00|Z)$"
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.0.0/datetime.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#",
4 | "title": "Date and Time Fields",
5 | "type": "object",
6 | "dependencies": {
7 | "start_datetime": {
8 | "required": [
9 | "end_datetime"
10 | ]
11 | },
12 | "end_datetime": {
13 | "required": [
14 | "start_datetime"
15 | ]
16 | }
17 | },
18 | "properties": {
19 | "datetime": {
20 | "title": "Date and Time",
21 | "description": "The searchable date/time of the assets, in UTC (Formatted in RFC 3339) ",
22 | "type": ["string", "null"],
23 | "format": "date-time",
24 | "pattern": "(\\+00:00|Z)$"
25 | },
26 | "start_datetime": {
27 | "title": "Start Date and Time",
28 | "description": "The searchable start date/time of the assets, in UTC (Formatted in RFC 3339) ",
29 | "type": "string",
30 | "format": "date-time",
31 | "pattern": "(\\+00:00|Z)$"
32 | },
33 | "end_datetime": {
34 | "title": "End Date and Time",
35 | "description": "The searchable end date/time of the assets, in UTC (Formatted in RFC 3339) ",
36 | "type": "string",
37 | "format": "date-time",
38 | "pattern": "(\\+00:00|Z)$"
39 | },
40 | "created": {
41 | "title": "Creation Time",
42 | "type": "string",
43 | "format": "date-time",
44 | "pattern": "(\\+00:00|Z)$"
45 | },
46 | "updated": {
47 | "title": "Last Update Time",
48 | "type": "string",
49 | "format": "date-time",
50 | "pattern": "(\\+00:00|Z)$"
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/crates/core/data/bands-v1.0.0.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Feature",
3 | "stac_version": "1.0.0",
4 | "id": "bands-migration",
5 | "geometry": null,
6 | "properties": {
7 | "datetime": "2024-08-11T16:07:32.766181+00:00"
8 | },
9 | "links": [],
10 | "assets": {
11 | "example": {
12 | "href": "example.tif",
13 | "eo:bands": [
14 | {
15 | "name": "r",
16 | "common_name": "red"
17 | },
18 | {
19 | "name": "g",
20 | "common_name": "green"
21 | },
22 | {
23 | "name": "b",
24 | "common_name": "blue"
25 | },
26 | {
27 | "name": "nir",
28 | "common_name": "nir"
29 | }
30 | ],
31 | "raster:bands": [
32 | {
33 | "data_type": "uint16",
34 | "spatial_resolution": 10,
35 | "sampling": "area"
36 | },
37 | {
38 | "data_type": "uint16",
39 | "spatial_resolution": 10,
40 | "sampling": "area"
41 | },
42 | {
43 | "data_type": "uint16",
44 | "spatial_resolution": 10,
45 | "sampling": "area"
46 | },
47 | {
48 | "data_type": "uint16",
49 | "spatial_resolution": 30,
50 | "sampling": "area"
51 | }
52 | ]
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/crates/validate/src/schemas/v1.1.0/catalog.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://schemas.stacspec.org/v1.1.0/catalog-spec/json-schema/catalog.json",
4 | "title": "STAC Catalog Specification",
5 | "description": "This object represents Catalogs in a SpatioTemporal Asset Catalog.",
6 | "allOf": [
7 | {
8 | "$ref": "#/definitions/catalog"
9 | },
10 | {
11 | "$ref": "../../item-spec/json-schema/common.json"
12 | }
13 | ],
14 | "definitions": {
15 | "catalog": {
16 | "title": "STAC Catalog",
17 | "type": "object",
18 | "$comment": "title and description is validated through the common metadata.",
19 | "required": [
20 | "stac_version",
21 | "type",
22 | "id",
23 | "description",
24 | "links"
25 | ],
26 | "properties": {
27 | "stac_version": {
28 | "title": "STAC version",
29 | "type": "string",
30 | "const": "1.1.0"
31 | },
32 | "stac_extensions": {
33 | "title": "STAC extensions",
34 | "type": "array",
35 | "uniqueItems": true,
36 | "items": {
37 | "title": "Reference to a JSON Schema",
38 | "type": "string",
39 | "format": "iri"
40 | }
41 | },
42 | "type": {
43 | "title": "Type of STAC entity",
44 | "const": "Catalog"
45 | },
46 | "id": {
47 | "title": "Identifier",
48 | "type": "string",
49 | "minLength": 1
50 | },
51 | "links": {
52 | "$ref": "../../item-spec/json-schema/item.json#/definitions/links"
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/crates/server/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | /// A crate-specific error type.
4 | #[derive(Debug, Error)]
5 | #[non_exhaustive]
6 | pub enum Error {
7 | /// [bb8::RunError]
8 | #[cfg(feature = "pgstac")]
9 | #[error(transparent)]
10 | Bb8TokioPostgresRun(#[from] bb8::RunError),
11 |
12 | /// [bb8::RunError]
13 | #[cfg(feature = "duckdb")]
14 | #[error(transparent)]
15 | Bb8DuckdbRun(#[from] Box>),
16 |
17 | /// [stac_duckdb::Error]
18 | #[cfg(feature = "duckdb")]
19 | #[error(transparent)]
20 | StacDuckdb(#[from] stac_duckdb::Error),
21 |
22 | /// A memory backend error.
23 | #[error("memory backend error: {0}")]
24 | MemoryBackend(String),
25 |
26 | /// [pgstac::Error]
27 | #[cfg(feature = "pgstac")]
28 | #[error(transparent)]
29 | Pgstac(#[from] pgstac::Error),
30 |
31 | /// [serde_json::Error]
32 | #[error(transparent)]
33 | SerdeJson(#[from] serde_json::Error),
34 |
35 | /// [serde_urlencoded::ser::Error]
36 | #[error(transparent)]
37 | SerdeUrlencodedSer(#[from] serde_urlencoded::ser::Error),
38 |
39 | /// [stac::Error]
40 | #[error(transparent)]
41 | Stac(#[from] stac::Error),
42 |
43 | /// The backend is read-only.
44 | #[error("this backend is read-only")]
45 | ReadOnly,
46 |
47 | /// [tokio_postgres::Error]
48 | #[cfg(feature = "pgstac")]
49 | #[error(transparent)]
50 | TokioPostgres(#[from] tokio_postgres::Error),
51 |
52 | /// [std::num::TryFromIntError]
53 | #[error(transparent)]
54 | TryFromInt(#[from] std::num::TryFromIntError),
55 |
56 | /// [url::ParseError]
57 | #[error(transparent)]
58 | UrlParse(#[from] url::ParseError),
59 | }
60 |
--------------------------------------------------------------------------------
/crates/pgstac/src/page.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use serde_json::{Map, Value};
3 | use stac::Link;
4 | use stac::api::{Context, Item};
5 |
6 | /// A page of search results.
7 | #[derive(Debug, Deserialize, Serialize)]
8 | pub struct Page {
9 | /// These are the out features, usually STAC items, but maybe not legal STAC
10 | /// items if fields are excluded.
11 | pub features: Vec
- ,
12 |
13 | /// The next id.
14 | #[serde(skip_serializing_if = "Option::is_none")]
15 | pub next: Option,
16 |
17 | /// The previous id.
18 | #[serde(skip_serializing_if = "Option::is_none")]
19 | pub prev: Option,
20 |
21 | /// The search context.
22 | ///
23 | /// This was removed in pgstac v0.9
24 | #[serde(skip_serializing_if = "Option::is_none")]
25 | pub context: Option,
26 |
27 | /// The number of values returned.
28 | ///
29 | /// Added in pgstac v0.9
30 | #[serde(rename = "numberReturned", skip_serializing_if = "Option::is_none")]
31 | pub number_returned: Option,
32 |
33 | /// Links
34 | ///
35 | /// Added in pgstac v0.9
36 | #[serde(default, skip_serializing_if = "Vec::is_empty")]
37 | pub links: Vec,
38 |
39 | /// Additional fields.
40 | #[serde(flatten)]
41 | pub additional_fields: Map,
42 | }
43 |
44 | impl Page {
45 | /// Returns this page's next token, if it has one.
46 | pub fn next_token(&self) -> Option {
47 | self.next.as_ref().map(|next| format!("next:{next}"))
48 | }
49 |
50 | /// Returns this page's prev token, if it has one.
51 | pub fn prev_token(&self) -> Option {
52 | self.prev.as_ref().map(|prev| format!("prev:{prev}"))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/crates/io/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-io"
3 | version = "0.2.0"
4 | description = "Input and output (I/O) for the SpatioTemporal Asset Catalog (STAC)"
5 | authors.workspace = true
6 | edition.workspace = true
7 | homepage.workspace = true
8 | repository.workspace = true
9 | license.workspace = true
10 | categories.workspace = true
11 | rust-version.workspace = true
12 |
13 | [features]
14 | geoparquet = ["stac/geoparquet", "dep:parquet"]
15 | store = ["dep:object_store"]
16 | store-aws = ["store", "object_store/aws"]
17 | store-azure = ["store", "object_store/azure"]
18 | store-gcp = ["store", "object_store/gcp"]
19 | store-http = ["store", "object_store/http"]
20 | store-all = ["store-aws", "store-azure", "store-gcp", "store-http"]
21 |
22 | [dependencies]
23 | async-stream.workspace = true
24 | bytes.workspace = true
25 | fluent-uri = { workspace = true, optional = true }
26 | futures.workspace = true
27 | http.workspace = true
28 | jsonschema = { workspace = true, optional = true }
29 | object_store = { workspace = true, optional = true }
30 | parquet = { workspace = true, optional = true, features = ["arrow", "async", "object_store"] }
31 | reqwest = { workspace = true, features = ["json", "blocking"] }
32 | serde.workspace = true
33 | serde_json = { workspace = true, features = ["preserve_order"] }
34 | stac = { version = "0.15.0", path = "../core" }
35 | thiserror.workspace = true
36 | tokio.workspace = true
37 | tracing.workspace = true
38 | url.workspace = true
39 |
40 | [dev-dependencies]
41 | geojson.workspace = true
42 | mockito.workspace = true
43 | rstest.workspace = true
44 | tempfile.workspace = true
45 | tokio = { workspace = true, features = ["rt", "macros"] }
46 | tokio-test.workspace = true
47 |
48 | [[test]]
49 | name = "aws"
50 | required-features = ["store-aws"]
51 |
--------------------------------------------------------------------------------
/crates/pgstac/README.md:
--------------------------------------------------------------------------------
1 | # pgstac
2 |
3 | [](https://github.com/stac-utils/rustac/actions/workflows/ci.yml)
4 | [](https://docs.rs/pgstac/latest/pgstac/)
5 | [](https://crates.io/crates/pgstac)
6 | [](./CODE_OF_CONDUCT)
7 |
8 | Rust interface for [pgstac](https://github.com/stac-utils/pgstac).
9 |
10 | ## Usage
11 |
12 | In your `Cargo.toml`:
13 |
14 | ```toml
15 | [dependencies]
16 | pgstac = "0.3"
17 | ```
18 |
19 | See the [documentation](https://docs.rs/pgstac) for more.
20 |
21 | ## Testing
22 |
23 | **pgstac** needs a blank **pgstac** database for testing, so is not part of the default workspace build.
24 | To test:
25 |
26 | ```shell
27 | docker compose up -d
28 | cargo test -p pgstac
29 | docker compose down
30 | ```
31 |
32 | Each test is run in its own transaction, which is rolled back after the test.
33 |
34 | ### Customizing the test database connection
35 |
36 | By default, the tests will connect to the database at `postgresql://username:password@localhost:5432/postgis`.
37 | If you need to customize the connection information for whatever reason, set your `PGSTAC_RS_TEST_DB` environment variable:
38 |
39 | ```shell
40 | PGSTAC_RS_TEST_DB=postgresql://otherusername:otherpassword@otherhost:7822/otherdbname cargo test
41 | ```
42 |
43 | ## Other info
44 |
45 | This crate is part of the [rustac](https://github.com/stac-utils/rustac) monorepo, see its README for contributing and license information.
46 |
--------------------------------------------------------------------------------
/crates/server/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! A [STAC API](https://github.com/radiantearth/stac-api-spec) server written in Rust.
2 |
3 | #![deny(
4 | elided_lifetimes_in_paths,
5 | explicit_outlives_requirements,
6 | keyword_idents,
7 | macro_use_extern_crate,
8 | meta_variable_misuse,
9 | missing_abi,
10 | missing_debug_implementations,
11 | missing_docs,
12 | non_ascii_idents,
13 | noop_method_call,
14 | rust_2021_incompatible_closure_captures,
15 | rust_2021_incompatible_or_patterns,
16 | rust_2021_prefixes_incompatible_syntax,
17 | rust_2021_prelude_collisions,
18 | single_use_lifetimes,
19 | trivial_casts,
20 | trivial_numeric_casts,
21 | unreachable_pub,
22 | unsafe_code,
23 | unsafe_op_in_unsafe_fn,
24 | unused_crate_dependencies,
25 | unused_extern_crates,
26 | unused_import_braces,
27 | unused_lifetimes,
28 | unused_qualifications,
29 | unused_results,
30 | warnings
31 | )]
32 |
33 | mod api;
34 | mod backend;
35 | mod error;
36 | #[cfg(feature = "axum")]
37 | pub mod routes;
38 |
39 | pub use api::Api;
40 | #[cfg(feature = "duckdb")]
41 | pub use backend::DuckdbBackend;
42 | #[cfg(feature = "pgstac")]
43 | pub use backend::PgstacBackend;
44 | pub use backend::{Backend, MemoryBackend};
45 | pub use error::Error;
46 |
47 | /// A crate-specific result type.
48 | pub type Result = std::result::Result;
49 |
50 | /// The default catalog id.
51 | pub const DEFAULT_ID: &str = "stac-server-rs";
52 |
53 | /// The default catalog description.
54 | pub const DEFAULT_DESCRIPTION: &str = "A STAC API server written in Rust";
55 |
56 | /// The default limit.
57 | pub const DEFAULT_LIMIT: u64 = 10;
58 |
59 | #[cfg(test)]
60 | use tokio_test as _;
61 |
62 | #[cfg(all(test, not(feature = "axum")))]
63 | use tower as _;
64 |
--------------------------------------------------------------------------------
/crates/server/data/joplin/feature.geojson:
--------------------------------------------------------------------------------
1 | {
2 | "id": "f2cca2a3-288b-4518-8a3e-a4492bb60b08",
3 | "type": "Feature",
4 | "collection": "joplin",
5 | "links": [],
6 | "geometry": {
7 | "type": "Polygon",
8 | "coordinates": [
9 | [
10 | [
11 | -94.6884155,
12 | 37.0595608
13 | ],
14 | [
15 | -94.6884155,
16 | 37.0332547
17 | ],
18 | [
19 | -94.6554565,
20 | 37.0332547
21 | ],
22 | [
23 | -94.6554565,
24 | 37.0595608
25 | ],
26 | [
27 | -94.6884155,
28 | 37.0595608
29 | ]
30 | ]
31 | ]
32 | },
33 | "properties": {
34 | "proj:epsg": 3857,
35 | "orientation": "nadir",
36 | "height": 2500,
37 | "width": 2500,
38 | "datetime": "2000-02-02T00:00:00Z",
39 | "gsd": 0.5971642834779395
40 | },
41 | "assets": {
42 | "COG": {
43 | "type": "image/tiff; application=geotiff; profile=cloud-optimized",
44 | "href": "https://arturo-stac-api-test-data.s3.amazonaws.com/joplin/images/may24C350000e4102500n.tif",
45 | "title": "NOAA STORM COG"
46 | }
47 | },
48 | "bbox": [
49 | -94.6884155,
50 | 37.0332547,
51 | -94.6554565,
52 | 37.0595608
53 | ],
54 | "stac_extensions": [
55 | "https://stac-extensions.github.io/eo/v1.0.0/schema.json",
56 | "https://stac-extensions.github.io/projection/v1.0.0/schema.json"
57 | ],
58 | "stac_version": "1.0.0"
59 | }
60 |
--------------------------------------------------------------------------------
/crates/core/src/version.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use std::{convert::Infallible, fmt::Display, str::FromStr};
3 |
4 | /// A version of the STAC specification.
5 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, PartialOrd)]
6 | #[allow(non_camel_case_types)]
7 | #[non_exhaustive]
8 | pub enum Version {
9 | /// [v1.0.0](https://github.com/radiantearth/stac-spec/releases/tag/v1.0.0)
10 | #[serde(rename = "1.0.0")]
11 | v1_0_0,
12 |
13 | /// [v1.1.0-beta.1](https://github.com/radiantearth/stac-spec/releases/tag/v1.1.0-beta.1)
14 | #[serde(rename = "1.1.0-beta.1")]
15 | v1_1_0_beta_1,
16 |
17 | /// [v1.1.0](https://github.com/radiantearth/stac-spec/releases/tag/v1.1.0)
18 | #[serde(rename = "1.1.0")]
19 | v1_1_0,
20 |
21 | /// An unknown STAC version.
22 | #[serde(untagged)]
23 | Unknown(String),
24 | }
25 |
26 | impl FromStr for Version {
27 | type Err = Infallible;
28 |
29 | fn from_str(s: &str) -> Result {
30 | match s {
31 | "1.0.0" => Ok(Version::v1_0_0),
32 | "1.1.0-beta.1" => Ok(Version::v1_1_0_beta_1),
33 | "1.1.0" => Ok(Version::v1_1_0),
34 | _ => Ok(Version::Unknown(s.to_string())),
35 | }
36 | }
37 | }
38 |
39 | impl Display for Version {
40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 | write!(
42 | f,
43 | "{}",
44 | match self {
45 | Version::v1_0_0 => "1.0.0",
46 | Version::v1_1_0_beta_1 => "1.1.0-beta.1",
47 | Version::v1_1_0 => "1.1.0",
48 | Version::Unknown(v) => v,
49 | }
50 | )
51 | }
52 | }
53 |
54 | impl Default for Version {
55 | fn default() -> Self {
56 | crate::STAC_VERSION
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/crates/cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rustac"
3 | description = "Command line interface for rustac"
4 | version = "0.2.0"
5 | keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
6 | authors.workspace = true
7 | edition.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | rust-version.workspace = true
13 |
14 | [features]
15 | default = []
16 | pgstac = ["stac-server/pgstac"]
17 | duckdb-bundled = ["stac-duckdb/bundled"]
18 |
19 | [dependencies]
20 | anyhow.workspace = true
21 | async-stream.workspace = true
22 | axum.workspace = true
23 | clap = { workspace = true, features = ["derive"] }
24 | clap_complete.workspace = true
25 | futures-core.workspace = true
26 | futures-util.workspace = true
27 | serde_json.workspace = true
28 | stac = { version = "0.15.0", path = "../core" }
29 | stac-duckdb = { version = "0.3.0", path = "../duckdb" }
30 | stac-io = { version = "0.2.0", path = "../io", features = [
31 | "store-all",
32 | "geoparquet",
33 | ] }
34 | stac-server = { version = "0.4.0", path = "../server", features = ["axum", "duckdb"] }
35 | stac-validate = { version = "0.6.0", path = "../validate" }
36 | tokio = { workspace = true, features = [
37 | "macros",
38 | "io-std",
39 | "rt-multi-thread",
40 | "fs",
41 | ] }
42 | tracing.workspace = true
43 | tracing-indicatif.workspace = true
44 | tracing-subscriber = { workspace = true, features = ["env-filter"] }
45 | url.workspace = true
46 |
47 | [dev-dependencies]
48 | assert_cmd.workspace = true
49 | rstest.workspace = true
50 | tempfile.workspace = true
51 |
52 | [lib]
53 | crate-type = ["lib", "cdylib"]
54 |
55 | [[bin]]
56 | name = "rustac"
57 | path = "src/main.rs"
58 | doc = false
59 | test = false
60 |
61 | [package.metadata.docs.rs]
62 | all-features = true
63 | rustdoc-args = ["--cfg", "docsrs"]
64 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: rustac
2 | site_description: Command Line Interface (CLI) and Rust crates the SpatioTemporal Asset Catalog (STAC) specification.
3 | site_url: https://stac-utils.github.io/rustac/
4 | repo_url: https://github.com/stac-utils/rustac
5 | theme:
6 | name: material
7 | logo: img/rustac-notext.svg
8 | icon:
9 | repo: fontawesome/brands/github
10 | favicon: img/rustac-notext.svg
11 | features:
12 | - navigation.indexes
13 | - navigation.footer
14 | palette:
15 | scheme: stac
16 | primary: custom
17 |
18 | nav:
19 | - Home: index.md
20 | - Formats: formats.md
21 | - Command-line interface: cli.md
22 | - History: history.md
23 | - Pronunciation: pronunciation.md
24 |
25 | plugins:
26 | - search
27 | - social:
28 | cards_layout_options:
29 | color: rgb(26, 78, 99)
30 | background_color: rgb(228, 246, 251)
31 | - redirects:
32 | redirect_maps:
33 | python/index.md: https://www.gadom.ski/rustac-py/latest/
34 | python/example.md: https://www.gadom.ski/rustac-py/latest/example/
35 | python/api/index.md: https://www.gadom.ski/rustac-py/latest/api/
36 | python/api/migrate.md: https://www.gadom.ski/rustac-py/latest/api/migrate/
37 | python/api/read.md: https://www.gadom.ski/rustac-py/latest/api/read/
38 | python/api/search.md: https://www.gadom.ski/rustac-py/latest/api/search/
39 | python/api/version.md: https://www.gadom.ski/rustac-py/latest/api/version/
40 | python/api/write.md: https://www.gadom.ski/rustac-py/latest/api/write/
41 |
42 | markdown_extensions:
43 | - admonition
44 | - pymdownx.highlight:
45 | anchor_linenums: true
46 | line_spans: __span
47 | pygments_lang_class: true
48 | - pymdownx.inlinehilite
49 | - pymdownx.snippets
50 | - pymdownx.superfences
51 | - pymdownx.details
52 |
53 | extra_css:
54 | - stylesheets/extra.css
55 |
--------------------------------------------------------------------------------
/crates/validate/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [0.6.0](https://github.com/stac-utils/rustac/compare/stac-validate-v0.5.1...stac-validate-v0.6.0) (2025-12-01)
8 |
9 |
10 | ### ⚠ BREAKING CHANGES
11 |
12 | * remove unused error enums ([#868](https://github.com/stac-utils/rustac/issues/868))
13 |
14 | ### Bug Fixes
15 |
16 | * remove circular dev depependency ([#886](https://github.com/stac-utils/rustac/issues/886)) ([dcb9b49](https://github.com/stac-utils/rustac/commit/dcb9b496d4979984178b279c245e897621b9ca76))
17 | * remove unused error enums ([#868](https://github.com/stac-utils/rustac/issues/868)) ([cf0e815](https://github.com/stac-utils/rustac/commit/cf0e815e03433e8ef219a79a67161174f3e99e84))
18 |
19 |
20 | ### Dependencies
21 |
22 | * The following workspace dependencies were updated
23 | * dependencies
24 | * stac bumped from 0.14.0 to 0.15.0
25 | * dev-dependencies
26 | * stac-io bumped from 0.1.0 to 0.2.0
27 |
28 | ## [0.5.1] - 2025-11-14
29 |
30 | Update **stac** dependency.
31 |
32 | ## [0.5.0] - 2025-09-08
33 |
34 | ### Changed
35 |
36 | - Validation is now async ([#798](https://github.com/stac-utils/rustac/pull/798))
37 |
38 | ## [0.4.0] - 2025-07-10
39 |
40 | First release with a changelog.
41 |
42 | [unreleased]: https://github.com/stac-utils/rustac/compare/stac-validate-v0.5.1...main
43 | [0.5.1]: https://github.com/stac-utils/rustac/compare/stac-validate-v0.5.0...stac-validate-v0.5.1
44 | [0.5.0]: https://github.com/stac-utils/rustac/compare/stac-validate-v0.4.0...stac-validate-v0.5.0
45 | [0.4.0]: https://github.com/stac-utils/rustac/releases/tag/stac-validate-v0.4.0
46 |
47 |
48 |
--------------------------------------------------------------------------------
/crates/io/src/json.rs:
--------------------------------------------------------------------------------
1 | use crate::Result;
2 | use serde::Serialize;
3 | use stac::{FromJson, SelfHref, ToJson};
4 | use std::{fs::File, io::Read, path::Path};
5 |
6 | /// Create a STAC object from JSON.
7 | pub trait FromJsonPath: FromJson + SelfHref {
8 | /// Reads JSON data from a file.
9 | ///
10 | /// # Examples
11 | ///
12 | /// ```
13 | /// use stac::Item;
14 | /// use stac_io::FromJsonPath;
15 | ///
16 | /// let item = Item::from_json_path("examples/simple-item.json").unwrap();
17 | /// ```
18 | fn from_json_path(path: impl AsRef) -> Result {
19 | let path = path.as_ref();
20 | let mut buf = Vec::new();
21 | let _ = File::open(path)?.read_to_end(&mut buf)?;
22 | let mut value = Self::from_json_slice(&buf)?;
23 | value.set_self_href(path.to_string_lossy());
24 | Ok(value)
25 | }
26 | }
27 |
28 | pub trait ToJsonPath: ToJson {
29 | /// Writes a value to a path as JSON.
30 | ///
31 | /// # Examples
32 | ///
33 | /// ```no_run
34 | /// use stac::Item;
35 | /// use stac_io::ToJsonPath;
36 | ///
37 | /// Item::new("an-id").to_json_path("an-id.json", true).unwrap();
38 | /// ```
39 | fn to_json_path(&self, path: impl AsRef, pretty: bool) -> Result<()> {
40 | let file = File::create(path)?;
41 | self.to_json_writer(file, pretty)?;
42 | Ok(())
43 | }
44 | }
45 |
46 | impl FromJsonPath for T {}
47 | impl ToJsonPath for T {}
48 |
49 | #[cfg(test)]
50 | mod tests {
51 | use super::FromJsonPath;
52 | use stac::{Item, SelfHref};
53 |
54 | #[test]
55 | fn set_href() {
56 | let item = Item::from_json_path("examples/simple-item.json").unwrap();
57 | assert!(
58 | item.self_href()
59 | .unwrap()
60 | .ends_with("examples/simple-item.json")
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/crates/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "stac-server"
3 | description = "SpatioTemporal Asset Catalog (STAC) API server"
4 | version = "0.4.0"
5 | keywords = ["geospatial", "stac", "metadata", "geo", "server"]
6 | categories = ["science", "data-structures"]
7 | edition.workspace = true
8 | authors.workspace = true
9 | homepage.workspace = true
10 | repository.workspace = true
11 | license.workspace = true
12 | rust-version.workspace = true
13 |
14 | [features]
15 | axum = ["dep:axum", "dep:bytes", "dep:mime", "dep:tower-http"]
16 | duckdb = ["dep:stac-duckdb", "dep:bb8"]
17 | pgstac = [
18 | "dep:bb8",
19 | "dep:bb8-postgres",
20 | "dep:pgstac",
21 | "dep:rustls",
22 | "dep:tokio-postgres",
23 | "dep:tokio-postgres-rustls",
24 | ]
25 |
26 | [dependencies]
27 | axum = { workspace = true, optional = true }
28 | bb8 = { workspace = true, optional = true }
29 | bb8-postgres = { workspace = true, optional = true }
30 | bytes = { workspace = true, optional = true }
31 | http.workspace = true
32 | mime = { workspace = true, optional = true }
33 | pgstac = { version = "0.4.0", path = "../pgstac", optional = true }
34 | rustls = { workspace = true, optional = true }
35 | serde.workspace = true
36 | serde_json.workspace = true
37 | serde_urlencoded.workspace = true
38 | stac = { version = "0.15.0", path = "../core" }
39 | stac-duckdb = { version = "0.3.0", path = "../duckdb", optional = true }
40 | thiserror.workspace = true
41 | tokio-postgres = { workspace = true, optional = true }
42 | tokio-postgres-rustls = { workspace = true, optional = true }
43 | tower-http = { workspace = true, features = ["cors", "trace"], optional = true }
44 | tracing.workspace = true
45 | url.workspace = true
46 |
47 | [dev-dependencies]
48 | serde_json.workspace = true
49 | tokio = { workspace = true, features = ["macros"] }
50 | tokio-test.workspace = true
51 | tower = { workspace = true, features = ["util"] }
52 |
53 | [package.metadata.docs.rs]
54 | all-features = true
55 | rustdoc-args = ["--cfg", "docsrs"]
56 |
--------------------------------------------------------------------------------
/crates/core/src/json.rs:
--------------------------------------------------------------------------------
1 | use crate::{Error, Result};
2 | use serde::{Serialize, de::DeserializeOwned};
3 | use std::io::Write;
4 |
5 | /// Create a STAC object from JSON.
6 | pub trait FromJson: DeserializeOwned {
7 | /// Creates an object from JSON bytes.
8 | ///
9 | /// # Examples
10 | ///
11 | /// ```
12 | /// use std::{fs::File, io::Read};
13 | /// use stac::{Item, FromJson};
14 | ///
15 | /// let mut buf = Vec::new();
16 | /// File::open("examples/simple-item.json").unwrap().read_to_end(&mut buf).unwrap();
17 | /// let item = Item::from_json_slice(&buf).unwrap();
18 | /// ```
19 | fn from_json_slice(slice: &[u8]) -> Result {
20 | serde_json::from_slice(slice).map_err(Error::from)
21 | }
22 | }
23 |
24 | /// Writes a STAC object to JSON bytes.
25 | pub trait ToJson: Serialize {
26 | /// Writes a value as JSON.
27 | ///
28 | /// # Examples
29 | ///
30 | /// ```
31 | /// use stac::{ToJson, Item};
32 | ///
33 | /// let mut buf = Vec::new();
34 | /// Item::new("an-id").to_json_writer(&mut buf, true).unwrap();
35 | /// ```
36 | fn to_json_writer(&self, writer: impl Write, pretty: bool) -> Result<()> {
37 | if pretty {
38 | serde_json::to_writer_pretty(writer, self).map_err(Error::from)
39 | } else {
40 | serde_json::to_writer(writer, self).map_err(Error::from)
41 | }
42 | }
43 |
44 | /// Writes a value as JSON bytes.
45 | ///
46 | /// # Examples
47 | ///
48 | /// ```
49 | /// use stac::{ToJson, Item};
50 | ///
51 | /// Item::new("an-id").to_json_vec(true).unwrap();
52 | /// ```
53 | fn to_json_vec(&self, pretty: bool) -> Result> {
54 | if pretty {
55 | serde_json::to_vec_pretty(self).map_err(Error::from)
56 | } else {
57 | serde_json::to_vec(self).map_err(Error::from)
58 | }
59 | }
60 | }
61 |
62 | impl FromJson for T {}
63 | impl ToJson for T {}
64 |
--------------------------------------------------------------------------------
/crates/extensions/data/auth/item.json:
--------------------------------------------------------------------------------
1 | {
2 | "stac_version": "1.0.0",
3 | "stac_extensions": [
4 | "https://stac-extensions.github.io/authentication/v1.1.0/schema.json"
5 | ],
6 | "type": "Feature",
7 | "id": "item",
8 | "bbox": [
9 | 172.9,
10 | 1.3,
11 | 173,
12 | 1.4
13 | ],
14 | "geometry": {
15 | "type": "Polygon",
16 | "coordinates": [
17 | [
18 | [
19 | 172.9,
20 | 1.3
21 | ],
22 | [
23 | 173,
24 | 1.3
25 | ],
26 | [
27 | 173,
28 | 1.4
29 | ],
30 | [
31 | 172.9,
32 | 1.4
33 | ],
34 | [
35 | 172.9,
36 | 1.3
37 | ]
38 | ]
39 | ]
40 | },
41 | "properties": {
42 | "datetime": "2020-12-11T22:38:32Z",
43 | "auth:schemes": {
44 | "oauth": {
45 | "type": "oauth2",
46 | "description": "requires a login and user token",
47 | "flows": {
48 | "authorizationCode": {
49 | "authorizationUrl": "https://example.com/oauth/authorize",
50 | "tokenUrl": "https://example.com/oauth/token",
51 | "scopes": {
52 | "read:example": "Read the example data",
53 | "write:example": "Write the example data",
54 | "admin:example": "Read/write/delete the example data"
55 | }
56 | }
57 | }
58 | },
59 | "none": {
60 | "type": "http",
61 | "scheme": "basic",
62 | "description": "Free access without restrictions"
63 | }
64 | }
65 | },
66 | "links": [
67 | {
68 | "href": "https://example.com/examples/item.json",
69 | "rel": "self"
70 | }
71 | ],
72 | "assets": {
73 | "data": {
74 | "href": "https://example.com/examples/file.xyz",
75 | "title": "Secure Asset Example",
76 | "type": "application/vnd.example",
77 | "roles": [
78 | "data"
79 | ],
80 | "auth:refs": [
81 | "oauth"
82 | ]
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/crates/io/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | /// Crate-specific error enum
4 | #[derive(Error, Debug)]
5 | #[non_exhaustive]
6 | pub enum Error {
7 | /// Returned when unable to read a STAC value from a path.
8 | #[error("{io}: {path}")]
9 | FromPath {
10 | /// The [std::io::Error]
11 | #[source]
12 | io: std::io::Error,
13 |
14 | /// The path.
15 | path: String,
16 | },
17 |
18 | /// [http::header::InvalidHeaderName]
19 | #[error(transparent)]
20 | InvalidHeaderName(#[from] http::header::InvalidHeaderName),
21 |
22 | /// [http::header::InvalidHeaderValue]
23 | #[error(transparent)]
24 | InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
25 |
26 | /// [http::method::InvalidMethod]
27 | #[error(transparent)]
28 | InvalidMethod(#[from] http::method::InvalidMethod),
29 |
30 | /// [tokio::task::JoinError]
31 | #[error(transparent)]
32 | Join(#[from] tokio::task::JoinError),
33 |
34 | /// [std::io::Error]
35 | #[error(transparent)]
36 | Io(#[from] std::io::Error),
37 |
38 | #[cfg(feature = "store")]
39 | #[error(transparent)]
40 | /// [object_store::Error]
41 | ObjectStore(#[from] object_store::Error),
42 |
43 | #[cfg(feature = "geoparquet")]
44 | #[error(transparent)]
45 | /// [parquet::errors::ParquetError]
46 | Parquet(#[from] parquet::errors::ParquetError),
47 |
48 | #[error(transparent)]
49 | /// [reqwest::Error]
50 | Reqwest(#[from] reqwest::Error),
51 |
52 | #[error(transparent)]
53 | /// [serde_json::Error]
54 | SerdeJson(#[from] serde_json::Error),
55 |
56 | #[error(transparent)]
57 | /// [stac::Error]
58 | Stac(#[from] stac::Error),
59 |
60 | /// [std::num::TryFromIntError]
61 | #[error(transparent)]
62 | TryFromInt(#[from] std::num::TryFromIntError),
63 |
64 | /// Unsupported file format.
65 | #[error("unsupported format: {0}")]
66 | UnsupportedFormat(String),
67 |
68 | /// [url::ParseError]
69 | #[error(transparent)]
70 | UrlParse(#[from] url::ParseError),
71 | }
72 |
--------------------------------------------------------------------------------
/crates/core/src/datetime.rs:
--------------------------------------------------------------------------------
1 | //! Datetime utilities.
2 |
3 | use crate::{Error, Result};
4 | use chrono::{DateTime, FixedOffset};
5 |
6 | /// A start and end datetime.
7 | pub type Interval = (Option>, Option>);
8 |
9 | /// Parse a datetime or datetime interval into a start and end datetime.
10 | ///
11 | /// Returns `None` to indicate an open interval.
12 | ///
13 | /// # Examples
14 | ///
15 | /// ```
16 | /// let (start, end) = stac::datetime::parse("2023-07-11T12:00:00Z/..").unwrap();
17 | /// assert!(start.is_some());
18 | /// assert!(end.is_none());
19 | /// ```
20 | pub fn parse(datetime: &str) -> Result {
21 | if datetime.contains('/') {
22 | let mut iter = datetime.split('/');
23 | let start = iter
24 | .next()
25 | .ok_or_else(|| Error::InvalidDatetime(datetime.to_string()))
26 | .and_then(parse_one)?;
27 | let end = iter
28 | .next()
29 | .ok_or_else(|| Error::InvalidDatetime(datetime.to_string()))
30 | .and_then(parse_one)?;
31 | if iter.next().is_some() {
32 | return Err(Error::InvalidDatetime(datetime.to_string()));
33 | }
34 | Ok((start, end))
35 | } else if datetime == ".." {
36 | Err(Error::InvalidDatetime(datetime.to_string()))
37 | } else {
38 | let datetime = DateTime::parse_from_rfc3339(datetime).map(Some)?;
39 | Ok((datetime, datetime))
40 | }
41 | }
42 |
43 | fn parse_one(s: &str) -> Result