Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

openEO-RuSTAC

openEO Rust License status

A Rust-native openEO 1.3.0 reference backend that streams Sentinel-2 Cloud-Optimized GeoTIFFs (COGs) straight from a STAC catalog and executes openEO process graphs with block-parallel raster compute β€” no Python, no JVM, no Dask.

⚠️ Reference backend, not certified. Single-tenant, opinionated, pinned to openEO 1.3.0. It exists so the existing openEO Python/R/JS ecosystem can drive Rust geo-compute remotely. See apps/orbit-openeo/BACKEND-SCOPE.md for the MAY / WILL-NOT contract.

The server lives in apps/orbit-openeo; the raster engine in crates/orbit-geo; the openEO process-graph AST in crates/eo-process. The repo is a Cargo workspace that also carries an ETL foundation (orbit-etl + CLI/gRPC server) β€” see Monorepo layout.

πŸ™ Credits. The orbit-geo API surface is derived from the JRSRP EORS Workspace (eorst + rss_core, LGPL-3.0). EORS is credited as the upstream this work derives from. See NOTICE.md for full attribution.


Table of contents


✨ Highlights

openEO 1.3.0 REST API Axum server; every request JSON-Schema-validated against the shipped spec/openapi.json.
68 openEO processes Reducers + arbitrary callbacks, merge_cubes (band-join + overlap-resolve), per-pixel apply, standard aggregate_spatial, real filter_temporal/filter_bbox/filter_spatial, 31 scalar math/logic + 9 array processes, cube-metadata ops. Authoritative list: geo_executor/registry.rs::register_defaults (mirrored by src/process_catalog.rs for GET /processes).
P2-full streaming download (default) Pure-Rust async-tiff + object_store COG reads with a STAC band_metadata hint and a shared S3 connection pool β€” no libGDAL on the hot read path.
Cross-CRS, no GDAL fallback bbox reprojection via pure-Rust proj.
Block-parallel compute RasterDataset<f32> tiled, multi-threaded reduction kernels.
DN β†’ reflectance honours STAC raster:bands.scale/offset so absolute math sees real reflectance.
Job lifecycle SqliteJobStore, orphan recovery on startup, per-job timeout.
Observability per-job download_s / mask_s / compute_s phase timing + STAC-hint telemetry.

πŸ— Architecture

                       openEO client  (Python / R / JS / curl)
                                   β”‚  HTTP β€” openEO 1.3.0 REST
                                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ apps/orbit-openeo β€” Axum HTTP server                                       β”‚
β”‚   β€’ JSON-Schema validation vs spec/openapi.json (at request time)          β”‚
β”‚   β€’ Bearer / Basic / OIDC auth Β· 128 MiB body cap Β· SqliteJobStore         β”‚
β”‚   β€’ ProcessRegistry β†’ ProcessHandler dispatch (+ "did you mean" hints)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚  process graph (eo-process AST)
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GeoExecutor (geo_executor/) β€” evaluates each node in topological order     β”‚
β”‚   load_collection Β· mask_scl_dilation Β· ndvi Β· reduce_dimension Β·          β”‚
β”‚   apply Β· merge_cubes Β· filter_bands Β· rename_labels Β· save_result Β· …      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚ STAC search (Element84 Earth Search)    β”‚ block-parallel compute
                β–Ό                                          β–Ό  (ndarray, N threads)
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ COG download                  β”‚          β”‚ crates/orbit-geo               β”‚
   β”‚   P2  async-tiff + S3 (default)β”‚          β”‚   RasterDataset<f32>, GDAL,    β”‚
   β”‚   P1  in-process libGDAL       β”‚          β”‚   proj, cloud_mask, STAC       β”‚
   β”‚   P3  /vsicurl/ range stream   β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β–Ό   s3://sentinel-cogs (us-west-2) β€” Sentinel-2 L2A COGs

