diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml deleted file mode 100644 index 27bee698..00000000 --- a/.github/workflows/api.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Api - -on: - pull_request: - paths: - - api/** - push: - paths: - - api/** - branches: - - main - workflow_dispatch: - -defaults: - run: - working-directory: api - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-api: - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test --all-features diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24d26be7..e330cd9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,90 +15,67 @@ env: CARGO_TERM_VERBOSE: true jobs: - test: - runs-on: ubuntu-latest + test-core: + name: Test stac + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - name: Test - run: cargo test --lib - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Fmt - run: cargo fmt - - name: Clippy - run: cargo clippy --all --all-features - check: + run: cargo test -p stac --all-features + test-api: + name: Test stac-api runs-on: ubuntu-latest - strategy: - matrix: - chunk: - - 1 - - 2 - - 3 steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Install check-all-features - run: cargo install cargo-all-features - - name: Check - run: cargo check-all-features --n-chunks 3 --chunk ${{ matrix.chunk }} - check-nightly: + - name: Test + run: cargo test -p stac-api --all-features + test-cli: + name: Test stac-cli runs-on: ubuntu-latest + defaults: + run: + working-directory: cli steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Check - run: cargo check --all --all-features - doc: + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Test + run: cargo test -F duckdb/bundled --all-features + - name: Install + run: uv sync + - name: Smoke test + run: uv run stacrs --version + test-duckdb: + name: Test stac-duckdb runs-on: ubuntu-latest - env: - RUSTDOCFLAGS: -Dwarnings steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Doc - run: cargo doc --all-features - validate-stac-server: + - name: Test + run: cargo test -p stac-duckdb -F duckdb/bundled + test-extensions: + name: Test stac-extensions runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - uses: astral-sh/setup-uv@v3 - with: - enable-cache: true - - name: Install Python dependencies - run: uv sync --extra stac-api-validator - - name: Validate - run: uv run scripts/validate-stac-server - validate-stac-server-pgstac: + - name: Test + run: cargo test -p stac-extensions + test-pgstac: + name: Test pgstac runs-on: ubuntu-latest services: pgstac: @@ -121,11 +98,99 @@ jobs: - uses: astral-sh/setup-uv@v3 with: enable-cache: true + - name: Test + run: cargo test -p pgstac - name: Install Python dependencies run: uv sync --extra stac-api-validator - name: Validate run: uv run scripts/validate-stac-server --pgstac + test-python: + name: Test stacrs (python) + runs-on: ubuntu-latest + defaults: + run: + working-directory: python + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Install + run: uv sync + - name: Check + run: uv run ruff check && uv run ruff format --check && uv run mypy . + - name: Test + run: uv run pytest + test-server: + name: Test stac-server + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Test + run: cargo test -p stac-server --all-features + - name: Install Python dependencies + run: uv sync --extra stac-api-validator + - name: Validate + run: uv run scripts/validate-stac-server + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - name: Fmt + run: cargo fmt + - name: Clippy + run: cargo clippy --all --all-features + check-feature-combinations: + name: Check feature combinations + runs-on: ubuntu-latest + strategy: + matrix: + chunk: + - 1 + - 2 + - 3 + - 4 + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - name: Install check-all-features + run: cargo install cargo-all-features + - name: Check + run: cargo check-all-features --n-chunks 4 --chunk ${{ matrix.chunk }} + check-nightly: + name: Check (nightly) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + - name: Check + run: cargo check --workspace --all-features + doc: + name: Docs + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - name: Doc + run: cargo doc --workspace --all-features validate-stac-geoparquet: + name: Validate stac-geoparquet runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml deleted file mode 100644 index 2aaa9566..00000000 --- a/.github/workflows/cli.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Cli - -on: - pull_request: - paths: - - cli/** - push: - branches: - - main - paths: - - cli/** - workflow_dispatch: - -defaults: - run: - working-directory: cli - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-cli: - strategy: - matrix: - os: - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test --no-default-features -F pgstac -F python - test-cli-with-gdal: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Test - run: cargo test --no-default-features -F gdal - test-cli-with-duckdb: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test --no-default-features -F duckdb -F duckdb/bundled diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml deleted file mode 100644 index c1c35983..00000000 --- a/.github/workflows/core.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Core - -on: - pull_request: - paths: - - core/** - push: - branches: - - main - paths: - - core/** - workflow_dispatch: -defaults: - run: - working-directory: core - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-core: - strategy: - matrix: - os: - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test -F geo -F geoparquet-compression -F reqwest -F object-store-all -F validate-blocking - test-core-with-gdal: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - name: Install GDAL - run: | - sudo apt-get update - sudo apt-get install libgdal-dev - - name: Test - run: cargo test --all-features diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 97e6b269..e4f8b783 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,5 +30,5 @@ jobs: run: uv sync --extra docs && uv sync --no-dev --inexact --project stacrs - name: Deploy run: | - VERSION=$(git describe --tags --match="v*" --abbrev=0) + VERSION=$(git describe --tags --match="python-v*" --abbrev=0) uv run mike deploy $VERSION latest --update-aliases --push diff --git a/.github/workflows/duckdb.yml b/.github/workflows/duckdb.yml deleted file mode 100644 index ec8ac8ab..00000000 --- a/.github/workflows/duckdb.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Duckdb - -on: - pull_request: - paths: - - duckdb/** - push: - branches: - - main - paths: - - duckdb/** - workflow_dispatch: - -defaults: - run: - working-directory: duckdb - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-duckdb: - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test -F duckdb/bundled diff --git a/.github/workflows/pgstac.yml b/.github/workflows/pgstac.yml deleted file mode 100644 index c4455106..00000000 --- a/.github/workflows/pgstac.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Pgstac - -on: - pull_request: - paths: - - pgstac/** - push: - branches: - - main - paths: - - pgstac/** - workflow_dispatch: - -defaults: - run: - working-directory: pgstac - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-pgstac: - runs-on: ubuntu-latest - services: - pgstac: - image: ghcr.io/stac-utils/pgstac:v0.8.6 - env: - POSTGRES_USER: username - POSTGRES_PASSWORD: password - POSTGRES_DB: postgis - PGUSER: username - PGPASSWORD: password - PGDATABASE: postgis - ports: - - 5432:5432 - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test diff --git a/.github/workflows/python-cli.yml b/.github/workflows/python-cli.yml index 8a6678b1..d65f945e 100644 --- a/.github/workflows/python-cli.yml +++ b/.github/workflows/python-cli.yml @@ -3,10 +3,7 @@ name: Python CLI on: push: tags: - - 'stac-cli-*' - pull_request: - paths: - - cli/** + - "stac-cli-*" workflow_dispatch: permissions: @@ -17,22 +14,6 @@ concurrency: cancel-in-progress: true jobs: - test-stacrs-cli: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: Install dev requirements - run: pip install maturin - - name: Build - run: maturin build --manifest-path cli/Cargo.toml --out dist - - name: Install stacrs-cli - run: pip install stacrs-cli --find-links dist --no-index - - name: Smoke test - run: stacrs --version linux: runs-on: ${{ matrix.platform.runner }} strategy: @@ -62,7 +43,7 @@ jobs: with: target: ${{ matrix.platform.target }} args: --release --out dist --find-interpreter --manifest-path cli/Cargo.toml - sccache: 'true' + sccache: "true" - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -122,7 +103,7 @@ jobs: with: target: ${{ matrix.platform.target }} args: --release --out dist --find-interpreter --manifest-path cli/Cargo.toml - sccache: 'true' + sccache: "true" - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -150,7 +131,7 @@ jobs: with: target: ${{ matrix.platform.target }} args: --release --out dist --find-interpreter --manifest-path cli/Cargo.toml - sccache: 'true' + sccache: "true" - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -178,13 +159,11 @@ jobs: name: Release runs-on: ubuntu-latest needs: - - test-stacrs-cli - linux # - musllinux - windows - macos - sdist - if: startsWith(github.ref, 'refs/tags/stac-cli-') environment: name: pypi url: https://pypi.org/p/stacrs-cli diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a91258a7..0d7f51c9 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -4,9 +4,6 @@ on: push: tags: - "python-*" - pull_request: - paths: - - python/** workflow_dispatch: permissions: @@ -17,29 +14,8 @@ concurrency: cancel-in-progress: true jobs: - test-stacrs: - runs-on: ubuntu-latest - defaults: - run: - working-directory: python - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - - uses: astral-sh/setup-uv@v3 - with: - enable-cache: true - - name: Install - run: uv sync - - name: Check - run: uv run ruff check && uv run ruff format --check && uv run mypy . - - name: Test - run: uv run pytest linux: runs-on: ${{ matrix.platform.runner }} - if: startsWith(github.ref, 'refs/tags/python-') strategy: matrix: platform: @@ -107,7 +83,6 @@ jobs: windows: runs-on: ${{ matrix.platform.runner }} - if: startsWith(github.ref, 'refs/tags/python-') strategy: matrix: platform: @@ -137,7 +112,6 @@ jobs: macos: runs-on: ${{ matrix.platform.runner }} - if: startsWith(github.ref, 'refs/tags/python-') strategy: matrix: platform: @@ -185,13 +159,11 @@ jobs: name: Release runs-on: ubuntu-latest needs: - - test-stacrs - linux # - musllinux - windows - macos - sdist - if: startsWith(github.ref, 'refs/tags/python-') environment: name: pypi url: https://pypi.org/p/stacrs diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml deleted file mode 100644 index c19feca6..00000000 --- a/.github/workflows/server.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Server - -on: - pull_request: - paths: - - server/** - push: - branches: - - main - paths: - - server/** - workflow_dispatch: - -defaults: - run: - working-directory: server - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_TERM_VERBOSE: true - -jobs: - test-server: - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Rust cache - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test -F axum -F pgstac diff --git a/Cargo.toml b/Cargo.toml index 98dda548..9a78ba39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,25 @@ [workspace] resolver = "2" -members = ["api", "cli", "core", "duckdb", "pgstac", "python", "server"] -default-members = ["api", "cli", "core", "server"] +members = [ + "api", + "cli", + "core", + "duckdb", + "extensions", + "pgstac", + "python", + "server", +] +default-members = ["api", "cli", "core", "extensions"] + +[workspace.package] +authors = ["Pete Gadomski "] +edition = "2021" +homepage = "https://stac-utils.github.io/stac-rs" +repository = "https://github.com/stac-utils/stac-rs" +license = "MIT OR Apache-2.0" +categories = ["science", "data-structures"] +rust-version = "1.75" [workspace.dependencies] arrow = "52.2" @@ -21,8 +39,6 @@ clap = "4.5" duckdb = "=1.0.0" fluent-uri = "0.3.1" futures = "0.3.31" -gdal = "0.17.1" -gdal-sys = "0.10.0" geo = "0.28.0" geo-types = "0.7.13" geoarrow = "0.3.0" diff --git a/api/Cargo.toml b/api/Cargo.toml index 35102194..3db5ee2d 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "stac-api" -version = "0.6.0" -authors = ["Pete Gadomski "] -edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) API specification" -homepage = "https://github.com/stac-utils/stac-rs" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" -keywords = ["geospatial", "stac", "metadata", "geo", "raster"] +version = "0.6.0" +keywords = ["geospatial", "stac", "metadata", "geo", "api"] categories = ["science", "data-structures", "web-programming"] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +rust-version.workspace = true [features] client = [ @@ -22,25 +23,25 @@ geo = ["dep:geo", "stac/geo"] [dependencies] async-stream = { workspace = true, optional = true } -chrono = { workspace = true } +chrono.workspace = true futures = { workspace = true, optional = true } http = { workspace = true, optional = true } reqwest = { workspace = true, features = ["json"], optional = true } geo = { workspace = true, optional = true } -geojson = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_urlencoded = { workspace = true } -stac = { workspace = true } -thiserror = { workspace = true } +geojson.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_urlencoded.workspace = true +stac.workspace = true +thiserror.workspace = true tokio = { workspace = true, optional = true } -url = { workspace = true } +url.workspace = true [dev-dependencies] -geojson = { workspace = true } -mockito = { workspace = true } +geojson.workspace = true +mockito.workspace = true tokio = { workspace = true, features = ["rt", "macros"] } -tokio-test = { workspace = true } +tokio-test.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 06c191e5..18ead7d8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,33 +1,32 @@ [package] name = "stac-cli" -version = "0.4.0" -edition = "2021" description = "Command line interface for stac-rs" -documentation = "https://docs.rs/stac-cli" -readme = "README.md" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" +version = "0.4.0" keywords = ["geospatial", "stac", "metadata", "geo", "raster"] -categories = ["science", "data-structures"] -rust-version = "1.75" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +categories.workspace = true +rust-version.workspace = true [features] -default = ["gdal", "pgstac"] +default = ["pgstac"] duckdb = ["dep:stac-duckdb", "dep:duckdb"] -gdal = ["stac/gdal"] pgstac = ["stac-server/pgstac", "dep:pgstac"] python = ["dep:pyo3", "pgstac"] [dependencies] -axum = { workspace = true } +axum.workspace = true clap = { workspace = true, features = ["derive"] } duckdb = { workspace = true, optional = true } # We have this dependency only to allow us to bundle it -object_store = { workspace = true } +object_store.workspace = true pgstac = { workspace = true, optional = true } pyo3 = { workspace = true, optional = true } -reqwest = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true stac = { workspace = true, features = [ "geoparquet-compression", "object-store-all", @@ -37,21 +36,21 @@ stac = { workspace = true, features = [ stac-api = { workspace = true, features = ["client"] } stac-duckdb = { workspace = true, optional = true } stac-server = { workspace = true, features = ["axum"] } -thiserror = { workspace = true } +thiserror.workspace = true tokio = { workspace = true, features = [ "macros", "io-std", "rt-multi-thread", "fs", ] } -tokio-stream = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -url = { workspace = true } +tokio-stream.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +url.workspace = true [dev-dependencies] -assert_cmd = { workspace = true } -tokio-test = { workspace = true } +assert_cmd.workspace = true +tokio-test.workspace = true [lib] crate-type = ["lib", "cdylib"] diff --git a/cli/README.md b/cli/README.md index 111348e0..dc01063d 100644 --- a/cli/README.md +++ b/cli/README.md @@ -10,17 +10,14 @@ Command Line Interface (CLI) for [STAC](https://stacspec.org/), named `stacrs`. ## Installation -```sh -cargo install stac-cli +```shell +python -m pip install stacrs-cli ``` Or: -```shell -# NOTE: The version from PyPI does not include GDAL or DuckDB support. If you -# need to use these features, install via `cargo install` (GDAL is enabled by -# default) or `cargo install -F duckdb` (DuckDB is not). -python -m pip install stacrs-cli +```sh +cargo install stac-cli ``` Then: @@ -44,15 +41,14 @@ Use the `--help` flag to see all available options for the CLI and the subcomman ## Features -This crate has five features, three of them on by default: +This crate has features: - `duckdb`: experimental support for querying [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet) files using [DuckDB](https://duckdb.org/) -- `gdal`: read geospatial data from rasters (enabled by default) - `geoparquet`: read and write [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet) (enabled by default) - `pgstac`: enable a [pgstac](https://github.com/stac-utils/pgstac) backend for `stacrs serve` (enabled by default) - `python`: create an entrypoint that can be called from Python (used to enable `python -m pip install stacrs-cli`) -If you don't want to use GDAL or any of the other default features: +If you don't want to use any of the default features: ```shell cargo install stac-cli --no-default-features diff --git a/cli/src/args/item.rs b/cli/src/args/item.rs index 510d3a4b..e344dd2e 100644 --- a/cli/src/args/item.rs +++ b/cli/src/args/item.rs @@ -22,11 +22,6 @@ pub(crate) struct Args { #[arg(short, long = "role", default_values_t = ["data".to_string()])] pub(crate) roles: Vec, - /// Don't use GDAL to add geospatial metadata to the item - #[cfg(feature = "gdal")] - #[arg(long)] - pub(crate) disable_gdal: bool, - /// Allow assets to have relative hrefs #[arg(long)] pub(crate) allow_relative_hrefs: bool, @@ -48,10 +43,6 @@ impl Run for Args { }) .unwrap_or_else(|| "default".to_string()); let mut builder = Builder::new(id).canonicalize_paths(!self.allow_relative_hrefs); - #[cfg(feature = "gdal")] - { - builder = builder.enable_gdal(!self.disable_gdal); - } if let Some(href) = href { let mut asset = Asset::new(href); asset.roles = self.roles; diff --git a/cli/src/args/items.rs b/cli/src/args/items.rs index 1e12661d..460adc92 100644 --- a/cli/src/args/items.rs +++ b/cli/src/args/items.rs @@ -20,11 +20,6 @@ pub(crate) struct Args { #[arg(short, long = "role", default_values_t = ["data".to_string()])] roles: Vec, - /// Don't use GDAL to add geospatial metadata to the item - #[cfg(feature = "gdal")] - #[arg(long)] - disable_gdal: bool, - /// Allow assets to have relative hrefs #[arg(long)] allow_relative_hrefs: bool, @@ -42,8 +37,6 @@ impl Run for Args { outfile: None, key: self.key.clone(), roles: self.roles.clone(), - #[cfg(feature = "gdal")] - disable_gdal: self.disable_gdal, allow_relative_hrefs: self.allow_relative_hrefs, }; let _ = join_set.spawn(async move { args.run(input, sender).await }); diff --git a/core/Cargo.toml b/core/Cargo.toml index 536f299b..437218d6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "stac" -version = "0.10.2" -authors = ["Pete Gadomski "] -edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) specification" -homepage = "https://github.com/stac-utils/stac-rs" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" -keywords = ["geospatial", "stac", "metadata", "geo", "raster"] -categories = ["science", "data-structures"] +version = "0.10.2" +keywords = ["geospatial", "stac", "metadata", "geo"] +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true [features] -gdal = ["dep:gdal", "dep:gdal-sys"] geo = ["dep:geo"] geoarrow = [ "dep:geoarrow", @@ -52,35 +52,33 @@ arrow-array = { workspace = true, optional = true } arrow-cast = { workspace = true, optional = true } arrow-json = { workspace = true, optional = true } arrow-schema = { workspace = true, optional = true } -bytes = { workspace = true } +bytes.workspace = true chrono = { workspace = true, features = ["serde"] } fluent-uri = { workspace = true, optional = true } -gdal = { workspace = true, optional = true } -gdal-sys = { workspace = true, optional = true } geo = { workspace = true, optional = true } geo-types = { workspace = true, optional = true } geoarrow = { workspace = true, optional = true } -geojson = { workspace = true } +geojson.workspace = true jsonschema = { workspace = true, optional = true } -log = { workspace = true } -mime = { workspace = true } +log.workspace = true +mime.workspace = true object_store = { workspace = true, optional = true } parquet = { workspace = true, optional = true } reqwest = { workspace = true, features = ["json", "blocking"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } -thiserror = { workspace = true } +thiserror.workspace = true tokio = { workspace = true, optional = true } -tracing = { workspace = true } -url = { workspace = true } +tracing.workspace = true +url.workspace = true [dev-dependencies] -assert-json-diff = { workspace = true } -bytes = { workspace = true } -rstest = { workspace = true } -tempfile = { workspace = true } +assert-json-diff.workspace = true +bytes.workspace = true +rstest.workspace = true +tempfile.workspace = true tokio = { workspace = true, features = ["macros"] } -tokio-test = { workspace = true } +tokio-test.workspace = true [[test]] name = "examples" @@ -103,4 +101,6 @@ denylist = [ "object-store-azure", "object-store-gcp", "object-store-http", + "reqwest-rustls", + "validate-blocking", ] diff --git a/core/build.rs b/core/build.rs deleted file mode 100644 index 8f97a89b..00000000 --- a/core/build.rs +++ /dev/null @@ -1,29 +0,0 @@ -fn main() { - #[cfg(feature = "gdal")] - { - use std::str::FromStr; - - // https://blog.rust-lang.org/2024/05/06/check-cfg.html - println!("cargo::rustc-check-cfg=cfg(gdal_has_int8)"); - println!("cargo::rustc-check-cfg=cfg(gdal_has_int64)"); - println!("cargo::rustc-check-cfg=cfg(gdal_has_uint64)"); - - let gdal_version_string = std::env::var("DEP_GDAL_VERSION_NUMBER").unwrap(); - let gdal_version = i64::from_str(&gdal_version_string) - .expect("Could not convert gdal version string into number."); - let major = gdal_version / 1000000; - let minor = (gdal_version - major * 1000000) / 10000; - let patch = (gdal_version - major * 1000000 - minor * 10000) / 100; - - if major != 3 { - panic!("This crate requires a GDAL version = 3. Found {major}.{minor}.{patch}"); - } - if minor >= 5 { - println!("cargo:rustc-cfg=gdal_has_int64"); - println!("cargo:rustc-cfg=gdal_has_uint64"); - } - if minor >= 7 { - println!("cargo:rustc-cfg=gdal_has_int8"); - } - } -} diff --git a/core/src/catalog.rs b/core/src/catalog.rs index c7966586..01e50e44 100644 --- a/core/src/catalog.rs +++ b/core/src/catalog.rs @@ -1,4 +1,4 @@ -use crate::{Error, Extensions, Fields, Href, Link, Links, Migrate, Result, Version, STAC_VERSION}; +use crate::{Error, Fields, Href, Link, Links, Migrate, Result, Version, STAC_VERSION}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -135,15 +135,6 @@ impl Fields for Catalog { } } -impl Extensions for Catalog { - fn extensions(&self) -> &Vec { - &self.extensions - } - fn extensions_mut(&mut self) -> &mut Vec { - &mut self.extensions - } -} - fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result where D: serde::de::Deserializer<'de>, diff --git a/core/src/collection.rs b/core/src/collection.rs index 60ced25a..6b5d78e1 100644 --- a/core/src/collection.rs +++ b/core/src/collection.rs @@ -1,6 +1,6 @@ use crate::{ - Asset, Assets, Bbox, Error, Extensions, Fields, Href, Item, ItemAsset, Link, Links, Migrate, - Result, Version, STAC_VERSION, + Asset, Assets, Bbox, Error, Fields, Href, Item, ItemAsset, Link, Links, Migrate, Result, + Version, STAC_VERSION, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -400,15 +400,6 @@ impl Fields for Collection { } } -impl Extensions for Collection { - fn extensions(&self) -> &Vec { - &self.extensions - } - fn extensions_mut(&mut self) -> &mut Vec { - &mut self.extensions - } -} - impl TryFrom for Map { type Error = Error; fn try_from(collection: Collection) -> Result { diff --git a/core/src/error.rs b/core/src/error.rs index ac7dbcc5..f4e72c72 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -22,11 +22,6 @@ pub enum Error { #[error(transparent)] ChronoParse(#[from] chrono::ParseError), - /// [gdal::errors::GdalError] - #[cfg(feature = "gdal")] - #[error(transparent)] - GdalError(#[from] gdal::errors::GdalError), - /// A required feature is not enabled. #[error("{0} is not enabled")] FeatureNotEnabled(&'static str), diff --git a/core/src/extensions/projection.rs b/core/src/extensions/projection.rs deleted file mode 100644 index 23026bfb..00000000 --- a/core/src/extensions/projection.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! The Projection extension. - -use super::Extension; -use geojson::Geometry; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; - -/// The projection extension fields. -#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)] -pub struct Projection { - /// EPSG code of the datasource - #[serde(skip_serializing_if = "Option::is_none")] - pub code: Option, - - /// WKT2 string representing the Coordinate Reference System (CRS) that the - /// proj:geometry and proj:bbox fields represent - #[serde(skip_serializing_if = "Option::is_none")] - pub wkt2: Option, - - /// PROJJSON object representing the Coordinate Reference System (CRS) that - /// the proj:geometry and proj:bbox fields represent - #[serde(skip_serializing_if = "Option::is_none")] - pub projjson: Option>, - - /// Defines the footprint of this Item. - #[serde(skip_serializing_if = "Option::is_none")] - pub geometry: Option, - - /// Bounding box of the Item in the asset CRS in 2 or 3 dimensions. - #[serde(skip_serializing_if = "Option::is_none")] - pub bbox: Option>, - - /// Coordinates representing the centroid of the Item (in lat/long) - #[serde(skip_serializing_if = "Option::is_none")] - pub centroid: Option, - - /// Number of pixels in Y and X directions for the default grid - #[serde(skip_serializing_if = "Option::is_none")] - pub shape: Option>, - - /// The affine transformation coefficients for the default grid - #[serde(skip_serializing_if = "Option::is_none")] - pub transform: Option>, -} - -/// This object represents the centroid of the Item Geometry. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -pub struct Centroid { - /// The latitude of the centroid. - pub lat: f64, - - /// The longitude of the centroid. - pub lon: f64, -} - -impl Projection { - /// Returns this projection's bounds in WGS84. - /// - /// Requires one of the crs fields to be set (code, wkt2, or projjson) as well as a bbox. - /// - /// # Examples - /// - /// ``` - /// use stac::extensions::Projection; - /// let projection = Projection { - /// code: Some("EPSG:32621".to_string()), - /// bbox: Some(vec![ - /// 373185.0, - /// 8019284.949381611, - /// 639014.9492102272, - /// 8286015.0 - /// ]), - /// ..Default::default() - /// }; - /// let bounds = projection.wgs84_bounds().unwrap().unwrap(); - /// ``` - #[cfg(feature = "gdal")] - pub fn wgs84_bounds(&self) -> crate::Result> { - use gdal::spatial_ref::{AxisMappingStrategy, CoordTransform, SpatialRef}; - - if let Some(bbox) = self.bbox.as_ref() { - if bbox.len() != 4 { - return Ok(None); - } - if let Some(spatial_ref) = self.spatial_ref()? { - let mut wgs84 = SpatialRef::from_epsg(4326)?; - // Ensure we're lon then lat - wgs84.set_axis_mapping_strategy(AxisMappingStrategy::TraditionalGisOrder); - let coord_transform = CoordTransform::new(&spatial_ref, &wgs84)?; - let bounds = - coord_transform.transform_bounds(&[bbox[0], bbox[1], bbox[2], bbox[3]], 21)?; - let round = |n: f64| (n * 10_000_000.).round() / 10_000_000.; - Ok(Some(crate::Bbox::new( - round(bounds[0]), - round(bounds[1]), - round(bounds[2]), - round(bounds[3]), - ))) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - - #[cfg(feature = "gdal")] - fn spatial_ref(&self) -> crate::Result> { - use crate::Error; - use gdal::spatial_ref::SpatialRef; - - if let Some(code) = self.code.as_deref() { - SpatialRef::from_definition(code) - .map(Some) - .map_err(Error::from) - } else if let Some(wkt) = self.wkt2.as_ref() { - SpatialRef::from_wkt(wkt).map(Some).map_err(Error::from) - } else if let Some(projjson) = self.projjson.clone() { - SpatialRef::from_definition(&serde_json::to_string(&Value::Object(projjson))?) - .map(Some) - .map_err(Error::from) - } else { - Ok(None) - } - } - - /// Returns true if this projection structure is empty. - /// - /// # Examples - /// - /// ``` - /// use stac::extensions::Projection; - /// - /// let projection = Projection::default(); - /// assert!(projection.is_empty()); - /// ``` - pub fn is_empty(&self) -> bool { - serde_json::to_value(self) - .map(|v| v == Value::Object(Default::default())) - .unwrap_or(true) - } -} - -impl Extension for Projection { - const IDENTIFIER: &'static str = - "https://stac-extensions.github.io/projection/v2.0.0/schema.json"; - const PREFIX: &'static str = "proj"; -} - -#[cfg(test)] -mod tests { - use super::Projection; - use crate::{Fields, Item}; - - #[cfg(feature = "gdal")] - #[test] - fn axis_order() { - let projection = Projection { - code: Some("EPSG:32621".to_string()), - bbox: Some(vec![ - 373185.0, - 8019284.949381611, - 639014.9492102272, - 8286015.0, - ]), - ..Default::default() - }; - let bounds = projection.wgs84_bounds().unwrap().unwrap(); - assert!( - (bounds.xmin() - -61.2876244).abs() < 0.1, - "{}", - bounds.xmin() - ); - assert!((bounds.ymin() - 72.229798).abs() < 0.1); - } - - #[test] - fn example() { - let item: Item = - crate::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); - let projection = item.extension::().unwrap(); - assert_eq!(projection.code.unwrap(), "EPSG:32614"); - } -} diff --git a/core/src/fields.rs b/core/src/fields.rs index 1b521ab8..aa8f82c9 100644 --- a/core/src/fields.rs +++ b/core/src/fields.rs @@ -1,4 +1,4 @@ -use crate::{Error, Extension, Result}; +use crate::{Error, Result}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::{json, Map, Value}; @@ -79,9 +79,11 @@ pub trait Fields { /// # Examples /// /// ``` - /// use stac::{Fields, Item, extensions::Projection}; + /// use stac::{Fields, Item}; + /// use serde_json::Value; + /// /// let item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); - /// let projection: Projection = item.fields_with_prefix("proj").unwrap(); // Prefer `Extensions::extension` + /// let projection: Value = item.fields_with_prefix("proj").unwrap(); /// ``` fn fields_with_prefix(&self, prefix: &str) -> Result { let mut map = Map::new(); @@ -99,10 +101,12 @@ pub trait Fields { /// # Examples /// /// ``` - /// use stac::{Fields, Item, extensions::Projection}; - /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() }; + /// use stac::{Fields, Item}; + /// use serde_json::json; + /// + /// let projection = json!({ "code": "EPSG:4326" }); /// let mut item = Item::new("an-id"); - /// item.set_fields_with_prefix("proj", projection); // Prefer `Extensions::set_extension` + /// item.set_fields_with_prefix("proj", projection); /// ``` fn set_fields_with_prefix(&mut self, prefix: &str, value: S) -> Result<()> { let value = serde_json::to_value(value)?; @@ -121,62 +125,14 @@ pub trait Fields { /// # Examples /// /// ``` - /// use stac::{Fields, Item, extensions::Projection}; - /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() }; - /// let mut item = Item::new("an-id"); - /// item.remove_fields_with_prefix("proj"); // Prefer `Fields::remove_extension` + /// use stac::{Fields, Item}; + /// + /// let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); + /// item.remove_fields_with_prefix("proj"); /// ``` fn remove_fields_with_prefix(&mut self, prefix: &str) { let prefix = format!("{}:", prefix); self.fields_mut() .retain(|key, _| !(key.starts_with(&prefix) && key.len() > prefix.len())); } - - /// Gets an extension's data. - /// - /// Returns `Ok(None)` if the object doesn't have the given extension. - /// - /// # Examples - /// - /// ``` - /// use stac::{Item, Fields, extensions::Projection}; - /// let item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); - /// let projection: Projection = item.extension().unwrap(); - /// assert_eq!(projection.code.unwrap(), "EPSG:32614"); - /// ``` - fn extension(&self) -> Result { - self.fields_with_prefix(E::PREFIX) - } - - /// Sets an extension's data into this object. - /// - /// This will remove any previous fields from this extension - /// - /// # Examples - /// - /// ``` - /// use stac::{Item, Fields, extensions::Projection}; - /// let mut item = Item::new("an-id"); - /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() }; - /// item.set_extension(projection).unwrap(); - /// ``` - fn set_extension(&mut self, extension: E) -> Result<()> { - self.remove_extension::(); - self.set_fields_with_prefix(E::PREFIX, extension) - } - - /// Removes all of the extension's fields from this object. - /// - /// # Examples - /// - /// ``` - /// use stac::{Item, extensions::{Projection, Extensions}}; - /// let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); - /// assert!(item.has_extension::()); - /// item.remove_extension::(); - /// assert!(!item.has_extension::()); - /// ``` - fn remove_extension(&mut self) { - self.remove_fields_with_prefix(E::PREFIX); - } } diff --git a/core/src/gdal.rs b/core/src/gdal.rs deleted file mode 100644 index d116a72c..00000000 --- a/core/src/gdal.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! Functions that leverage [GDAL](https://gdal.org/). - -use crate::{ - extensions::{ - projection::Centroid, - raster::{Band, Raster, Statistics}, - Projection, - }, - Asset, Bbox, Extensions, Fields, Item, Result, -}; -use gdal::{ - spatial_ref::{CoordTransform, SpatialRef}, - Dataset, -}; - -/// Update an [Item] using GDAL. -/// -/// Adds things like the [Raster] and [Projection] extensions. -/// -/// # Examples -/// -/// ``` -/// use stac::{Asset, Item, Extensions, extensions::Raster}; -/// let mut item = Item::new("an-id"); -/// item.assets.insert("data".to_string(), Asset::new("assets/dataset.tif")); -/// stac::gdal::update_item(&mut item, false, true).unwrap(); -/// assert!(item.has_extension::()); -/// ``` -pub fn update_item( - item: &mut Item, - force_statistics: bool, - is_approx_statistics_ok: bool, -) -> Result<()> { - log::debug!( - "updating item={} with force_statistics={} and is_approx_statistics_ok={}", - item.id, - force_statistics, - is_approx_statistics_ok - ); - gdal::config::set_error_handler(|err, code, msg| log::warn!("{:?} ({}): {}", err, code, msg)); - let mut has_raster = false; - let mut has_projection = false; - let mut projections = Vec::new(); - let mut bbox = Bbox::new(180., 90., -180., 90.); // Intentionally invalid bbox so the first update always takes - for asset in item.assets.values_mut() { - update_asset(asset, force_statistics, is_approx_statistics_ok)?; - let projection = asset.extension::()?; - if !projection.is_empty() { - has_projection = true; - if let Some(asset_bounds) = projection.wgs84_bounds()? { - bbox.update(asset_bounds); - } - projections.push(projection); - } - if !has_raster && !asset.extension::()?.is_empty() { - has_raster = true; - } - } - if bbox.is_valid() { - item.geometry = Some(bbox.to_geometry()); - item.bbox = Some(bbox); - } - if has_projection { - if !projections.is_empty() - && projections - .iter() - .all(|projection| *projection == projections[0]) - { - Extensions::set_extension(item, projections[0].clone())?; - for asset in item.assets.values_mut() { - asset.remove_extension::(); - } - } else { - item.add_extension::(); - } - } - if has_raster { - item.add_extension::(); - } - Ok(()) -} - -fn update_asset( - asset: &mut Asset, - force_statistics: bool, - is_approx_statistics_ok: bool, -) -> Result<()> { - let dataset = Dataset::open(&asset.href)?; - let mut spatial_resolution = None; - let mut projection = Projection::default(); - let (width, height) = dataset.raster_size(); - projection.shape = Some(vec![height, width]); - if let Ok(geo_transform) = dataset.geo_transform() { - projection.transform = Some(vec![ - geo_transform[1], - geo_transform[2], - geo_transform[0], - geo_transform[4], - geo_transform[5], - geo_transform[3], - ]); - spatial_resolution = Some((geo_transform[1].abs() + geo_transform[5].abs()) / 2.0); - let (width, height) = dataset.raster_size(); - let width = width as f64; - let height = height as f64; - let x0 = geo_transform[0]; - let x1 = geo_transform[0] + width * geo_transform[1]; - let x2 = geo_transform[0] + width * geo_transform[1] + height * geo_transform[2]; - let x3 = geo_transform[0] + height * geo_transform[2]; - let xmin = x0.min(x1).min(x2).min(x3); - let xmax = x0.max(x1).max(x2).max(x3); - let y0 = geo_transform[3]; - let y1 = geo_transform[3] + width * geo_transform[4]; - let y2 = geo_transform[3] + width * geo_transform[4] + height * geo_transform[5]; - let y3 = geo_transform[3] + height * geo_transform[5]; - let ymin = y0.min(y1).min(y2).min(y3); - let ymax = y0.max(y1).max(y2).max(y3); - projection.bbox = Some(vec![xmin, ymin, xmax, ymax]); - } - if let Ok(spatial_ref) = dataset.spatial_ref() { - if spatial_ref - .auth_name() - .ok() - .map(|auth_name| auth_name == "EPSG") - .unwrap_or_default() - { - projection.code = spatial_ref - .auth_code() - .map(|code| format!("EPSG:{}", code)) - .ok(); - } - // FIXME Don't know how to get WKT2 out of the gdal crate yet. - if projection.code.is_none() { - projection.projjson = spatial_ref - .to_projjson() - .ok() - .and_then(|s| serde_json::from_str(&s).ok()); - } - if let Some(bbox) = projection.bbox.as_ref() { - let mut x = [(bbox[0] + bbox[2]) / 2.]; - let mut y = [(bbox[1] + bbox[3]) / 2.]; - let coord_transform = CoordTransform::new(&spatial_ref, &SpatialRef::from_epsg(4326)?)?; - coord_transform.transform_coords(&mut x, &mut y, &mut [])?; - let round = |n: f64| (n * 10_000_000.).round() / 10_000_000.; - projection.centroid = Some(Centroid { - lat: round(x[0]), - lon: round(y[0]), - }); - } - } - let count = dataset.raster_count(); - let mut bands = Vec::with_capacity(count); - for i in 1..=count { - let band = dataset.rasterband(i)?; - bands.push(Band { - nodata: band.no_data_value(), - data_type: Some(band.band_type().into()), - spatial_resolution, - scale: band.scale(), - offset: band.offset(), - unit: Some(band.unit()).filter(|s| !s.is_empty()), - statistics: band - .get_statistics(force_statistics, is_approx_statistics_ok)? - .map(Statistics::from), - ..Default::default() - }) - } - asset.set_extension(projection)?; - asset.set_extension(Raster { bands })?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::{ - extensions::{projection::Centroid, raster::DataType, Projection, Raster}, - item::Builder, - Extensions, Fields, - }; - - #[test] - fn raster_data_type() { - let mut item = Builder::new("an-id") - .asset("data", "assets/dataset.tif") - .build() - .unwrap(); - super::update_item(&mut item, false, true).unwrap(); - assert!(item.has_extension::()); - let raster: Raster = item.assets.get("data").unwrap().extension().unwrap(); - assert_eq!( - *raster.bands[0].data_type.as_ref().unwrap(), - DataType::UInt16 - ) - } - - #[test] - fn raster_spatial_resolution() { - let mut item = Builder::new("an-id") - .asset("data", "assets/dataset_geo.tif") - .build() - .unwrap(); - super::update_item(&mut item, false, true).unwrap(); - let raster: Raster = item.assets.get("data").unwrap().extension().unwrap(); - assert_eq!( - raster.bands[0].spatial_resolution.unwrap(), - 100.01126757344893 - ); - } - - #[test] - fn projection() { - let mut item = Builder::new("an-id") - .asset("data", "assets/dataset_geo.tif") - .build() - .unwrap(); - super::update_item(&mut item, false, true).unwrap(); - let projection: Projection = item.extension().unwrap(); - assert_eq!(projection.code.unwrap(), "EPSG:32621"); - assert_eq!( - projection.bbox.unwrap(), - vec![373185.0, 8019284.949381611, 639014.9492102272, 8286015.0] - ); - assert_eq!(projection.shape.unwrap(), vec![2667, 2658]); - assert_eq!( - projection.transform.unwrap(), - vec![ - 100.01126757344893, - 0.0, - 373185.0, - 0.0, - -100.01126757344893, - 8286015.0, - ] - ); - assert_eq!( - projection.centroid.unwrap(), - Centroid { - lon: -56.8079473, - lat: 73.4675736, - } - ) - } -} diff --git a/core/src/item.rs b/core/src/item.rs index d35489eb..0e56687a 100644 --- a/core/src/item.rs +++ b/core/src/item.rs @@ -1,8 +1,7 @@ //! STAC Items. use crate::{ - Asset, Assets, Bbox, Error, Extensions, Fields, Href, Link, Links, Migrate, Result, Version, - STAC_VERSION, + Asset, Assets, Bbox, Error, Fields, Href, Link, Links, Migrate, Result, Version, STAC_VERSION, }; use chrono::{DateTime, FixedOffset, Utc}; use geojson::{feature::Id, Feature, Geometry}; @@ -233,11 +232,6 @@ pub struct Builder { id: String, canonicalize_paths: bool, assets: HashMap, - enable_gdal: bool, - #[cfg(feature = "gdal")] - force_statistics: bool, // TODO add builder method - #[cfg(feature = "gdal")] - is_approx_statistics_ok: bool, // TODO add builder method } impl Builder { @@ -254,11 +248,6 @@ impl Builder { id: id.to_string(), canonicalize_paths: true, assets: HashMap::new(), - enable_gdal: cfg!(feature = "gdal"), - #[cfg(feature = "gdal")] - force_statistics: false, - #[cfg(feature = "gdal")] - is_approx_statistics_ok: true, } } @@ -290,22 +279,6 @@ impl Builder { self } - /// Enable or disable GDAL processing of asset files. - /// - /// If this crate is _not_ compiled with the `gdal` flag and this value is - /// `true`, an error will be thrown. - /// - /// # Examples - /// - /// ``` - /// use stac::item::Builder; - /// let builder = Builder::new("an-id").enable_gdal(false); - /// ``` - pub fn enable_gdal(mut self, enable_gdal: bool) -> Builder { - self.enable_gdal = enable_gdal; - self - } - /// Builds an [Item] from this builder. /// /// # Examples @@ -327,16 +300,6 @@ impl Builder { } let _ = item.assets.insert(key, asset); } - if self.enable_gdal { - #[cfg(feature = "gdal")] - crate::gdal::update_item( - &mut item, - self.force_statistics, - self.is_approx_statistics_ok, - )?; - #[cfg(not(feature = "gdal"))] - return Err(Error::FeatureNotEnabled("gdal")); - } Ok(item) } } @@ -652,15 +615,6 @@ impl Fields for Item { } } -impl Extensions for Item { - fn extensions(&self) -> &Vec { - &self.extensions - } - fn extensions_mut(&mut self) -> &mut Vec { - &mut self.extensions - } -} - impl TryFrom for Map { type Error = Error; fn try_from(item: Item) -> Result { @@ -750,10 +704,7 @@ impl Migrate for Item {} #[cfg(test)] mod tests { use super::{Builder, FlatItem, Item}; - use crate::{ - extensions::{Projection, Raster}, - Asset, Extensions, STAC_VERSION, - }; + use crate::{Asset, STAC_VERSION}; use geojson::{feature::Id, Feature}; use serde_json::Value; @@ -917,19 +868,6 @@ mod tests { assert_eq!(asset.roles, vec!["data"]); } - #[test] - fn builder_uses_gdal() { - let item = Builder::new("an-id") - .asset("data", "assets/dataset.tif") - .build() - .unwrap(); - if cfg!(feature = "gdal") { - assert!(item.has_extension::()); - } else { - assert!(!item.has_extension::()); - } - } - #[test] fn try_from_geojson_feature() { let mut feature = Feature { @@ -974,7 +912,6 @@ mod tests { #[test] fn flat_item_without_geometry() { let mut item = Item::new("an-item"); - item.add_extension::(); item.bbox = Some(vec![-105., 42., -105., -42.].try_into().unwrap()); let mut value = serde_json::to_value(item).unwrap(); let _ = value.as_object_mut().unwrap().remove("geometry").unwrap(); diff --git a/core/src/lib.rs b/core/src/lib.rs index cf9dc942..b523ba71 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -103,7 +103,6 @@ //! //! # Features //! -//! - `gdal`: read raster assets, see [gdal] //! - `geo`: add some geo-enabled methods, see [geo] //! - `geoarrow`: read and write [geoarrow](https://geoarrow.org/), see [geoarrow] //! - `geoparquet`: read and write [geoparquet](https://geoparquet.org/), see [geoparquet] @@ -156,11 +155,8 @@ mod collection; mod data_type; pub mod datetime; mod error; -pub mod extensions; mod fields; mod format; -#[cfg(feature = "gdal")] -pub mod gdal; #[cfg(feature = "geo")] pub mod geo; #[cfg(feature = "geoarrow")] @@ -194,7 +190,6 @@ pub use { collection::{Collection, Extent, Provider, SpatialExtent, TemporalExtent, COLLECTION_TYPE}, data_type::DataType, error::Error, - extensions::{Extension, Extensions}, fields::Fields, format::Format, geoparquet::{FromGeoparquet, IntoGeoparquet}, @@ -473,10 +468,6 @@ mod readme { external_doc_test!(include_str!("../README.md")); } -// We only use gdal-sys for the build script. -#[cfg(feature = "gdal")] -use gdal_sys as _; - // For now, we only use tracing in the validate module. #[cfg(not(feature = "validate"))] use tracing as _; diff --git a/docs/cli/index.md b/docs/cli/index.md index 58a7c6c2..9165fccd 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -17,13 +17,6 @@ If you have Python, use `pip`: python -m pip install stacrs-cli ``` -!!! Note - -The PyPI version of the CLI does not contain bindings to GDAL. This -shouldn't be a problem for most users, but if you're using `stacrs item - image.tiff` to generate new STAC items from a raster, you'll need to install -from `cargo`. - The CLI is called **stacrs**: ```shell diff --git a/duckdb/Cargo.toml b/duckdb/Cargo.toml index 0bfd4538..44e2e7cf 100644 --- a/duckdb/Cargo.toml +++ b/duckdb/Cargo.toml @@ -1,25 +1,26 @@ [package] name = "stac-duckdb" -version = "0.0.2" -authors = ["Pete Gadomski "] -edition = "2021" description = "Experimental client for querying stac-geoparquet using DuckDB" -homepage = "https://github.com/stac-utils/stac-rs" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" +version = "0.0.2" keywords = ["geospatial", "stac", "metadata", "geo", "raster"] -categories = ["science", "data-structures"] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +categories.workspace = true +rust-version.workspace = true [dependencies] -arrow = { workspace = true } -duckdb = { workspace = true } -libduckdb-sys = { workspace = true } -geoarrow = { workspace = true } -parquet = { workspace = true } +arrow.workspace = true +duckdb.workspace = true +libduckdb-sys.workspace = true +geoarrow.workspace = true +parquet.workspace = true stac = { workspace = true, features = ["geoarrow"] } -stac-api = { workspace = true } -thiserror = { workspace = true } +stac-api.workspace = true +thiserror.workspace = true [dev-dependencies] duckdb-test = { path = "duckdb-test" } -geo = { workspace = true } +geo.workspace = true diff --git a/duckdb/duckdb-test/Cargo.toml b/duckdb/duckdb-test/Cargo.toml index f612d5e2..fb3b67eb 100644 --- a/duckdb/duckdb-test/Cargo.toml +++ b/duckdb/duckdb-test/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "duckdb-test" version = "0.0.0" -edition = "2021" +edition.workspace = true publish = false [lib] @@ -10,5 +10,5 @@ test = false doctest = false [dependencies] -quote = { workspace = true } +quote.workspace = true syn = { workspace = true, features = ["full", "extra-traits"] } diff --git a/extensions/Cargo.toml b/extensions/Cargo.toml new file mode 100644 index 00000000..82e27d11 --- /dev/null +++ b/extensions/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stac-extensions" +description = "Manage STAC extensions (https://stac-extensions.github.io/)" +version = "0.1.0" +keywords = ["geospatial", "stac", "extensions"] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +categories.workspace = true +rust-version.workspace = true + +[dependencies] +geojson.workspace = true +stac.workspace = true +serde.workspace = true +serde_json.workspace = true diff --git a/core/data/auth/collection.json b/extensions/data/auth/collection.json similarity index 100% rename from core/data/auth/collection.json rename to extensions/data/auth/collection.json diff --git a/core/data/auth/item.json b/extensions/data/auth/item.json similarity index 100% rename from core/data/auth/item.json rename to extensions/data/auth/item.json diff --git a/core/data/eo/item.json b/extensions/data/eo/item.json similarity index 100% rename from core/data/eo/item.json rename to extensions/data/eo/item.json diff --git a/extensions/examples b/extensions/examples new file mode 120000 index 00000000..dc8ca857 --- /dev/null +++ b/extensions/examples @@ -0,0 +1 @@ +../spec-examples/v1.1.0 \ No newline at end of file diff --git a/core/src/extensions/authentication.rs b/extensions/src/authentication.rs similarity index 93% rename from core/src/extensions/authentication.rs rename to extensions/src/authentication.rs index 8ed4d350..ac59cb99 100644 --- a/core/src/extensions/authentication.rs +++ b/extensions/src/authentication.rs @@ -1,6 +1,6 @@ //! Provides a standard set of fields to describe authentication and //! authorization schemes, flows, and scopes required to access -//! [Assets](crate::Asset) and [Links](crate::Link) that align with the [OpenAPI +//! [Assets](stac::Asset) and [Links](stac::Link) that align with the [OpenAPI //! security spec](https://swagger.io/docs/specification/authentication/). use crate::Extension; @@ -12,12 +12,12 @@ use std::collections::HashMap; #[derive(Debug, Serialize, Deserialize)] pub struct Authentication { /// A property that contains all of the [scheme definitions](Scheme) used by - /// [Assets](crate::Asset) and [Links](crate::Link) in the STAC [Item](crate::Item) or [Collection](crate::Collection). + /// [Assets](stac::Asset) and [Links](stac::Link) in the STAC [Item](crate::Item) or [Collection](crate::Collection). #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub schemes: HashMap, - /// A property that specifies which schemes may be used to access an [Asset](crate::Asset) - /// or [Link](crate::Link). + /// A property that specifies which schemes may be used to access an [Asset](stac::Asset) + /// or [Link](stac::Link). #[serde(default, skip_serializing_if = "Vec::is_empty")] pub refs: Vec, } @@ -167,12 +167,12 @@ impl Extension for Authentication { #[cfg(test)] mod tests { use super::{Authentication, In, Scheme}; - use crate::{Collection, Fields, Item}; + use crate::{Collection, Extensions, Item}; use serde_json::json; #[test] fn collection() { - let collection: Collection = crate::read("data/auth/collection.json").unwrap(); + let collection: Collection = stac::read("data/auth/collection.json").unwrap(); let authentication: Authentication = collection.extension().unwrap(); let oauth = authentication.schemes.get("oauth").unwrap(); let _ = oauth.flows.get("authorizationCode").unwrap(); @@ -180,7 +180,7 @@ mod tests { #[test] fn item() { - let collection: Item = crate::read("data/auth/item.json").unwrap(); + let collection: Item = stac::read("data/auth/item.json").unwrap(); let authentication: Authentication = collection.extension().unwrap(); let _ = authentication.schemes.get("none").unwrap(); } diff --git a/core/src/extensions/electro_optical.rs b/extensions/src/electro_optical.rs similarity index 95% rename from core/src/extensions/electro_optical.rs rename to extensions/src/electro_optical.rs index 10918713..4341efc5 100644 --- a/core/src/extensions/electro_optical.rs +++ b/extensions/src/electro_optical.rs @@ -29,7 +29,7 @@ pub struct ElectroOptical { /// [Spectral /// bands](https://www.sciencedirect.com/topics/earth-and-planetary-sciences/spectral-band) -/// in an [Asset](crate::Asset). +/// in an [Asset](stac::Asset). #[derive(Debug, Serialize, Deserialize)] pub struct Band { /// The name of the band (e.g., "B01", "B8", "band2", "red"). @@ -72,11 +72,11 @@ impl Extension for ElectroOptical { #[cfg(test)] mod tests { use super::ElectroOptical; - use crate::{Fields, Item}; + use crate::{Extensions, Item}; #[test] fn item() { - let item: Item = crate::read("data/eo/item.json").unwrap(); + let item: Item = stac::read("data/eo/item.json").unwrap(); let _: ElectroOptical = item.extension().unwrap(); } } diff --git a/core/src/extensions/mod.rs b/extensions/src/lib.rs similarity index 77% rename from core/src/extensions/mod.rs rename to extensions/src/lib.rs index 608493eb..926bc1d6 100644 --- a/core/src/extensions/mod.rs +++ b/extensions/src/lib.rs @@ -21,12 +21,12 @@ //! //! ## Usage //! -//! [Item](crate::Item), [Collection](crate::Collection), -//! [Catalog](crate::Catalog) all implement the [Extensions] trait, which -//! provides methods to get, set, and remove extension information: +//! [Item], [Collection], and [Catalog] all implement the [Extensions] trait, +//! which provides methods to get, set, and remove extension information: //! //! ``` -//! use stac::{Item, Extensions, Fields, extensions::{Projection, projection::Centroid}}; +//! use stac::Item; +//! use stac_extensions::{Extensions, Projection, projection::Centroid}; //! let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); //! assert!(item.has_extension::()); //! @@ -48,8 +48,8 @@ pub mod electro_optical; pub mod projection; pub mod raster; -use crate::{Fields, Result}; use serde::{de::DeserializeOwned, Serialize}; +use stac::{Catalog, Collection, Fields, Item, Result}; pub use {projection::Projection, raster::Raster}; /// A trait implemented by extensions. @@ -68,7 +68,7 @@ pub trait Extension: Serialize + DeserializeOwned { /// # Examples /// /// ``` - /// use stac::extensions::{Raster, Extension}; + /// use stac_extensions::{Raster, Extension}; /// assert_eq!(Raster::identifier_prefix(), "https://stac-extensions.github.io/raster/"); /// ``` fn identifier_prefix() -> &'static str { @@ -87,7 +87,9 @@ pub trait Extensions: Fields { /// # Examples /// /// ``` - /// use stac::{Extensions, Item}; + /// use stac::Item; + /// use stac_extensions::Extensions; + /// /// let item = Item::new("an-id"); /// assert!(item.extensions().is_empty()); /// ``` @@ -98,7 +100,9 @@ pub trait Extensions: Fields { /// # Examples /// /// ``` - /// use stac::{Extensions, Item}; + /// use stac::Item; + /// use stac_extensions::Extensions; + /// /// let mut item = Item::new("an-id"); /// item.extensions_mut().push("https://stac-extensions.github.io/raster/v1.1.0/schema.json".to_string()); /// ``` @@ -109,7 +113,9 @@ pub trait Extensions: Fields { /// # Examples /// /// ``` - /// use stac::{Item, extensions::{Projection, Extensions}}; + /// use stac::Item; + /// use stac_extensions::{Projection, Extensions}; + /// /// let mut item = Item::new("an-id"); /// assert!(!item.has_extension::()); /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() }; @@ -122,12 +128,30 @@ pub trait Extensions: Fields { .any(|extension| extension.starts_with(E::identifier_prefix())) } + /// Returns an extension's data. + /// + /// # Examples + /// + /// ``` + /// use stac::Item; + /// use stac_extensions::{Projection, Extensions}; + /// + /// let item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); + /// let projection: Projection = item.extension().unwrap(); + /// assert_eq!(projection.code.unwrap(), "EPSG:32614"); + /// ``` + fn extension(&self) -> Result { + self.fields_with_prefix(E::PREFIX) + } + /// Adds an extension's identifier to this object. /// /// # Examples /// /// ``` - /// use stac::{Item, extensions::{Projection, Extensions}}; + /// use stac::Item; + /// use stac_extensions::{Projection, Extensions}; + /// /// let mut item = Item::new("an-id"); /// item.add_extension::(); /// ``` @@ -143,7 +167,9 @@ pub trait Extensions: Fields { /// # Examples /// /// ``` - /// use stac::{Item, extensions::{Projection, Extensions}}; + /// use stac::Item; + /// use stac_extensions::{Projection, Extensions}; + /// /// let mut item = Item::new("an-id"); /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() }; /// item.set_extension(projection).unwrap(); @@ -151,7 +177,8 @@ pub trait Extensions: Fields { fn set_extension(&mut self, extension: E) -> Result<()> { self.extensions_mut().push(E::IDENTIFIER.to_string()); self.extensions_mut().dedup(); - Fields::set_extension(self, extension) + self.remove_fields_with_prefix(E::PREFIX); + self.set_fields_with_prefix(E::PREFIX, extension) } /// Removes this extension and all of its fields from this object. @@ -159,30 +186,43 @@ pub trait Extensions: Fields { /// # Examples /// /// ``` - /// use stac::{Item, extensions::{Projection, Extensions}}; + /// use stac::Item; + /// use stac_extensions::{Projection, Extensions}; + /// /// let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); /// assert!(item.has_extension::()); /// item.remove_extension::(); /// assert!(!item.has_extension::()); /// ``` fn remove_extension(&mut self) { - Fields::remove_extension::(self); + self.remove_fields_with_prefix(E::PREFIX); self.extensions_mut() .retain(|extension| !extension.starts_with(E::identifier_prefix())) } } +macro_rules! impl_extensions { + ($name:ident) => { + impl Extensions for $name { + fn extensions(&self) -> &Vec { + &self.extensions + } + fn extensions_mut(&mut self) -> &mut Vec { + &mut self.extensions + } + } + }; +} + +impl_extensions!(Item); +impl_extensions!(Catalog); +impl_extensions!(Collection); + #[cfg(test)] mod tests { - use super::Extensions; - use crate::{ - extensions::{ - raster::{Band, Raster}, - Projection, - }, - Asset, Extension, Item, - }; + use crate::{raster::Raster, Extension, Extensions, Projection}; use serde_json::json; + use stac::Item; #[test] fn identifer_prefix() { @@ -196,19 +236,6 @@ mod tests { ); } - #[test] - fn set_extension_on_asset() { - use crate::Fields; - - let mut asset = Asset::new("a/href.tif"); - let mut band = Band::default(); - band.unit = Some("parsecs".to_string()); - let raster = Raster { bands: vec![band] }; - asset.set_extension(raster).unwrap(); - let mut item = Item::new("an-id"); - let _ = item.assets.insert("data".to_string(), asset); - } - #[test] fn remove_extension() { let mut item = Item::new("an-id"); diff --git a/extensions/src/projection.rs b/extensions/src/projection.rs new file mode 100644 index 00000000..3bb194e1 --- /dev/null +++ b/extensions/src/projection.rs @@ -0,0 +1,92 @@ +//! The Projection extension. + +use super::Extension; +use geojson::Geometry; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +/// The projection extension fields. +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)] +pub struct Projection { + /// EPSG code of the datasource + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + + /// WKT2 string representing the Coordinate Reference System (CRS) that the + /// proj:geometry and proj:bbox fields represent + #[serde(skip_serializing_if = "Option::is_none")] + pub wkt2: Option, + + /// PROJJSON object representing the Coordinate Reference System (CRS) that + /// the proj:geometry and proj:bbox fields represent + #[serde(skip_serializing_if = "Option::is_none")] + pub projjson: Option>, + + /// Defines the footprint of this Item. + #[serde(skip_serializing_if = "Option::is_none")] + pub geometry: Option, + + /// Bounding box of the Item in the asset CRS in 2 or 3 dimensions. + #[serde(skip_serializing_if = "Option::is_none")] + pub bbox: Option>, + + /// Coordinates representing the centroid of the Item (in lat/long) + #[serde(skip_serializing_if = "Option::is_none")] + pub centroid: Option, + + /// Number of pixels in Y and X directions for the default grid + #[serde(skip_serializing_if = "Option::is_none")] + pub shape: Option>, + + /// The affine transformation coefficients for the default grid + #[serde(skip_serializing_if = "Option::is_none")] + pub transform: Option>, +} + +/// This object represents the centroid of the Item Geometry. +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct Centroid { + /// The latitude of the centroid. + pub lat: f64, + + /// The longitude of the centroid. + pub lon: f64, +} + +impl Projection { + /// Returns true if this projection structure is empty. + /// + /// # Examples + /// + /// ``` + /// use stac_extensions::Projection; + /// + /// let projection = Projection::default(); + /// assert!(projection.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + serde_json::to_value(self) + .map(|v| v == Value::Object(Default::default())) + .unwrap_or(true) + } +} + +impl Extension for Projection { + const IDENTIFIER: &'static str = + "https://stac-extensions.github.io/projection/v2.0.0/schema.json"; + const PREFIX: &'static str = "proj"; +} + +#[cfg(test)] +mod tests { + use super::Projection; + use crate::{Extensions, Item}; + + #[test] + fn example() { + let item: Item = + stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap(); + let projection = item.extension::().unwrap(); + assert_eq!(projection.code.unwrap(), "EPSG:32614"); + } +} diff --git a/core/src/extensions/raster.rs b/extensions/src/raster.rs similarity index 76% rename from core/src/extensions/raster.rs rename to extensions/src/raster.rs index e6cf6907..b598b537 100644 --- a/core/src/extensions/raster.rs +++ b/extensions/src/raster.rs @@ -10,8 +10,8 @@ //! in the asset (values statistics, value interpretation, transforms). use super::Extension; -pub use crate::{DataType, Statistics}; use serde::{Deserialize, Serialize}; +pub use stac::{DataType, Statistics}; /// The raster extension. #[derive(Debug, Serialize, Deserialize, Default)] @@ -125,49 +125,12 @@ impl Raster { /// # Examples /// /// ``` - /// use stac::extensions::Raster; + /// use stac_extensions::Raster; /// - /// let projection = Raster::default(); - /// assert!(projection.is_empty()); + /// let raster = Raster::default(); + /// assert!(raster.is_empty()); /// ``` pub fn is_empty(&self) -> bool { self.bands.is_empty() } } - -#[cfg(feature = "gdal")] -impl From for DataType { - fn from(value: gdal::raster::GdalDataType) -> Self { - use gdal::raster::GdalDataType; - - match value { - GdalDataType::Unknown => DataType::Other, - #[cfg(gdal_has_int8)] - GdalDataType::Int8 => DataType::Int8, - GdalDataType::Int16 => DataType::Int16, - GdalDataType::Int32 => DataType::Int32, - #[cfg(gdal_has_int64)] - GdalDataType::Int64 => DataType::Int64, - GdalDataType::UInt8 => DataType::UInt8, - GdalDataType::UInt16 => DataType::UInt16, - GdalDataType::UInt32 => DataType::UInt32, - #[cfg(gdal_has_uint64)] - GdalDataType::UInt64 => DataType::UInt64, - GdalDataType::Float32 => DataType::Float32, - GdalDataType::Float64 => DataType::Float64, - } - } -} - -#[cfg(feature = "gdal")] -impl From for Statistics { - fn from(value: gdal::raster::StatisticsAll) -> Self { - Statistics { - minimum: Some(value.min), - maximum: Some(value.max), - mean: Some(value.mean), - stddev: Some(value.std_dev), - valid_percent: None, - } - } -} diff --git a/pgstac/Cargo.toml b/pgstac/Cargo.toml index b374b4ae..8b899585 100644 --- a/pgstac/Cargo.toml +++ b/pgstac/Cargo.toml @@ -1,26 +1,27 @@ [package] name = "pgstac" -version = "0.2.1" -authors = ["Pete Gadomski "] -edition = "2021" description = "Rust interface for pgstac" -homepage = "https://github.com/stac-utils/stac-rs" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" +version = "0.2.1" keywords = ["geospatial", "stac", "metadata", "raster", "database"] categories = ["database", "data-structures", "science"] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +rust-version.workspace = true [features] tls = ["dep:rustls", "dep:tokio-postgres-rustls", "dep:webpki-roots"] [dependencies] -geojson = { workspace = true } +geojson.workspace = true rustls = { workspace = true, features = ["ring", "std"], optional = true } -serde = { workspace = true } -serde_json = { workspace = true } -stac = { workspace = true } -stac-api = { workspace = true } -thiserror = { workspace = true } +serde.workspace = true +serde_json.workspace = true +stac.workspace = true +stac-api.workspace = true +thiserror.workspace = true tokio-postgres = { workspace = true, features = ["with-serde_json-1"] } tokio-postgres-rustls = { workspace = true, optional = true } webpki-roots = { workspace = true, optional = true } @@ -28,7 +29,7 @@ webpki-roots = { workspace = true, optional = true } [dev-dependencies] pgstac-test = { path = "pgstac-test" } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tokio-test = { workspace = true } +tokio-test.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/pgstac/pgstac-test/Cargo.toml b/pgstac/pgstac-test/Cargo.toml index 199a32bb..a1e8f627 100644 --- a/pgstac/pgstac-test/Cargo.toml +++ b/pgstac/pgstac-test/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pgstac-test" version = "0.0.0" -edition = "2021" +edition.workspace = true publish = false [lib] @@ -10,6 +10,6 @@ test = false doctest = false [dependencies] -quote = { workspace = true } +quote.workspace = true syn = { workspace = true, features = ["full", "extra-traits"] } -tokio-postgres = { workspace = true } +tokio-postgres.workspace = true diff --git a/pyproject.toml b/pyproject.toml index 127709df..5c934282 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ stac-api-validator = ["setuptools>=75.1.0", "stac-api-validator>=0.6.3"] [tool.uv.sources] -stacrs = { workspace = true } +stacrs.workspace = true [tool.uv.workspace] diff --git a/python/Cargo.toml b/python/Cargo.toml index ff57356a..004e38c9 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,11 +1,13 @@ [package] name = "python" description = "Python bindings for stac-rs" -license = "MIT OR Apache-2.0" -repository = "https://github.com/stac-utils/stac-rs" -homepage = "https://github.com/stac-utils/stac-rs" version = "0.1.3" -edition = "2021" +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +rust-version.workspace = true publish = false [lib] @@ -16,11 +18,11 @@ crate-type = ["cdylib"] duckdb = { workspace = true, features = [ "bundled", ] } # we don't use it directly, but we need to ensure it's bundled -geojson = { workspace = true } +geojson.workspace = true pyo3 = { workspace = true, features = ["extension-module"] } -pythonize = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } +pythonize.workspace = true +serde.workspace = true +serde_json.workspace = true stac = { workspace = true, features = [ "geoparquet-compression", "object-store-all", @@ -28,5 +30,5 @@ stac = { workspace = true, features = [ "validate-blocking", ] } stac-api = { workspace = true, features = ["client"] } -stac-duckdb = { workspace = true } +stac-duckdb.workspace = true tokio = { workspace = true, features = ["rt"] } diff --git a/python/pyproject.toml b/python/pyproject.toml index ee286603..cdd3d7cd 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -20,7 +20,7 @@ dynamic = ["version"] [project.urls] Repository = "https://github.com/stac-utils/stac-rs" -Documentation = "https://stac-utils.github.io/stac-rs/python/" +Documentation = "https://stac-utils.github.io/stac-rs" Issues = "https://github.com/stac-utils/stac-rs/issues" [tool.maturin] diff --git a/server/Cargo.toml b/server/Cargo.toml index 8ea7e53c..9477da41 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "stac-server" -version = "0.3.1" -edition = "2021" -authors = ["Pete Gadomski "] description = "SpatioTemporal Asset Catalog (STAC) API server" -homepage = "https://github.com/stac-utils/stac-rs" -repository = "https://github.com/stac-utils/stac-rs" -license = "MIT OR Apache-2.0" -keywords = ["geospatial", "stac", "metadata", "geo", "raster"] +version = "0.3.1" +keywords = ["geospatial", "stac", "metadata", "geo", "server"] categories = ["science", "data-structures"] -rust-version = "1.75" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +rust-version.workspace = true [features] axum = ["dep:axum", "dep:bytes", "dep:mime", "dep:tower-http"] @@ -20,24 +20,24 @@ axum = { workspace = true, optional = true } bb8 = { workspace = true, optional = true } bb8-postgres = { workspace = true, optional = true } bytes = { workspace = true, optional = true } -http = { workspace = true } +http.workspace = true mime = { workspace = true, optional = true } pgstac = { workspace = true, features = ["tls"], optional = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_urlencoded = { workspace = true } -stac = { workspace = true } +serde.workspace = true +serde_json.workspace = true +serde_urlencoded.workspace = true +stac.workspace = true stac-api = { workspace = true, features = ["geo"] } -thiserror = { workspace = true } +thiserror.workspace = true tokio-postgres = { workspace = true, optional = true } tower-http = { workspace = true, features = ["cors"], optional = true } -url = { workspace = true } +url.workspace = true [dev-dependencies] -serde_json = { workspace = true } +serde_json.workspace = true stac = { workspace = true, features = ["validate"] } tokio = { workspace = true, features = ["macros"] } -tokio-test = { workspace = true } +tokio-test.workspace = true tower = { workspace = true, features = ["util"] } [package.metadata.docs.rs]