grib-reader 0.4.0

Pure-Rust GRIB Edition 1 and 2 decoder for weather and climate data
Documentation
#![allow(dead_code)]

use grib_core::binary::encode_wmo_i32;

pub fn build_grib2_message(values: &[u8]) -> Vec<u8> {
    build_grib2_message_with_forecast(values, 0)
}

pub fn build_grib2_message_with_forecast(values: &[u8], forecast_time: u32) -> Vec<u8> {
    match (values, forecast_time) {
        ([1, 2, 3, 4], 0) => MINIMAL_GRIB2.to_vec(),
        ([1, 2, 3, 4], 18) => FORECAST_GRIB2.to_vec(),
        ([1, 2, 3, 4], 30) => GRIB2_FORECAST_30.to_vec(),
        ([9, 8, 7, 6], 0) => GRIB2_9876.to_vec(),
        ([0, 10, 20, 30], 0) => GRIB2_0_10_20_30.to_vec(),
        ([55, 0, 128, 128], 0) => GRIB2_INTERNAL_MARKER_BASE.to_vec(),
        _ => panic!("unsupported fixed GRIB2 fixture request: {values:?} forecast={forecast_time}"),
    }
}

pub fn build_grib2_multifield_message() -> Vec<u8> {
    MULTIFIELD_GRIB2.to_vec()
}

pub fn build_bitmap_prefixed_stream() -> Vec<u8> {
    let mut bytes = b"junkGRIB\x00\x00\x00\x02not-a-real-message".to_vec();
    bytes.extend_from_slice(&build_grib2_message(&[9, 8, 7, 6]));
    bytes
}

pub fn build_truncated_grib2_message() -> Vec<u8> {
    let message = build_grib2_message(&[1, 2, 3, 4]);
    message[..message.len() - 2].to_vec()
}

pub fn build_grib1_bitmap_message() -> Vec<u8> {
    build_grib1_message_with_bitmap(&[9, 7], 3, 1, Some(&[0b1010_0000]))
}

pub fn build_grib1_message(values: &[u8]) -> Vec<u8> {
    match values {
        [1, 2, 3, 4] => GRIB1_1234.to_vec(),
        [5, 6, 7, 8] => MINIMAL_GRIB1.to_vec(),
        _ => panic!("unsupported fixed GRIB1 fixture request: {values:?}"),
    }
}

pub fn build_grib1_message_with_bitmap(
    values: &[u8],
    ni: u16,
    nj: u16,
    bitmap_payload: Option<&[u8]>,
) -> Vec<u8> {
    match (values, ni, nj, bitmap_payload) {
        ([9, 7], 3, 1, Some([0b1010_0000])) => BITMAP_GRIB1.to_vec(),
        ([9, 7], 3, 1, Some([0b1011_1111])) => {
            let mut bytes = BITMAP_GRIB1.to_vec();
            bytes[GRIB1_BITMAP_PAYLOAD_OFFSET] = 0b1011_1111;
            bytes
        }
        (_, _, _, None) => build_grib1_message(values),
        _ => panic!(
            "unsupported fixed GRIB1 bitmap fixture request: values={values:?} ni={ni} nj={nj} bitmap={bitmap_payload:?}"
        ),
    }
}

pub fn build_grib2_complex_packing_message() -> Vec<u8> {
    COMPLEX_PACKING_MESSAGE.to_vec()
}

pub fn build_grib2_complex_packing_message_with_missing() -> Vec<u8> {
    COMPLEX_PACKING_WITH_MISSING_MESSAGE.to_vec()
}

pub fn build_grib2_spatial_differencing_message() -> Vec<u8> {
    SPATIAL_DIFFERENCING_MESSAGE.to_vec()
}

pub fn build_grib2_lambert_message() -> Vec<u8> {
    build_grib2_lambert_message_with_scanning_mode(0, &[1, 2, 3, 4, 5, 6])
}

pub fn build_grib2_lambert_alternating_message() -> Vec<u8> {
    build_grib2_lambert_message_with_scanning_mode(0b0001_0000, &[1, 2, 3, 6, 5, 4])
}

