Skip to main content

grib_core/
data.rs

1//! Data Representation Section (Section 5) shared model.
2
3use crate::error::{Error, Result};
4use crate::util::grib_i16;
5
6/// Data representation template number and parameters.
7#[derive(Debug, Clone, PartialEq)]
8pub enum DataRepresentation {
9    /// Template 5.0: Simple packing.
10    SimplePacking(SimplePackingParams),
11    /// Template 5.2/5.3: Complex packing with optional spatial differencing.
12    ComplexPacking(ComplexPackingParams),
13    /// Template 5.40: JPEG 2000 code stream packing.
14    Jpeg2000Packing(Jpeg2000PackingParams),
15    /// Template 5.41: PNG image packing.
16    PngPacking(PngPackingParams),
17    /// Unsupported template.
18    Unsupported(u16),
19}
20
21/// Parameters for simple packing (Template 5.0).
22#[derive(Debug, Clone, PartialEq)]
23pub struct SimplePackingParams {
24    pub encoded_values: usize,
25    pub reference_value: f32,
26    pub binary_scale: i16,
27    pub decimal_scale: i16,
28    pub bits_per_value: u8,
29    pub original_field_type: u8,
30}
31
32/// Parameters shared by image-backed grid point packing templates.
33#[derive(Debug, Clone, PartialEq)]
34pub struct ImagePackingParams {
35    pub encoded_values: usize,
36    pub reference_value: f32,
37    pub binary_scale: i16,
38    pub decimal_scale: i16,
39    pub bits_per_value: u8,
40    pub original_field_type: u8,
41}
42
43/// Parameters for JPEG 2000 code stream packing (Template 5.40).
44#[derive(Debug, Clone, PartialEq)]
45pub struct Jpeg2000PackingParams {
46    pub packing: ImagePackingParams,
47    pub compression_type: u8,
48    pub target_compression_ratio: u8,
49}
50
51/// Parameters for PNG image packing (Template 5.41).
52#[derive(Debug, Clone, PartialEq)]
53pub struct PngPackingParams {
54    pub packing: ImagePackingParams,
55}
56
57/// Parameters for complex packing (Templates 5.2 and 5.3).
58#[derive(Debug, Clone, PartialEq)]
59pub struct ComplexPackingParams {
60    pub encoded_values: usize,
61    pub reference_value: f32,
62    pub binary_scale: i16,
63    pub decimal_scale: i16,
64    pub group_reference_bits: u8,
65    pub original_field_type: u8,
66    pub group_splitting_method: u8,
67    pub missing_value_management: u8,
68    pub primary_missing_substitute: u32,
69    pub secondary_missing_substitute: u32,
70    pub num_groups: usize,
71    pub group_width_reference: u8,
72    pub group_width_bits: u8,
73    pub group_length_reference: u32,
74    pub group_length_increment: u8,
75    pub true_length_last_group: u32,
76    pub scaled_group_length_bits: u8,
77    pub spatial_differencing: Option<SpatialDifferencingParams>,
78}
79
80/// Parameters specific to template 5.3 spatial differencing.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct SpatialDifferencingParams {
83    pub order: u8,
84    pub descriptor_octets: u8,
85}
86
87impl DataRepresentation {
88    pub fn parse(section_bytes: &[u8]) -> Result<Self> {
89        if section_bytes.len() < 11 {
90            return Err(Error::InvalidSection {
91                section: 5,
92                reason: format!("expected at least 11 bytes, got {}", section_bytes.len()),
93            });
94        }
95        if section_bytes[4] != 5 {
96            return Err(Error::InvalidSection {
97                section: section_bytes[4],
98                reason: "not a data representation section".into(),
99            });
100        }
101
102        let template = u16::from_be_bytes(section_bytes[9..11].try_into().unwrap());
103        match template {
104            0 => parse_simple_packing(section_bytes),
105            2 => parse_complex_packing(section_bytes, false),
106            3 => parse_complex_packing(section_bytes, true),
107            40 => parse_jpeg2000_packing(section_bytes),
108            41 => parse_png_packing(section_bytes),
109            _ => Ok(Self::Unsupported(template)),
110        }
111    }
112
113    pub fn encoded_values(&self) -> Option<usize> {
114        match self {
115            Self::SimplePacking(params) => Some(params.encoded_values),
116            Self::ComplexPacking(params) => Some(params.encoded_values),
117            Self::Jpeg2000Packing(params) => Some(params.packing.encoded_values),
118            Self::PngPacking(params) => Some(params.packing.encoded_values),
119            Self::Unsupported(_) => None,
120        }
121    }
122}
123
124fn parse_simple_packing(data: &[u8]) -> Result<DataRepresentation> {
125    if data.len() < 21 {
126        return Err(Error::InvalidSection {
127            section: 5,
128            reason: format!("template 5.0 requires 21 bytes, got {}", data.len()),
129        });
130    }
131
132    let encoded_values = u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize;
133    let reference_value = f32::from_be_bytes(data[11..15].try_into().unwrap());
134    let binary_scale = grib_i16(&data[15..17]).unwrap();
135    let decimal_scale = grib_i16(&data[17..19]).unwrap();
136    let bits_per_value = data[19];
137    let original_field_type = data[20];
138
139    Ok(DataRepresentation::SimplePacking(SimplePackingParams {
140        encoded_values,
141        reference_value,
142        binary_scale,
143        decimal_scale,
144        bits_per_value,
145        original_field_type,
146    }))
147}
148
149fn parse_image_packing_base(
150    data: &[u8],
151    template: u16,
152    required: usize,
153) -> Result<ImagePackingParams> {
154    if data.len() < required {
155        return Err(Error::InvalidSection {
156            section: 5,
157            reason: format!(
158                "template 5.{template} requires {required} bytes, got {}",
159                data.len()
160            ),
161        });
162    }
163
164    Ok(ImagePackingParams {
165        encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
166        reference_value: f32::from_be_bytes(data[11..15].try_into().unwrap()),
167        binary_scale: grib_i16(&data[15..17]).unwrap(),
168        decimal_scale: grib_i16(&data[17..19]).unwrap(),
169        bits_per_value: data[19],
170        original_field_type: data[20],
171    })
172}
173
174fn parse_jpeg2000_packing(data: &[u8]) -> Result<DataRepresentation> {
175    Ok(DataRepresentation::Jpeg2000Packing(Jpeg2000PackingParams {
176        packing: parse_image_packing_base(data, 40, 23)?,
177        compression_type: data[21],
178        target_compression_ratio: data[22],
179    }))
180}
181
182fn parse_png_packing(data: &[u8]) -> Result<DataRepresentation> {
183    Ok(DataRepresentation::PngPacking(PngPackingParams {
184        packing: parse_image_packing_base(data, 41, 21)?,
185    }))
186}
187
188fn parse_complex_packing(
189    data: &[u8],
190    with_spatial_differencing: bool,
191) -> Result<DataRepresentation> {
192    let required = if with_spatial_differencing { 49 } else { 47 };
193    if data.len() < required {
194        return Err(Error::InvalidSection {
195            section: 5,
196            reason: format!(
197                "template 5.{} requires {required} bytes, got {}",
198                if with_spatial_differencing { 3 } else { 2 },
199                data.len()
200            ),
201        });
202    }
203
204    let group_splitting_method = data[21];
205    if group_splitting_method != 1 {
206        return Err(Error::UnsupportedGroupSplittingMethod(
207            group_splitting_method,
208        ));
209    }
210
211    let missing_value_management = data[22];
212    if missing_value_management > 2 {
213        return Err(Error::UnsupportedMissingValueManagement(
214            missing_value_management,
215        ));
216    }
217
218    let spatial_differencing = if with_spatial_differencing {
219        let order = data[47];
220        if !matches!(order, 1 | 2) {
221            return Err(Error::UnsupportedSpatialDifferencingOrder(order));
222        }
223        Some(SpatialDifferencingParams {
224            order,
225            descriptor_octets: data[48],
226        })
227    } else {
228        None
229    };
230
231    Ok(DataRepresentation::ComplexPacking(ComplexPackingParams {
232        encoded_values: u32::from_be_bytes(data[5..9].try_into().unwrap()) as usize,
233        reference_value: f32::from_be_bytes(data[11..15].try_into().unwrap()),
234        binary_scale: grib_i16(&data[15..17]).unwrap(),
235        decimal_scale: grib_i16(&data[17..19]).unwrap(),
236        group_reference_bits: data[19],
237        original_field_type: data[20],
238        group_splitting_method,
239        missing_value_management,
240        primary_missing_substitute: u32::from_be_bytes(data[23..27].try_into().unwrap()),
241        secondary_missing_substitute: u32::from_be_bytes(data[27..31].try_into().unwrap()),
242        num_groups: u32::from_be_bytes(data[31..35].try_into().unwrap()) as usize,
243        group_width_reference: data[35],
244        group_width_bits: data[36],
245        group_length_reference: u32::from_be_bytes(data[37..41].try_into().unwrap()),
246        group_length_increment: data[41],
247        true_length_last_group: u32::from_be_bytes(data[42..46].try_into().unwrap()),
248        scaled_group_length_bits: data[46],
249        spatial_differencing,
250    }))
251}
252
253#[cfg(test)]
254mod tests {
255    use super::{
256        DataRepresentation, ImagePackingParams, Jpeg2000PackingParams, PngPackingParams,
257        SimplePackingParams,
258    };
259    use crate::util::encode_grib_i16;
260
261    #[test]
262    fn parses_simple_packing_template() {
263        let mut section = vec![0u8; 21];
264        section[..4].copy_from_slice(&(21u32).to_be_bytes());
265        section[4] = 5;
266        section[5..9].copy_from_slice(&3u32.to_be_bytes());
267        section[9..11].copy_from_slice(&0u16.to_be_bytes());
268        section[11..15].copy_from_slice(&10.0f32.to_be_bytes());
269        section[15..17].copy_from_slice(&2i16.to_be_bytes());
270        section[17..19].copy_from_slice(&1i16.to_be_bytes());
271        section[19] = 8;
272        section[20] = 0;
273
274        assert_eq!(
275            DataRepresentation::parse(&section).unwrap(),
276            DataRepresentation::SimplePacking(SimplePackingParams {
277                encoded_values: 3,
278                reference_value: 10.0,
279                binary_scale: 2,
280                decimal_scale: 1,
281                bits_per_value: 8,
282                original_field_type: 0,
283            })
284        );
285    }
286
287    #[test]
288    fn parses_jpeg2000_packing_template() {
289        let mut section = vec![0u8; 23];
290        section[..4].copy_from_slice(&(23u32).to_be_bytes());
291        section[4] = 5;
292        section[5..9].copy_from_slice(&4u32.to_be_bytes());
293        section[9..11].copy_from_slice(&40u16.to_be_bytes());
294        section[11..15].copy_from_slice(&3.5f32.to_be_bytes());
295        section[15..17].copy_from_slice(&encode_grib_i16(-1).unwrap());
296        section[17..19].copy_from_slice(&2i16.to_be_bytes());
297        section[19] = 12;
298        section[20] = 0;
299        section[21] = 1;
300        section[22] = 255;
301
302        assert_eq!(
303            DataRepresentation::parse(&section).unwrap(),
304            DataRepresentation::Jpeg2000Packing(Jpeg2000PackingParams {
305                packing: ImagePackingParams {
306                    encoded_values: 4,
307                    reference_value: 3.5,
308                    binary_scale: -1,
309                    decimal_scale: 2,
310                    bits_per_value: 12,
311                    original_field_type: 0,
312                },
313                compression_type: 1,
314                target_compression_ratio: 255,
315            })
316        );
317    }
318
319    #[test]
320    fn parses_png_packing_template() {
321        let mut section = vec![0u8; 21];
322        section[..4].copy_from_slice(&(21u32).to_be_bytes());
323        section[4] = 5;
324        section[5..9].copy_from_slice(&6u32.to_be_bytes());
325        section[9..11].copy_from_slice(&41u16.to_be_bytes());
326        section[11..15].copy_from_slice(&1.25f32.to_be_bytes());
327        section[15..17].copy_from_slice(&1i16.to_be_bytes());
328        section[17..19].copy_from_slice(&encode_grib_i16(-2).unwrap());
329        section[19] = 16;
330        section[20] = 1;
331
332        assert_eq!(
333            DataRepresentation::parse(&section).unwrap(),
334            DataRepresentation::PngPacking(PngPackingParams {
335                packing: ImagePackingParams {
336                    encoded_values: 6,
337                    reference_value: 1.25,
338                    binary_scale: 1,
339                    decimal_scale: -2,
340                    bits_per_value: 16,
341                    original_field_type: 1,
342                },
343            })
344        );
345    }
346}