Layering (clean-room re-implementations from the openEO spec):

  • crates/eo-process β€” openEO process-graph AST + executor trait surface.
  • crates/eo-kernel, eo-mask, eo-catalog, eo-vector, eo-io, eo-core β€” compute/IO building blocks.
  • crates/orbit-geo β€” GDAL/async-tiff raster engine, STAC client, providers, cloud-mask, ML.
  • apps/orbit-openeo β€” the HTTP faΓ§ade + GeoExecutor that wires it all together.

πŸš€ Quick start (end-to-end)

Prerequisites

  • Rust (2024 edition, stable toolchain) β€” install via rustup.
  • GDAL (for the geo-kernel feature) β€” brew install gdal (macOS) / apt-get install libgdal-dev (Debian/Ubuntu).
  • PROJ (for P2-full cross-CRS) β€” bundled with GDAL on most platforms; else brew install proj / apt-get install libproj-dev.

1. Build (with the P2-full streaming downloader)

cd path/to/orbit-etl
cargo build -p orbit-openeo --features async-tiff-downloader
# binary: ./target/debug/orbit-openeo

2. Run the server

mkdir -p /tmp/orbit-files
ORBIT_DOWNLOAD_CONCURRENCY=4 RUST_LOG=info \
./target/debug/orbit-openeo \
  --bind 127.0.0.1:9080 \
  --executor geo \
  --files-dir /tmp/orbit-files \
  --stac-url https://earth-search.aws.element84.com/v1

Binding to a loopback address needs no auth. A non-loopback bind refuses to start without --auth-token / ORBIT_OPENEO_AUTH_TOKEN (security default).

3. Submit a process graph, poll, fetch the result

openEO batch jobs are 3 calls: create β†’ start β†’ poll. The job id comes back in the OpenEO-Identifier response header.

PORT=9080
GRAPH=apps/orbit-openeo/examples/complex_15node_environmental_real_s2.json

# create the job β†’ capture the job id from the OpenEO-Identifier header
JOB=$(curl -s -i -X POST "http://127.0.0.1:$PORT/jobs" \
        -H 'content-type: application/json' --data-binary @"$GRAPH" \
      | grep -i '^openeo-identifier:' | awk '{print $2}' | tr -d '\r\n')
echo "job: $JOB"

# start it (async execution)
curl -s -X POST "http://127.0.0.1:$PORT/jobs/$JOB/results" -o /dev/null

# poll until finished / error
until [ "$(curl -s "http://127.0.0.1:$PORT/jobs/$JOB" | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')" \
        != "running" ]; do sleep 2; done

# the rendered PNG lands under <files-dir>/<job-id>/
open /tmp/orbit-files/$JOB/result.png   # macOS; use xdg-open on Linux

A full Sentinel-2 graph over a small AOI typically finishes in ~30–60 s (download-bound β€” see Observability).


βš™ Configuration

CLI flags (each also reads an env var)

Flag Env var Default Purpose
--bind ORBIT_OPENEO_BIND 127.0.0.1:9080 HTTP bind address.
--executor ORBIT_OPENEO_EXECUTOR geo geo (real raster compute) or local (JSON-only, CI).
--files-dir ORBIT_OPENEO_FILES_DIR (in-memory) Where job results (<id>/result.png) are written.
--stac-url ORBIT_OPENEO_STAC_URL Element84 Earth Search v1 STAC API backing /collections. Empty = disabled.
--auth-token ORBIT_OPENEO_AUTH_TOKEN (unset) Bearer token; required for non-loopback binds.
--backend-id ORBIT_OPENEO_BACKEND_ID orbit-rs Reported in the capabilities document.
--db-url ORBIT_OPENEO_DB_URL (in-memory) e.g. sqlite://./jobs.db?mode=rwc for persistent jobs.

Runtime tuning (environment only)

