1use 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#[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(§ion_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(§ion_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]) -> 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 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}
199
200fn parse_latlon_grid(section_bytes: &[u8]) -> GridDefinition {
201 let ni = u32::from(u16::from_be_bytes(section_bytes[6..8].try_into().unwrap()));
202 let nj = u32::from(u16::from_be_bytes(section_bytes[8..10].try_into().unwrap()));
203 let lat_first = grib_i24(§ion_bytes[10..13]).unwrap() * 1_000;
204 let lon_first = grib_i24(§ion_bytes[13..16]).unwrap() * 1_000;
205 let lat_last = grib_i24(§ion_bytes[17..20]).unwrap() * 1_000;
206 let lon_last = grib_i24(§ion_bytes[20..23]).unwrap() * 1_000;
207 let di = u32::from(u16::from_be_bytes(
208 section_bytes[23..25].try_into().unwrap(),
209 )) * 1_000;
210 let dj = u32::from(u16::from_be_bytes(
211 section_bytes[25..27].try_into().unwrap(),
212 )) * 1_000;
213 let scanning_mode = section_bytes[27];
214
215 GridDefinition::LatLon(LatLonGrid {
216 ni,
217 nj,
218 lat_first,
219 lon_first,
220 lat_last,
221 lon_last,
222 di,
223 dj,
224 scanning_mode,
225 })
226}
227
228#[cfg(test)]
229mod tests {
230 use super::ProductDefinition;
231 use crate::metadata::ReferenceTime;
232
233 #[test]
234 fn decodes_indicator_ten_forecast_time_as_u16() {
235 let product = ProductDefinition {
236 table_version: 2,
237 center_id: 7,
238 generating_process_id: 255,
239 grid_id: 0,
240 has_grid_definition: true,
241 has_bitmap: false,
242 parameter_number: 11,
243 level_type: 100,
244 level_value: 850,
245 reference_time: ReferenceTime {
246 year: 2026,
247 month: 3,
248 day: 20,
249 hour: 12,
250 minute: 0,
251 second: 0,
252 },
253 forecast_time_unit: 1,
254 p1: 0x01,
255 p2: 0x2c,
256 time_range_indicator: 10,
257 average_count: 0,
258 missing_count: 0,
259 century: 21,
260 subcenter_id: 0,
261 decimal_scale: 0,
262 };
263
264 assert_eq!(product.forecast_time(), Some(300));
265 }
266}