Skip to main content

grib_reader/
grib1.rs

1//! GRIB Edition 1 parsing.
2
3use crate::data::{decode_payload, DataRepresentation, SimplePackingParams};
4use crate::error::{Error, Result};
5use crate::grid::{GridDefinition, LatLonGrid};
6use crate::metadata::{Parameter, ReferenceTime};
7use crate::parameter;
8use crate::sections::SectionRef;
9use crate::util::{grib_i16, grib_i24};
10
11/// GRIB1 product definition metadata.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ProductDefinition {
14    pub table_version: u8,
15    pub center_id: u8,
16    pub generating_process_id: u8,
17    pub grid_id: u8,
18    pub has_grid_definition: bool,
19    pub has_bitmap: bool,
20    pub parameter_number: u8,
21    pub level_type: u8,
22    pub level_value: u16,
23    pub reference_time: ReferenceTime,
24    pub forecast_time_unit: u8,
25    pub p1: u8,
26    pub p2: u8,
27    pub time_range_indicator: u8,
28    pub average_count: u16,
29    pub missing_count: u8,
30    pub century: u8,
31    pub subcenter_id: u8,
32    pub decimal_scale: i16,
33}
34
35impl ProductDefinition {
36    pub fn parse(section_bytes: &[u8]) -> Result<Self> {
37        if section_bytes.len() < 28 {
38            return Err(Error::InvalidSection {
39                section: 1,
40                reason: format!("expected at least 28 bytes, got {}", section_bytes.len()),
41            });
42        }
43
44        Ok(Self {
45            table_version: section_bytes[3],
46            center_id: section_bytes[4],
47            generating_process_id: section_bytes[5],
48            grid_id: section_bytes[6],
49            has_grid_definition: section_bytes[7] & 0b1000_0000 != 0,
50            has_bitmap: section_bytes[7] & 0b0100_0000 != 0,
51            parameter_number: section_bytes[8],
52            level_type: section_bytes[9],
53            level_value: u16::from_be_bytes(section_bytes[10..12].try_into().unwrap()),
54            reference_time: parse_reference_time(section_bytes)?,
55            forecast_time_unit: section_bytes[17],
56            p1: section_bytes[18],
57            p2: section_bytes[19],
58            time_range_indicator: section_bytes[20],
59            average_count: u16::from_be_bytes(section_bytes[21..23].try_into().unwrap()),
60            missing_count: section_bytes[23],
61            century: section_bytes[24],
62            subcenter_id: section_bytes[25],
63            decimal_scale: grib_i16(&section_bytes[26..28]).unwrap(),
64        })
65    }
66
67    pub fn parameter(&self) -> Parameter {
68        let short_name = parameter::grib1_parameter_name(self.table_version, self.parameter_number);
69        let description =
70            parameter::grib1_parameter_description(self.table_version, self.parameter_number);
71        Parameter::new_grib1(
72            self.table_version,
73            self.parameter_number,
74            short_name,
75            description,
76        )
77    }
78
79    pub fn forecast_time(&self) -> Option<u32> {
80        match self.time_range_indicator {
81            0 | 1 => Some(self.p1 as u32),
82            10 => Some(u16::from_be_bytes([self.p1, self.p2]) as u32),
83            _ => None,
84        }
85    }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub struct GridDescription {
90    pub nv: u8,
91    pub pv_or_pl: u8,
92    pub data_representation_type: u8,
93    pub grid: GridDefinition,
94}
95
96impl GridDescription {
97    pub fn parse(section_bytes: &[u8]) -> Result<Self> {
98        if section_bytes.len() < 32 {
99            return Err(Error::InvalidSection {
100                section: 2,
101                reason: format!("expected at least 32 bytes, got {}", section_bytes.len()),
102            });
103        }
104
105        let data_representation_type = section_bytes[5];
106        let grid = match data_representation_type {
107            0 => parse_latlon_grid(section_bytes)?,
108            other => GridDefinition::Unsupported(other as u16),
109        };
110
111        Ok(Self {
112            nv: section_bytes[3],
113            pv_or_pl: section_bytes[4],
114            data_representation_type,
115            grid,
116        })
117    }
118}
119
120#[derive(Debug, Clone, PartialEq)]
121pub struct BinaryDataSection {
122    pub flags: u8,
123    pub unused_bits: u8,
124    pub binary_scale: i16,
125    pub reference_value: f32,
126    pub bits_per_value: u8,
127}
128
129impl BinaryDataSection {
130    pub fn parse(
131        section_bytes: &[u8],
132        decimal_scale: i16,
133        encoded_values: usize,
134    ) -> Result<(Self, DataRepresentation)> {
135        if section_bytes.len() < 11 {
136            return Err(Error::InvalidSection {
137                section: 4,
138                reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
139            });
140        }
141
142        let data_flag = section_bytes[3];
143        let flags = data_flag >> 4;
144        if flags & 0b1000 != 0 {
145            return Err(Error::UnsupportedDataTemplate(1004));
146        }
147        if flags & 0b0100 != 0 {
148            return Err(Error::UnsupportedDataTemplate(1005));
149        }
150        if flags & 0b0010 != 0 {
151            return Err(Error::UnsupportedDataTemplate(1006));
152        }
153        if flags & 0b0001 != 0 {
154            return Err(Error::UnsupportedDataTemplate(1007));
155        }
156
157        let binary_scale = grib_i16(&section_bytes[4..6]).unwrap();
158        let reference_value = ibm_f32(section_bytes[6..10].try_into().unwrap());
159        let bits_per_value = section_bytes[10];
160        let simple = SimplePackingParams {
161            encoded_values,
162            reference_value,
163            binary_scale,
164            decimal_scale,
165            bits_per_value,
166            original_field_type: if flags & 0b0010_0000 != 0 { 1 } else { 0 },
167        };
168
169        Ok((
170            Self {
171                flags,
172                unused_bits: data_flag & 0x0f,
173                binary_scale,
174                reference_value,
175                bits_per_value,
176            },
177            DataRepresentation::SimplePacking(simple),
178        ))
179    }
180}
181
182pub fn bitmap_payload(section_bytes: &[u8]) -> Result<Option<&[u8]>> {
183    if section_bytes.len() < 6 {
184        return Err(Error::InvalidSection {
185            section: 3,
186            reason: format!("expected at least 6 bytes, got {}", section_bytes.len()),
187        });
188    }
189    let indicator = u16::from_be_bytes(section_bytes[4..6].try_into().unwrap());
190    if indicator == 0 {
191        Ok(Some(&section_bytes[6..]))
192    } else {
193        Err(Error::UnsupportedBitmapIndicator(
194            if indicator <= u16::from(u8::MAX) {
195                indicator as u8
196            } else {
197                u8::MAX
198            },
199        ))
200    }
201}
202
203pub fn decode_simple_field(
204    data_section: &[u8],
205    representation: &DataRepresentation,
206    bitmap_section: Option<&[u8]>,
207    num_grid_points: usize,
208) -> Result<Vec<f64>> {
209    decode_payload(
210        data_section,
211        representation,
212        bitmap_section,
213        num_grid_points,
214    )
215}
216
217pub fn parse_message_sections(message_bytes: &[u8]) -> Result<Grib1Sections> {
218    if message_bytes.len() < 8 + 28 + 11 + 4 {
219        return Err(Error::InvalidMessage(format!(
220            "GRIB1 message too short: {} bytes",
221            message_bytes.len()
222        )));
223    }
224
225    let payload_limit = message_bytes.len() - 4;
226    let pds = parse_section(message_bytes, 8, 1, payload_limit)?;
227    let pds_bytes = &message_bytes[pds.offset..pds.offset + pds.length];
228    let product = ProductDefinition::parse(pds_bytes)?;
229
230    let mut cursor = pds.offset + pds.length;
231    let grid = if product.has_grid_definition {
232        let section_ref = parse_section(message_bytes, cursor, 2, payload_limit)?;
233        cursor += section_ref.length;
234        Some(section_ref)
235    } else {
236        None
237    };
238
239    let bitmap = if product.has_bitmap {
240        let section_ref = parse_section(message_bytes, cursor, 3, payload_limit)?;
241        cursor += section_ref.length;
242        Some(section_ref)
243    } else {
244        None
245    };
246
247    let data = parse_section(message_bytes, cursor, 4, payload_limit)?;
248    if data.offset + data.length != payload_limit {
249        return Err(Error::InvalidMessage(
250            "GRIB1 message contains trailing bytes before end marker".into(),
251        ));
252    }
253
254    Ok(Grib1Sections {
255        product,
256        pds,
257        grid,
258        bitmap,
259        data,
260    })
261}
262
263#[derive(Debug, Clone)]
264pub struct Grib1Sections {
265    pub product: ProductDefinition,
266    pub pds: SectionRef,
267    pub grid: Option<SectionRef>,
268    pub bitmap: Option<SectionRef>,
269    pub data: SectionRef,
270}
271
272fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
273    let century = section_bytes[24];
274    let year_of_century = section_bytes[12] as u16;
275    let year = match century {
276        0 => year_of_century,
277        c => (c as u16 - 1) * 100 + year_of_century,
278    };
279
280    Ok(ReferenceTime {
281        year,
282        month: section_bytes[13],
283        day: section_bytes[14],
284        hour: section_bytes[15],
285        minute: section_bytes[16],
286        second: 0,
287    })
288}
289
290fn parse_latlon_grid(section_bytes: &[u8]) -> Result<GridDefinition> {
291    let ni = u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()) as u32;
292    let nj = u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()) as u32;
293    let lat_first = grib_i24(&section_bytes[10..13]).unwrap() * 1_000;
294    let lon_first = grib_i24(&section_bytes[13..16]).unwrap() * 1_000;
295    let lat_last = grib_i24(&section_bytes[17..20]).unwrap() * 1_000;
296    let lon_last = grib_i24(&section_bytes[20..23]).unwrap() * 1_000;
297    let di = u16::from_be_bytes(section_bytes[23..25].try_into().unwrap()) as u32 * 1_000;
298    let dj = u16::from_be_bytes(section_bytes[25..27].try_into().unwrap()) as u32 * 1_000;
299    let scanning_mode = section_bytes[27];
300
301    Ok(GridDefinition::LatLon(LatLonGrid {
302        ni,
303        nj,
304        lat_first,
305        lon_first,
306        lat_last,
307        lon_last,
308        di,
309        dj,
310        scanning_mode,
311    }))
312}
313
314fn read_u24(bytes: &[u8]) -> u32 {
315    ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32)
316}
317
318fn parse_section(
319    message_bytes: &[u8],
320    offset: usize,
321    number: u8,
322    payload_limit: usize,
323) -> Result<SectionRef> {
324    let length_bytes = message_bytes
325        .get(offset..offset + 3)
326        .ok_or(Error::Truncated {
327            offset: offset as u64,
328        })?;
329    let length = read_u24(length_bytes) as usize;
330    if length < 3 {
331        return Err(Error::InvalidSection {
332            section: number,
333            reason: format!("section length {length} is smaller than the 3-byte header"),
334        });
335    }
336
337    let end = offset
338        .checked_add(length)
339        .ok_or_else(|| Error::InvalidMessage("GRIB1 section length overflow".into()))?;
340    if end > payload_limit {
341        return Err(Error::Truncated {
342            offset: offset as u64,
343        });
344    }
345
346    Ok(section(number, offset, length))
347}
348
349fn section(number: u8, offset: usize, length: usize) -> SectionRef {
350    SectionRef {
351        number,
352        offset,
353        length,
354    }
355}
356
357fn ibm_f32(bytes: [u8; 4]) -> f32 {
358    if bytes == [0, 0, 0, 0] {
359        return 0.0;
360    }
361
362    let sign = if bytes[0] & 0x80 == 0 { 1.0 } else { -1.0 };
363    let exponent = ((bytes[0] & 0x7f) as i32) - 64;
364    let mantissa = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
365    let value = sign * (mantissa as f64) / 16_777_216.0 * 16f64.powi(exponent);
366    value as f32
367}
368
369#[cfg(test)]
370mod tests {
371    use super::{bitmap_payload, ibm_f32, parse_message_sections, ProductDefinition};
372    use crate::error::Error;
373    use crate::metadata::ReferenceTime;
374
375    #[test]
376    fn decodes_zero_ibm_float() {
377        assert_eq!(ibm_f32([0, 0, 0, 0]), 0.0);
378    }
379
380    #[test]
381    fn parses_minimal_section_layout() {
382        let mut message = Vec::new();
383        message.extend_from_slice(b"GRIB");
384        message.extend_from_slice(&[0, 0, 64, 1]);
385        let mut pds = vec![0u8; 28];
386        pds[..3].copy_from_slice(&[0, 0, 28]);
387        pds[7] = 0b1000_0000;
388        pds[24] = 21;
389        message.extend_from_slice(&pds);
390        let mut gds = vec![0u8; 32];
391        gds[..3].copy_from_slice(&[0, 0, 32]);
392        message.extend_from_slice(&gds);
393        let mut bds = vec![0u8; 12];
394        bds[..3].copy_from_slice(&[0, 0, 12]);
395        message.extend_from_slice(&bds);
396        message.extend_from_slice(b"7777");
397
398        let sections = parse_message_sections(&message).unwrap();
399        assert!(sections.grid.is_some());
400        assert!(sections.bitmap.is_none());
401        assert_eq!(sections.data.length, 12);
402    }
403
404    #[test]
405    fn rejects_section_length_beyond_message_boundary() {
406        let mut message = Vec::new();
407        message.extend_from_slice(b"GRIB");
408        message.extend_from_slice(&[0, 0, 64, 1]);
409        let mut pds = vec![0u8; 28];
410        pds[..3].copy_from_slice(&[0, 0, 28]);
411        pds[7] = 0b1000_0000;
412        pds[24] = 21;
413        message.extend_from_slice(&pds);
414        let mut gds = vec![0u8; 32];
415        gds[..3].copy_from_slice(&[0, 0, 250]);
416        message.extend_from_slice(&gds);
417        let mut bds = vec![0u8; 12];
418        bds[..3].copy_from_slice(&[0, 0, 12]);
419        message.extend_from_slice(&bds);
420        message.extend_from_slice(b"7777");
421
422        assert!(parse_message_sections(&message).is_err());
423    }
424
425    #[test]
426    fn reports_small_predefined_bitmap_indicator() {
427        let err = bitmap_payload(&[0, 0, 6, 0, 0, 5]).unwrap_err();
428        assert!(matches!(err, Error::UnsupportedBitmapIndicator(5)));
429    }
430
431    #[test]
432    fn decodes_indicator_ten_forecast_time_as_u16() {
433        let product = ProductDefinition {
434            table_version: 2,
435            center_id: 7,
436            generating_process_id: 255,
437            grid_id: 0,
438            has_grid_definition: true,
439            has_bitmap: false,
440            parameter_number: 11,
441            level_type: 100,
442            level_value: 850,
443            reference_time: ReferenceTime {
444                year: 2026,
445                month: 3,
446                day: 20,
447                hour: 12,
448                minute: 0,
449                second: 0,
450            },
451            forecast_time_unit: 1,
452            p1: 0x01,
453            p2: 0x2c,
454            time_range_indicator: 10,
455            average_count: 0,
456            missing_count: 0,
457            century: 21,
458            subcenter_id: 0,
459            decimal_scale: 0,
460        };
461
462        assert_eq!(product.forecast_time(), Some(300));
463    }
464}