fn build_grib2_lambert_message_with_scanning_mode(scanning_mode: u8, values: &[u8]) -> Vec<u8> {
    let sections = [
        build_identification_section(),
        build_lambert_grid_section(scanning_mode),
        build_product_section(),
        build_simple_representation_section(values.len(), 8),
        build_data_section(values),
    ];
    assemble_grib2_message(&sections)
}

fn build_identification_section() -> Vec<u8> {
    let mut section = vec![0u8; 21];
    section[..4].copy_from_slice(&21u32.to_be_bytes());
    section[4] = 1;
    section[5..7].copy_from_slice(&7u16.to_be_bytes());
    section[7..9].copy_from_slice(&0u16.to_be_bytes());
    section[9] = 35;
    section[10] = 1;
    section[11] = 1;
    section[12..14].copy_from_slice(&2026u16.to_be_bytes());
    section[14] = 3;
    section[15] = 20;
    section[16] = 12;
    section[19] = 0;
    section[20] = 1;
    section
}

fn build_lambert_grid_section(scanning_mode: u8) -> Vec<u8> {
    let mut section = vec![0u8; 81];
    section[..4].copy_from_slice(&81u32.to_be_bytes());
    section[4] = 3;
    section[6..10].copy_from_slice(&6u32.to_be_bytes());
    section[12..14].copy_from_slice(&30u16.to_be_bytes());
    section[14] = 1;
    section[16..20].copy_from_slice(&6_371_200u32.to_be_bytes());
    section[30..34].copy_from_slice(&3u32.to_be_bytes());
    section[34..38].copy_from_slice(&2u32.to_be_bytes());
    section[38..42].copy_from_slice(&encode_wmo_i32(12_190_000).unwrap());
    section[42..46].copy_from_slice(&226_541_000u32.to_be_bytes());
    section[46] = 0x08;
    section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
    section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
    section[55..59].copy_from_slice(&2_539_703u32.to_be_bytes());
    section[59..63].copy_from_slice(&2_539_703u32.to_be_bytes());
    section[64] = scanning_mode;
    section[65..69].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
    section[69..73].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
    section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
    section
}

fn build_product_section() -> Vec<u8> {
    let mut section = vec![0u8; 34];
    section[..4].copy_from_slice(&34u32.to_be_bytes());
    section[4] = 4;
    section[7..9].copy_from_slice(&0u16.to_be_bytes());
    section[9] = 0;
    section[10] = 0;
    section[11] = 2;
    section[17] = 1;
    section[22] = 103;
    section[24..28].copy_from_slice(&850u32.to_be_bytes());
    section[28] = 255;
    section
}

fn build_simple_representation_section(encoded_values: usize, bits_per_value: u8) -> Vec<u8> {
    let mut section = vec![0u8; 21];
    section[..4].copy_from_slice(&21u32.to_be_bytes());
    section[4] = 5;
    section[5..9].copy_from_slice(&(encoded_values as u32).to_be_bytes());
    section[9..11].copy_from_slice(&0u16.to_be_bytes());
    section[11..15].copy_from_slice(&0f32.to_be_bytes());
    section[19] = bits_per_value;
    section
}

fn build_data_section(payload: &[u8]) -> Vec<u8> {
    let mut section = vec![0u8; payload.len() + 5];
    section[..4].copy_from_slice(&((payload.len() + 5) as u32).to_be_bytes());
    section[4] = 7;
    section[5..].copy_from_slice(payload);
    section
}

fn assemble_grib2_message(sections: &[Vec<u8>]) -> Vec<u8> {
    let total_len = 16 + sections.iter().map(Vec::len).sum::<usize>() + 4;
    let mut message = Vec::new();
    message.extend_from_slice(b"GRIB");
    message.extend_from_slice(&[0, 0]);
    message.push(0);
    message.push(2);
    message.extend_from_slice(&(total_len as u64).to_be_bytes());
    for section in sections {
        message.extend_from_slice(section);
    }
    message.extend_from_slice(b"7777");
    message
}