Env var Default Effect
ORBIT_INPROCESS_DOWNLOADER=1 (off) Opt out of P2-full to the in-process libGDAL path (P1).
ORBIT_DOWNLOAD_CONCURRENCY 8 Max simultaneous COG fetches. 4–6 is the S3 sweet spot.
ORBIT_S3_MAX_RETRIES 3 Per-request retry cap (vs object_store default 10).
ORBIT_S3_RETRY_TIMEOUT_SECS 60 Retry-loop budget per request (vs default 180).
ORBIT_S3_REQUEST_TIMEOUT_SECS 120 Per-request wall-clock.
ORBIT_S3_CONNECT_TIMEOUT_SECS 10 TCP connect timeout.
ORBIT_JOB_TIMEOUT_SECS 600 Per-job execution timeout β†’ marks the job error.
ORBIT_SCRATCH_DIR (auto temp) Pin scratch to a dir; preserved on exit (debug / value-verification).
ORBIT_VSICURL_STREAM=1 (off) P3: skip downloads, emit /vsicurl/ paths (block-level range reads).

πŸ“– The deep runbook (P1/P2/P3 recipes, the "14 s" best-practice config, foot-guns) lives in CLAUDE.md.


⬇ Download paths (P1 / P2 / P3)

Path How Use when
P2-full (default) async-tiff + object_store streaming COG reads, STAC band_metadata hint (skips IFD round-trips), shared S3 pool, cross-CRS via proj. The default β€” fastest, no libGDAL on the read path.
P1 In-process gdal::Dataset cropped reads. S3 transport instability, hard p99 SLA, or a STAC backend without proj:* extensions. Set ORBIT_INPROCESS_DOWNLOADER=1.
P3 libGDAL /vsicurl/ range reads from worker threads (no pre-download). Experimental block-level streaming. Set ORBIT_VSICURL_STREAM=1.

Confirm the active path in the logs: downloader: async-tiff + object_store + STAC hint (P2-full, default; …).


🧩 Supported processes

68 processes as of 2026-06-03 (authoritative: apps/orbit-openeo/src/geo_executor/registry.rs::register_defaults):

Category Processes
Data access load_collection (DN→reflectance), save_result (GeoTIFF, PNG)
Cube structure filter_bands, filter_temporal, filter_spatial, filter_bbox, rename_labels, add_dimension, drop_dimension, resample_spatial
Reduce / combine reduce_dimension (mean/min/max/sum/median/count/first/last/sd/variance + arbitrary callbacks, over t and bands), merge_cubes (band-join Β· overlap-resolver Β· spatial mosaic), aggregate_spatial (+ aggregate_spatial_* extensions)
Per-pixel apply (sub-graph over all bands), ndvi, normalized_difference
Masking mask, mask_scl_dilation (per-band SCL resample), mask_from_values
Math (31) absolute sqrt exp ln log power sgn floor ceil int round mod clip cos sin tan arccos arcsin arctan arctan2 …
Logic / comparison eq neq gt gte lt lte between and or xor not
Arrays (9) array_element array_create array_concat array_append array_contains array_find count order sort
Analysis zonal_histogram, fit_classifier, predict_classifier

Unknown processes return an UnknownProcess error with a Levenshtein "did you mean" suggestion.


🌐 HTTP API surface

Method Path Purpose
GET /.well-known/openeo Β· / Β· /conformance Capabilities / version / conformance discovery
GET /collections Β· /collections/{id} STAC collections (proxied from --stac-url)
GET /processes Advertised process list (full descriptions)
GET /file_formats Β· /output_formats Β· /service_types Β· /udf_runtimes Capability docs (/output_formats is a pre-1.0 alias)
POST /validation Validate a graph (schema + unsupported-process check) without running it
POST /jobs Create a batch job (returns OpenEO-Identifier header; id is a v4 UUID)
GET PATCH DELETE /jobs/{id} Job status / update / delete
POST /jobs/{id}/results Start execution
GET /jobs/{id}/results Β· /jobs/{id}/results/{asset} Result STAC Item + assets
GET /jobs/{id}/estimate Β· /jobs/{id}/logs Cost/size estimate Β· per-job log entries
GET /process_graphs Β· GET/PUT/DELETE /process_graphs/{id} User-defined process graphs (UDPs)
GET /credentials/basic Β· /credentials/oidc Β· POST /credentials/oidc/token Auth
GET /me Authenticated user info

πŸ“‚ Example process graphs

