1use crate::error::{Error, Result};
4use crate::util::grib_i32;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum GridDefinition {
9 LatLon(LatLonGrid),
11 Unsupported(u16),
13}
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct LatLonGrid {
18 pub ni: u32,
19 pub nj: u32,
20 pub lat_first: i32,
21 pub lon_first: i32,
22 pub lat_last: i32,
23 pub lon_last: i32,
24 pub di: u32,
25 pub dj: u32,
26 pub scanning_mode: u8,
27}
28
29impl GridDefinition {
30 pub fn shape(&self) -> (usize, usize) {
31 match self {
32 Self::LatLon(g) => (g.ni as usize, g.nj as usize),
33 Self::Unsupported(_) => (0, 0),
34 }
35 }
36
37 pub fn ndarray_shape(&self) -> Vec<usize> {
38 let (ni, nj) = self.shape();
39 match self {
40 Self::LatLon(_) if ni > 0 && nj > 0 => vec![nj, ni],
41 _ => Vec::new(),
42 }
43 }
44
45 pub fn num_points(&self) -> usize {
46 let (ni, nj) = self.shape();
47 ni.saturating_mul(nj)
48 }
49
50 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
51 if section_bytes.len() < 14 {
52 return Err(Error::InvalidSection {
53 section: 3,
54 reason: format!("expected at least 14 bytes, got {}", section_bytes.len()),
55 });
56 }
57 if section_bytes[4] != 3 {
58 return Err(Error::InvalidSection {
59 section: section_bytes[4],
60 reason: "not a grid definition section".into(),
61 });
62 }
63
64 let template = u16::from_be_bytes(section_bytes[12..14].try_into().unwrap());
65 match template {
66 0 => parse_latlon(section_bytes),
67 _ => Ok(Self::Unsupported(template)),
68 }
69 }
70}
71
72impl LatLonGrid {
73 pub fn longitudes(&self) -> Vec<f64> {
74 let step = self.di as f64 / 1_000_000.0;
75 let signed_step = if self.i_scans_positive() { step } else { -step };
76 let start = self.lon_first as f64 / 1_000_000.0;
77 (0..self.ni)
78 .map(|index| start + signed_step * index as f64)
79 .collect()
80 }
81
82 pub fn latitudes(&self) -> Vec<f64> {
83 let step = self.dj as f64 / 1_000_000.0;
84 let signed_step = if self.j_scans_positive() { step } else { -step };
85 let start = self.lat_first as f64 / 1_000_000.0;
86 (0..self.nj)
87 .map(|index| start + signed_step * index as f64)
88 .collect()
89 }
90
91 pub fn reorder_for_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
92 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
93 Ok(values)
94 }
95
96 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
97 self.reorder_grib_scan_to_ndarray_in_place(values)
98 }
99
100 pub fn reorder_grib_scan_to_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
101 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
102 Ok(values)
103 }
104
105 pub fn reorder_grib_scan_to_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
106 self.transform_supported_scan_order_in_place(values)
107 }
108
109 pub fn reorder_ndarray_to_grib_scan<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
110 self.reorder_ndarray_to_grib_scan_in_place(&mut values)?;
111 Ok(values)
112 }
113
114 pub fn reorder_ndarray_to_grib_scan_in_place<T>(&self, values: &mut [T]) -> Result<()> {
115 self.transform_supported_scan_order_in_place(values)
116 }
117
118 pub fn validate_supported_scan_order(&self) -> Result<()> {
119 if self.i_points_are_consecutive() {
120 Ok(())
121 } else {
122 Err(Error::UnsupportedScanningMode(self.scanning_mode))
123 }
124 }
125
126 fn transform_supported_scan_order_in_place<T>(&self, values: &mut [T]) -> Result<()> {
127 self.validate_supported_scan_order()?;
128 let ni = self.ni as usize;
129 let nj = self.nj as usize;
130 if values.len() != ni * nj {
131 return Err(Error::DataLengthMismatch {
132 expected: ni * nj,
133 actual: values.len(),
134 });
135 }
136
137 if self.adjacent_rows_alternate_direction() {
138 reverse_alternating_rows(values, ni, nj, self.i_scans_positive());
139 }
140
141 Ok(())
142 }
143
144 fn i_scans_positive(&self) -> bool {
145 self.scanning_mode & 0b1000_0000 == 0
146 }
147
148 fn j_scans_positive(&self) -> bool {
149 self.scanning_mode & 0b0100_0000 != 0
150 }
151
152 fn i_points_are_consecutive(&self) -> bool {
153 self.scanning_mode & 0b0010_0000 == 0
154 }
155
156 fn adjacent_rows_alternate_direction(&self) -> bool {
157 self.scanning_mode & 0b0001_0000 != 0
158 }
159}
160
161fn reverse_alternating_rows<T>(values: &mut [T], ni: usize, nj: usize, i_scans_positive: bool) {
162 for row in 0..nj {
163 let reverse = if i_scans_positive {
164 row % 2 == 1
165 } else {
166 row % 2 == 0
167 };
168 if reverse {
169 values[row * ni..(row + 1) * ni].reverse();
170 }
171 }
172}
173
174fn parse_latlon(data: &[u8]) -> Result<GridDefinition> {
175 if data.len() < 72 {
176 return Err(Error::InvalidSection {
177 section: 3,
178 reason: format!("template 3.0 requires 72 bytes, got {}", data.len()),
179 });
180 }
181
182 let ni = u32::from_be_bytes(data[30..34].try_into().unwrap());
183 let nj = u32::from_be_bytes(data[34..38].try_into().unwrap());
184 let lat_first = grib_i32(&data[46..50]).unwrap();
185 let lon_first = grib_i32(&data[50..54]).unwrap();
186 let lat_last = grib_i32(&data[55..59]).unwrap();
187 let lon_last = grib_i32(&data[59..63]).unwrap();
188 let di = u32::from_be_bytes(data[63..67].try_into().unwrap());
189 let dj = u32::from_be_bytes(data[67..71].try_into().unwrap());
190 let scanning_mode = data[71];
191
192 Ok(GridDefinition::LatLon(LatLonGrid {
193 ni,
194 nj,
195 lat_first,
196 lon_first,
197 lat_last,
198 lon_last,
199 di,
200 dj,
201 scanning_mode,
202 }))
203}
204
205#[cfg(test)]
206mod tests {
207 use super::{GridDefinition, LatLonGrid};
208
209 #[test]
210 fn reports_latlon_shape() {
211 let grid = GridDefinition::LatLon(LatLonGrid {
212 ni: 3,
213 nj: 2,
214 lat_first: 50_000_000,
215 lon_first: -120_000_000,
216 lat_last: 49_000_000,
217 lon_last: -118_000_000,
218 di: 1_000_000,
219 dj: 1_000_000,
220 scanning_mode: 0,
221 });
222
223 assert_eq!(grid.shape(), (3, 2));
224 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
225 match grid {
226 GridDefinition::LatLon(ref latlon) => {
227 assert_eq!(latlon.longitudes(), vec![-120.0, -119.0, -118.0]);
228 assert_eq!(latlon.latitudes(), vec![50.0, 49.0]);
229 }
230 GridDefinition::Unsupported(_) => panic!("expected lat/lon grid"),
231 }
232 }
233
234 #[test]
235 fn normalizes_alternating_row_scan() {
236 let grid = LatLonGrid {
237 ni: 3,
238 nj: 2,
239 lat_first: 0,
240 lon_first: 0,
241 lat_last: 0,
242 lon_last: 0,
243 di: 1,
244 dj: 1,
245 scanning_mode: 0b0001_0000,
246 };
247
248 let ordered = grid
249 .reorder_for_ndarray(vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0])
250 .unwrap();
251 assert_eq!(ordered, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
252 }
253
254 #[test]
255 fn converts_ndarray_order_to_alternating_scan_order() {
256 let grid = LatLonGrid {
257 ni: 3,
258 nj: 2,
259 lat_first: 0,
260 lon_first: 0,
261 lat_last: 0,
262 lon_last: 0,
263 di: 1,
264 dj: 1,
265 scanning_mode: 0b0001_0000,
266 };
267
268 let scan_order = grid
269 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
270 .unwrap();
271 assert_eq!(scan_order, vec![1, 2, 3, 6, 5, 4]);
272 }
273
274 #[test]
275 fn preserves_non_alternating_scan_modes_in_current_reader_order() {
276 for scanning_mode in [0b0000_0000, 0b1000_0000, 0b0100_0000, 0b1100_0000] {
277 let grid = LatLonGrid {
278 ni: 3,
279 nj: 2,
280 lat_first: 0,
281 lon_first: 0,
282 lat_last: 0,
283 lon_last: 0,
284 di: 1,
285 dj: 1,
286 scanning_mode,
287 };
288
289 let values = vec![1, 2, 3, 4, 5, 6];
290 assert_eq!(
291 grid.reorder_grib_scan_to_ndarray(values.clone()).unwrap(),
292 values
293 );
294 assert_eq!(
295 grid.reorder_ndarray_to_grib_scan(values.clone()).unwrap(),
296 values
297 );
298 }
299 }
300
301 #[test]
302 fn rejects_j_consecutive_scan_order() {
303 let grid = LatLonGrid {
304 ni: 3,
305 nj: 2,
306 lat_first: 0,
307 lon_first: 0,
308 lat_last: 0,
309 lon_last: 0,
310 di: 1,
311 dj: 1,
312 scanning_mode: 0b0010_0000,
313 };
314
315 let err = grid
316 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
317 .unwrap_err();
318 assert!(matches!(
319 err,
320 crate::Error::UnsupportedScanningMode(0b0010_0000)
321 ));
322 }
323}