Welcome to EORST

// 2026-03-19 · Updated 2026-05-11 · 3 min read

Welcome to the EORST blog!

EORST is an open-source Rust library for processing geospatial raster data. Inspired by Python libraries like rasterio and rioxarray, it enables efficient parallel processing of large-scale raster datasets.

New to remote sensing? Start with Getting Started with Geospatial Rust for foundational concepts.

Want a complete workflow? See End-to-End Geospatial Processing with EORST for a code-heavy tutorial.

Benchmarking eorst vs Python+Dask? See Rust vs Python+Dask for NDVI: 23× Faster — real numbers with FMask cloud masking and carbon impact analysis.

Why Rust for Geospatial Processing?

Rust offers several advantages for geospatial computing:

Your First EORST Project

Let’s build a complete NDVI (Normalized Difference Vegetation Index) processing pipeline that queries satellite imagery via STAC and uses semantic band selection!

1. Create a new project

cargo new ndvi_processor
cd ndvi_processor

2. Add dependencies to Cargo.toml

[package]
name = "ndvi_processor"
version = "0.1.0"
edition = "2021"

[dependencies]
eorst = "0.3"
rss_core = "0.4"
ndarray = "0.17"
anyhow = "1.0"
chrono = "0.4"
log = "0.4"
env_logger = "0.11"

3. Write the code

Create src/main.rs:

use anyhow::Result;
use chrono::NaiveDate;
use eorst::{
    init_logger,
    types::BlockSize,
    RasterDataset, RasterDatasetBuilder, Select,
};
use log::info;
use ndarray::{Array4, Zip};
use rss_core::{
    query::ImageQueryBuilder,
    qvf::Collection,
    utils::{Cmp, Intersects},
    DEA,
};
use std::path::PathBuf;

fn main() -> Result<()> {
    init_logger();

    info!("Building NDVI processor...");

    // Step 1: Query Sentinel-2 data from DEA using canonical band names
    let query = ImageQueryBuilder::new(
        DEA,
        Collection::Sentinel2,
        Intersects::Scene(vec!["56jns"]),
    )
    .canonical_bands(["red", "nir"])  // Semantic band names!
    .start_date(NaiveDate::parse_from_str("20210101", "%Y%m%d").unwrap())
    .end_date(NaiveDate::parse_from_str("20210601", "%Y%m%d").unwrap())
    .cloudcover((Cmp::Less, 5))
    .build();

    // Step 2: Download data to temporary directory
    let tmp_dir = PathBuf::from("/tmp/ndvi_data");
    std::fs::create_dir_all(&tmp_dir)?;

    let local_stac = query.get(&tmp_dir, None, None)?;
    info!("Downloaded {} items", local_stac.items.len());

    // Step 3: Build the RasterDataset
    let rds: RasterDataset<i16> = RasterDatasetBuilder::from_stac_query(&local_stac)
        .block_size(BlockSize { rows: 2048, cols: 2048 })
        .build();

    info!("Created dataset:\n{}", rds);

    // Step 4: Process - compute NDVI using apply() and Select trait
    let output_path = PathBuf::from("./ndvi_output.tif");
    rds.apply::<i16>(
        |block| {
            // Select bands by semantic name - no hardcoded indices!
            let red = block.select_layers(&["red"])?;
            let nir = block.select_layers(&["nir"])?;

            let mut ndvi = Array4::zeros(red.data.raw_dim());
            Zip::from(&mut ndvi)
                .and(&red.data)
                .and(&nir.data)
                .for_each(|out, &r, &n| {
                    let val = (n as f32 - r as f32) / (n as f32 + r as f32 + 1e-10);
                    *out = (val * 10000.0) as i16;
                });

            Ok(ndvi)
        },
        4,  // number of threads
        &output_path,
    )?;

    info!("NDVI computation complete! Output: {:?}", output_path);

    Ok(())
}

4. Run it!

# With Nix (recommended - handles all dependencies)
nix develop
cargo run --release

What Just Happened?

  1. Query: We asked DEA for Sentinel-2 data for scene “56jns” between Jan-June 2021 with <5% cloud cover, selecting only the red and NIR bands
  2. Download: STAC client fetched the data and created a local cache
  3. Build: RasterDatasetBuilder created a virtual dataset from the STAC items, aligning bands automatically
  4. Process: The apply() function applied our worker to every block using the Select trait to pick bands by name
  5. Output: Results saved to a GeoTIFF with proper georeferencing

Key Concepts

Concept Description
RasterDataset Main data structure representing a multi-band, multi-temporal raster
RasterDatasetBuilder Fluent API for creating datasets from files, STAC queries, or scratch
apply Parallel processing that passes RasterDataBlock with metadata for name-based selection
Select trait Select bands by semantic name: .select_layers(&["red", "nir"])
canonical_bands Provider-agnostic band names: ["red", "nir"]["nbart_red", "nbart_nir_1"]
RasterDataBlock<T> Typed block with layer names and time indices for metadata-aware processing

Next Steps

Happy processing!

Type to search...