Skip to main content

grib_core/
grib1.rs

1//! GRIB Edition 1 shared metadata and section models.
2
3use crate::binary::decode_ibm_f32;
4use crate::data::{DataRepresentation, SimplePackingParams};
5use crate::error::{Error, Result};
6use crate::grid::{GridDefinition, LatLonGrid};
7use crate::metadata::{Parameter, ReferenceTime};
8use crate::parameter;
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(u32::from(self.p1)),
82            10 => Some(u32::from(u16::from_be_bytes([self.p1, self.p2]))),
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(u16::from(other)),
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 = decode_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
182fn parse_reference_time(section_bytes: &[u8]) -> Result<ReferenceTime> {
183    let century = section_bytes[24];
184    let year_of_century = u16::from(section_bytes[12]);
185    let year = match century {
186        0 => year_of_century,
187        c => (u16::from(c) - 1) * 100 + year_of_century,
188    };
189
190    let reference_time = ReferenceTime {
191        year,
192        month: section_bytes[13],
193        day: section_bytes[14],
194        hour: section_bytes[15],
195        minute: section_bytes[16],
196        second: 0,
197    };
198    reference_time.validate_in_section(1)?;
199    Ok(reference_time)
200}
201
202fn parse_latlon_grid(section_bytes: &[u8]) -> GridDefinition {
203    let ni = u32::from(u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()));
204    let nj = u32::from(u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()));
205    let lat_first = grib_i24(&section_bytes[10..13]).unwrap() * 1_000;
206    let lon_first = grib_i24(&section_bytes[13..16]).unwrap() * 1_000;
207    let lat_last = grib_i24(&section_bytes[17..20]).unwrap() * 1_000;
208    let lon_last = grib_i24(&section_bytes[20..23]).unwrap() * 1_000;
209    let di = u32::from(u16::from_be_bytes(
210        section_bytes[23..25].try_into().unwrap(),
211    )) * 1_000;
212    let dj = u32::from(u16::from_be_bytes(
213        section_bytes[25..27].try_into().unwrap(),
214    )) * 1_000;
215    let scanning_mode = section_bytes[27];
216
217    GridDefinition::LatLon(LatLonGrid {
218        ni,
219        nj,
220        lat_first,
221        lon_first,
222        lat_last,
223        lon_last,
224        di,
225        dj,
226        scanning_mode,
227    })
228}
229
230#[cfg(test)]
231mod tests {
232    use super::ProductDefinition;
233    use crate::error::Error;
234    use crate::metadata::ReferenceTime;
235
236    #[test]
237    fn decodes_indicator_ten_forecast_time_as_u16() {
238        let product = ProductDefinition {
239            table_version: 2,
240            center_id: 7,
241            generating_process_id: 255,
242            grid_id: 0,
243            has_grid_definition: true,
244            has_bitmap: false,
245            parameter_number: 11,
246            level_type: 100,
247            level_value: 850,
248            reference_time: ReferenceTime {
249                year: 2026,
250                month: 3,
251                day: 20,
252                hour: 12,
253                minute: 0,
254                second: 0,
255            },
256            forecast_time_unit: 1,
257            p1: 0x01,
258            p2: 0x2c,
259            time_range_indicator: 10,
260            average_count: 0,
261            missing_count: 0,
262            century: 21,
263            subcenter_id: 0,
264            decimal_scale: 0,
265        };
266
267        assert_eq!(product.forecast_time(), Some(300));
268    }
269
270    #[test]
271    fn rejects_invalid_product_definition_reference_time() {
272        let mut section = valid_product_definition_section();
273        section[13] = 2;
274        section[14] = 29;
275        let err = ProductDefinition::parse(&section).unwrap_err();
276        assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
277        assert!(err.to_string().contains("invalid reference timestamp"));
278
279        let mut section = valid_product_definition_section();
280        section[15] = 24;
281        let err = ProductDefinition::parse(&section).unwrap_err();
282        assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
283
284        let mut section = valid_product_definition_section();
285        section[16] = 60;
286        let err = ProductDefinition::parse(&section).unwrap_err();
287        assert!(matches!(err, Error::InvalidSection { section: 1, .. }));
288    }
289
290    fn valid_product_definition_section() -> Vec<u8> {
291        let mut section = vec![0u8; 28];
292        section[..3].copy_from_slice(&[0, 0, 28]);
293        section[3] = 2;
294        section[4] = 7;
295        section[5] = 255;
296        section[6] = 0;
297        section[7] = 0b1000_0000;
298        section[8] = 11;
299        section[9] = 100;
300        section[10..12].copy_from_slice(&850u16.to_be_bytes());
301        section[12] = 26;
302        section[13] = 3;
303        section[14] = 20;
304        section[15] = 12;
305        section[16] = 0;
306        section[17] = 1;
307        section[18] = 0;
308        section[19] = 0;
309        section[20] = 0;
310        section[24] = 21;
311        section[25] = 0;
312        section
313    }
314}