In apps/orbit-openeo/examples/ (all hit live Sentinel-2 over a Vienna AOI):

Graph Demonstrates
ndvi_mean_png_real_s2.json Minimal: load β†’ NDVI β†’ temporal mean β†’ PNG
masked_ndvi_png_real_s2.json + mask_scl_dilation cloud masking
complex_15node_environmental_real_s2.json Full tour: merge_cubes (band-join + overlap-resolver), compound reducer, reduce_dimension(bands), rename_labels, apply/clip
complex_branching_diamond_real_s2.json Branching DAG (vegetation Γ— moisture diamond)

πŸ§ͺ Testing

# full lib suite for the backend (543 tests)
cargo test -p orbit-openeo --features geo-kernel,async-tiff-downloader --lib

# whole workspace
cargo test --workspace

Discipline is TDD (RED β†’ GREEN β†’ REFACTOR) β€” see CLAUDE.md Β§8.


πŸ“ˆ Observability

Every job logs a phase breakdown at INFO on completion:

phase timing β€” per-node-category wall (download=load_collection, mask=mask_scl_dilation, compute=rest)
  download_s=25.55 mask_s=1.77 compute_s=1.32 total_s=28.63 nodes=15

On a typical Sentinel-2 graph this shows ~89 % download / ~5 % compute β€” wall time is S3-I/O-bound, not CPU-bound. STAC-hint telemetry (hint_dispatched=N hint_missing=0) confirms the P2 fast path is live.


🐳 Docker deployment

The multi-stage Dockerfile builds and ships the orbit-openeo server (GDAL-backed, default geo-kernel feature) on a slim Debian runtime:

docker build -t orbit-openeo:latest .
docker run --rm -p 9080:9080 orbit-openeo:latest \
  --bind 0.0.0.0:9080 --executor geo --auth-token "$TOKEN" \
  --stac-url https://earth-search.aws.element84.com/v1

A non-loopback --bind (0.0.0.0) requires --auth-token, or the server refuses to start (security default). The image ships the stable P1 (libgdal) download path; for the faster P2-full streaming path, add --features async-tiff-downloader to the builder stage and libproj-dev to its apt list.


πŸ—‚ Monorepo layout

This is a Cargo workspace (resolver = "3", edition 2024). The openEO backend is the headline app; the rest is the orbit-rs foundation it grew out of.

apps/
  orbit-openeo/   ← openEO 1.3.0 HTTP backend  (this project's star)
  orbit-server/   ← ETL gRPC server (Tonic)
  orbit-cli/      ← ETL + `orbit geo …` CLI (clap)
crates/
  orbit-geo/      ← raster engine: GDAL + async-tiff, STAC, proj, cloud-mask, ML
  eo-process/     ← openEO process-graph AST + executor trait
  eo-kernel/ eo-mask/ eo-catalog/ eo-vector/ eo-io/ eo-core/   ← EO building blocks
  orbit-etl/      ← Polars β†’ SQLite ETL engine (Phase-1 MVP foundation)
  orbit-core/ orbit-proto/ orbit-cache/ orbit-resilience/ orbit-observability/ orbit-config/

The ETL foundation (orbit-etl + orbit-server + orbit-cli) runs a File β†’ Polars β†’ SQLite pipeline with a gRPC service and a progress-bar CLi; see crates/orbit-etl and apps/orbit-cli for its usage.


πŸ“œ Scope, roadmap & license

  • Scope contract: apps/orbit-openeo/BACKEND-SCOPE.md β€” what the backend MAY and WILL NOT do. Reference backend, not certified.
  • Changelog: CHANGELOG.md (Keep-a-Changelog).
  • Deferred work / decisions: CLAUDE.md Β§9 β€” e.g. consolidating the STAC searcher onto orbit-geo::StacClient (rustac), apply_kernel, aggregate_temporal_period.
  • Attribution: clean-room re-implementations from the openEO spec β€” see NOTICE.md and THIRD_PARTY.md.

License: MIT.

Releases

Packages

Contributors

Languages