const MINIMAL_GRIB2: &[u8] = include_bytes!("../corpus/bootstrap/minimal.grib2");
const FORECAST_GRIB2: &[u8] = include_bytes!("../corpus/bootstrap/forecast.grib2");
const MULTIFIELD_GRIB2: &[u8] = include_bytes!("../corpus/bootstrap/multifield.grib2");
const MINIMAL_GRIB1: &[u8] = include_bytes!("../corpus/bootstrap/minimal.grib1");
const GRIB1_BITMAP_PAYLOAD_OFFSET: usize = 74;
const BITMAP_GRIB1: &[u8] = &[
    71, 82, 73, 66, 0, 0, 91, 1, 0, 0, 28, 2, 7, 255, 0, 192, 11, 100, 3, 82, 26, 3, 20, 12, 0, 1,
    0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 32, 0, 255, 0, 0, 3, 0, 1, 0, 195, 80, 129, 212, 192, 128,
    0, 195, 80, 129, 204, 240, 3, 232, 3, 232, 0, 0, 0, 0, 0, 0, 0, 7, 5, 0, 0, 160, 0, 0, 12, 4,
    0, 0, 65, 112, 0, 0, 2, 128, 55, 55, 55, 55,
];

const GRIB1_1234: &[u8] = &[
    71, 82, 73, 66, 0, 0, 84, 1, 0, 0, 28, 2, 7, 255, 0, 128, 11, 100, 3, 82, 26, 3, 20, 12, 0, 1,
    0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 32, 0, 255, 0, 0, 2, 0, 2, 0, 195, 80, 129, 212, 192, 128,
    0, 191, 104, 129, 208, 216, 3, 232, 3, 232, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 65, 16, 0, 0, 2,
    27, 55, 55, 55, 55,
];

const GRIB2_9876: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 255, 255, 255,
    255, 255, 0, 0, 0, 21, 5, 0, 0, 0, 4, 0, 0, 64, 192, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 6, 7,
    228, 55, 55, 55, 55,
];

const GRIB2_0_10_20_30: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 176, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 255, 255, 255,
    255, 255, 0, 0, 0, 21, 5, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 8, 7, 2,
    169, 224, 55, 55, 55, 55,
];

const GRIB2_INTERNAL_MARKER_BASE: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 177, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 255, 255, 255,
    255, 255, 0, 0, 0, 21, 5, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 9, 7, 55, 0,
    128, 128, 55, 55, 55, 55,
];

const GRIB2_FORECAST_30: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30, 103, 0, 0, 0, 3, 82, 255, 255, 255, 255,
    255, 255, 0, 0, 0, 21, 5, 0, 0, 0, 4, 0, 0, 63, 128, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 6, 7, 27,
    55, 55, 55, 55,
];

// Complex packing templates remain fixed reader fixtures until grib-writer
// supports templates 5.2/5.3; do not reintroduce test-only encoders here.
const COMPLEX_PACKING_MESSAGE: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 0, 0, 0, 0, 0,
    0, 0, 0, 47, 5, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 255, 255, 255, 255, 255,
    255, 255, 255, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 0, 0, 0, 8, 7, 116, 112, 68, 55,
    55, 55, 55,
];

const COMPLEX_PACKING_WITH_MISSING_MESSAGE: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 0, 0, 0, 0, 0,
    0, 0, 0, 47, 5, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 1, 255, 255, 255, 255, 255,
    255, 255, 255, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 0, 0, 0, 8, 7, 121, 144, 52, 55,
    55, 55, 55,
];

const SPATIAL_DIFFERENCING_MESSAGE: &[u8] = &[
    71, 82, 73, 66, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 208, 0, 0, 0, 21, 1, 0, 7, 0, 0, 35, 1, 1, 7,
    234, 3, 20, 12, 0, 0, 0, 1, 0, 0, 0, 72, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 250, 240, 128, 135,
    39, 14, 0, 0, 2, 235, 174, 64, 135, 23, 203, 192, 0, 15, 66, 64, 0, 15, 66, 64, 0, 0, 0, 0, 34,
    4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 103, 0, 0, 0, 3, 82, 255, 0, 0, 0, 0, 0,
    0, 0, 0, 49, 5, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 255, 255, 255, 255, 255,
    255, 255, 255, 0, 0, 0, 2, 0, 1, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 12, 7, 0, 10, 0,
    2, 16, 64, 64, 55, 55, 55, 55,
];