grib-reader 0.3.0

Pure-Rust GRIB Edition 1 and 2 decoder for weather and climate data
Documentation

grib-rust

Pure-Rust GRIB reader, writer, and shared core primitives for weather and climate data. No C libraries, no build scripts, and no unsafe beyond memmap2.

Crates

Crate Description
grib-core Shared GRIB data model, code tables, binary primitives, bit I/O, and validation helpers
grib-reader GRIB1/GRIB2 file opening, message scanning, metadata parsing, and packed data decoding
grib-writer GRIB1/GRIB2 field builders, simple packing, bitmap handling, and message serialization

Reader Usage

use grib_reader::GribFile;

let file = GribFile::open("gfs.grib2")?;
println!("messages: {}", file.message_count());

for msg in file.messages() {
    println!(
        "  {} {:?} {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
        msg.parameter_name(),
        msg.grid_shape(),
        msg.reference_time().year,
        msg.reference_time().month,
        msg.reference_time().day,
        msg.reference_time().hour,
        msg.reference_time().minute,
        msg.reference_time().second,
    );

    if let Some(valid) = msg.valid_time() {
        println!(
            "  valid {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
            valid.year,
            valid.month,
            valid.day,
            valid.hour,
            valid.minute,
            valid.second,
        );
    }
}

let field = file.message(0)?;
let flat = field.read_flat_data_as_f32()?;
println!("decoded values: {}", flat.len());

let mut reused = vec![0.0_f64; field.grid_shape().0 * field.grid_shape().1];
field.decode_into(&mut reused)?;

let data = field.read_data_as_f64()?;
println!("ndarray shape: {:?}", data.shape());

let tolerant = GribFile::from_bytes_with_options(
    std::fs::read("mixed.bin")?,
    grib_reader::OpenOptions { strict: false },
)?;
println!("recoverable messages: {}", tolerant.message_count());

Writer Usage

use grib_core::{
    AnalysisOrForecastTemplate, FixedSurface, GridDefinition, Identification, LatLonGrid,
    ProductDefinition, ProductDefinitionTemplate,
};
use grib_writer::{Grib2FieldBuilder, GribWriter, PackingStrategy};

let grid = GridDefinition::LatLon(LatLonGrid {
    ni: 2,
    nj: 2,
    lat_first: 50_000_000,
    lon_first: -120_000_000,
    lat_last: 49_000_000,
    lon_last: -119_000_000,
    di: 1_000_000,
    dj: 1_000_000,
    scanning_mode: 0,
});
let id = Identification {
    center_id: 7,
    subcenter_id: 0,
    master_table_version: 35,
    local_table_version: 1,
    significance_of_reference_time: 1,
    reference_year: 2026,
    reference_month: 3,
    reference_day: 20,
    reference_hour: 12,
    reference_minute: 0,
    reference_second: 0,
    production_status: 0,
    processed_data_type: 1,
};
let product = ProductDefinition {
    parameter_category: 0,
    parameter_number: 0,
    template: ProductDefinitionTemplate::AnalysisOrForecast(AnalysisOrForecastTemplate {
        generating_process: 2,
        forecast_time_unit: 1,
        forecast_time: 6,
        first_surface: Some(FixedSurface {
            surface_type: 103,
            scale_factor: 0,
            scaled_value: 850,
        }),
        second_surface: None,
    }),
};

let values = [1.0, 2.0, f64::NAN, 4.0];
let field = Grib2FieldBuilder::new()
    .identification(id)
    .grid(grid)
    .product(product)
    .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
    .values(&values)
    .build()?;

let mut bytes = Vec::new();
GribWriter::new(&mut bytes).write_grib2_message([field])?;

Supported Now

  • GRIB1 and GRIB2 message scanning with "GRIB"/"7777" boundary detection
  • Logical field indexing for multi-field GRIB2 messages
  • Regular latitude/longitude grids for GRIB1 and GRIB2
  • Reader simple packing for GRIB1 and GRIB2
  • GRIB2 complex packing with general group splitting, including spatial differencing
  • WMO parameter table lookups (Code Table 4.2)
  • Typed metadata access for reference time, parameter identity, product metadata, grid geometry, and lat/lon coordinates
  • Forecast valid-time helpers for supported fixed-width GRIB1/GRIB2 time units
  • OpenOptions for strict or tolerant scanning
  • Bitmap application with missing values surfaced as NaN
  • Parallel field decoding via Rayon
  • Output: caller-owned &mut [f32]/&mut [f64], flat Vec<f32>/Vec<f64>, or ndarray::ArrayD<f32>/ArrayD<f64>
  • Memory-mapped I/O or owned byte buffers
  • Writer GRIB2 regular lat/lon fields with product template 4.0 and simple packing template 5.0
  • Writer GRIB2 bitmap section generation from explicit masks or NaN values
  • Writer single-message multi-field GRIB2 output with reused grid sections
  • Writer GRIB1 regular lat/lon fields with simple packing and optional bitmap section

Not Yet Supported

  • Non-lat/lon grid templates
  • Writer complex packing templates 5.2/5.3 and row-by-row complex packing
  • JPEG2000 and PNG-packed GRIB2 fields
  • GRIB1 predefined bitmaps

Unsupported cases fail explicitly with typed errors. Calendar-dependent forecast units such as months and years are exposed through raw metadata but currently return None from valid_time().

Feature flags

Flag Default Description
rayon yes Parallel field decoding

Testing

cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo run -p grib-reader --example sync_corpus
git diff --exit-code
cargo test --workspace --all-features
cargo test -p grib-reader --no-default-features
cargo check --manifest-path grib-reader/fuzz/Cargo.toml --bins
cargo clippy --manifest-path grib-reader/fuzz/Cargo.toml --bins -- -D warnings
cargo package --workspace --locked

Reference compatibility checks are intentionally outside default PR CI:

./scripts/run-reference-parity.sh

Release Checklist

git switch main
git pull --ff-only
git merge <release-branch>

cargo package --workspace --locked
cargo publish -p grib-core --dry-run --locked
cargo publish -p grib-reader --dry-run --locked
cargo publish -p grib-core --locked
cargo publish -p grib-reader --locked
cargo publish -p grib-writer --dry-run --locked
cargo publish -p grib-writer --locked

git tag v<version>
git push origin main
git push origin v<version>

Corpus And Fuzzing

  • Bootstrap corpus samples live in grib-reader/tests/corpus/bootstrap/
  • Real interoperability samples belong in grib-reader/tests/corpus/interop/samples/
  • Regenerate the bootstrap and fuzz seed corpora with cargo run -p grib-reader --example sync_corpus
  • Fuzzer entry points and usage notes live in grib-reader/fuzz/README.md

Reference Checks

  • ./scripts/run-reference-parity.sh runs the Dockerized ecCodes parity suite.
  • grib-writer has a versioned dev-dependency on grib-reader for local validation tests and benchmarks, so dry-run and publish it after grib-reader v0.3.0 is visible in the crates.io index.
  • For reference comparisons and current benchmark results against ecCodes, see docs/benchmark-report.md. Re-run the benchmark scripts after corpus changes before using those numbers as current throughput claims.

License

MIT OR Apache-2.0