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_for_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 let ni = self.ni as usize;
98 let nj = self.nj as usize;
99 if values.len() != ni * nj {
100 return Err(Error::DataLengthMismatch {
101 expected: ni * nj,
102 actual: values.len(),
103 });
104 }
105
106 if !self.i_points_are_consecutive() {
107 return Err(Error::UnsupportedScanningMode(self.scanning_mode));
108 }
109
110 if self.adjacent_rows_alternate_direction() {
111 for row in 0..nj {
112 let reverse = if self.i_scans_positive() {
113 row % 2 == 1
114 } else {
115 row % 2 == 0
116 };
117 if reverse {
118 values[row * ni..(row + 1) * ni].reverse();
119 }
120 }
121 }
122
123 Ok(())
124 }
125
126 fn i_scans_positive(&self) -> bool {
127 self.scanning_mode & 0b1000_0000 == 0
128 }
129
130 fn j_scans_positive(&self) -> bool {
131 self.scanning_mode & 0b0100_0000 != 0
132 }
133
134 fn i_points_are_consecutive(&self) -> bool {
135 self.scanning_mode & 0b0010_0000 == 0
136 }
137
138 fn adjacent_rows_alternate_direction(&self) -> bool {
139 self.scanning_mode & 0b0001_0000 != 0
140 }
141}
142
143fn parse_latlon(data: &[u8]) -> Result<GridDefinition> {
144 if data.len() < 72 {
145 return Err(Error::InvalidSection {
146 section: 3,
147 reason: format!("template 3.0 requires 72 bytes, got {}", data.len()),
148 });
149 }
150
151 let ni = u32::from_be_bytes(data[30..34].try_into().unwrap());
152 let nj = u32::from_be_bytes(data[34..38].try_into().unwrap());
153 let lat_first = grib_i32(&data[46..50]).unwrap();
154 let lon_first = grib_i32(&data[50..54]).unwrap();
155 let lat_last = grib_i32(&data[55..59]).unwrap();
156 let lon_last = grib_i32(&data[59..63]).unwrap();
157 let di = u32::from_be_bytes(data[63..67].try_into().unwrap());
158 let dj = u32::from_be_bytes(data[67..71].try_into().unwrap());
159 let scanning_mode = data[71];
160
161 Ok(GridDefinition::LatLon(LatLonGrid {
162 ni,
163 nj,
164 lat_first,
165 lon_first,
166 lat_last,
167 lon_last,
168 di,
169 dj,
170 scanning_mode,
171 }))
172}
173
174#[cfg(test)]
175mod tests {
176 use super::{GridDefinition, LatLonGrid};
177
178 #[test]
179 fn reports_latlon_shape() {
180 let grid = GridDefinition::LatLon(LatLonGrid {
181 ni: 3,
182 nj: 2,
183 lat_first: 50_000_000,
184 lon_first: -120_000_000,
185 lat_last: 49_000_000,
186 lon_last: -118_000_000,
187 di: 1_000_000,
188 dj: 1_000_000,
189 scanning_mode: 0,
190 });
191
192 assert_eq!(grid.shape(), (3, 2));
193 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
194 match grid {
195 GridDefinition::LatLon(ref latlon) => {
196 assert_eq!(latlon.longitudes(), vec![-120.0, -119.0, -118.0]);
197 assert_eq!(latlon.latitudes(), vec![50.0, 49.0]);
198 }
199 GridDefinition::Unsupported(_) => panic!("expected lat/lon grid"),
200 }
201 }
202
203 #[test]
204 fn normalizes_alternating_row_scan() {
205 let grid = LatLonGrid {
206 ni: 3,
207 nj: 2,
208 lat_first: 0,
209 lon_first: 0,
210 lat_last: 0,
211 lon_last: 0,
212 di: 1,
213 dj: 1,
214 scanning_mode: 0b0001_0000,
215 };
216
217 let ordered = grid
218 .reorder_for_ndarray(vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0])
219 .unwrap();
220 assert_eq!(ordered, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
221 }
222}