1use super::{
2 helpers::{evenly_spaced_degrees, evenly_spaced_longitudes, RegularGridIterator},
3 GridPointIndexIterator, ScanningMode,
4};
5use crate::{
6 error::GribError,
7 helpers::{read_as, GribInt},
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub struct LatLonGridDefinition {
12 pub ni: u32,
13 pub nj: u32,
14 pub first_point_lat: i32,
15 pub first_point_lon: i32,
16 pub last_point_lat: i32,
17 pub last_point_lon: i32,
18 pub scanning_mode: ScanningMode,
19}
20
21impl LatLonGridDefinition {
22 pub fn grid_shape(&self) -> (usize, usize) {
41 (self.ni as usize, self.nj as usize)
42 }
43
44 pub fn short_name(&self) -> &'static str {
46 "regular_ll"
47 }
48
49 pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
76 if self.scanning_mode.has_unsupported_flags() {
77 let ScanningMode(mode) = self.scanning_mode;
78 return Err(GribError::NotSupported(format!("scanning mode {mode}")));
79 }
80
81 let iter =
82 GridPointIndexIterator::new(self.ni as usize, self.nj as usize, self.scanning_mode);
83 Ok(iter)
84 }
85
86 pub fn latlons(&self) -> Result<RegularGridIterator, GribError> {
114 if !self.is_consistent_for_j() {
115 return Err(GribError::InvalidValueError(
116 "Latitudes for first/last grid points are not consistent with scanning mode"
117 .to_owned(),
118 ));
119 }
120
121 let ij = self.ij()?;
122 let lat = evenly_spaced_degrees(
123 self.first_point_lat as f32,
124 self.last_point_lat as f32,
125 (self.nj - 1) as usize,
126 );
127 let lon = evenly_spaced_longitudes(
128 self.first_point_lon,
129 self.last_point_lon,
130 (self.ni - 1) as usize,
131 self.scanning_mode,
132 );
133
134 let iter = RegularGridIterator::new(lat, lon, ij);
135 Ok(iter)
136 }
137
138 pub(crate) fn is_consistent_for_j(&self) -> bool {
139 let lat_diff = self.last_point_lat - self.first_point_lat;
140 !((lat_diff > 0) ^ self.scanning_mode.scans_positively_for_j())
141 }
142
143 pub(crate) fn from_buf(buf: &[u8]) -> Self {
144 let ni = read_as!(u32, buf, 0);
145 let nj = read_as!(u32, buf, 4);
146 let first_point_lat = read_as!(u32, buf, 16).as_grib_int();
147 let first_point_lon = read_as!(u32, buf, 20).as_grib_int();
148 let last_point_lat = read_as!(u32, buf, 25).as_grib_int();
149 let last_point_lon = read_as!(u32, buf, 29).as_grib_int();
150 let scanning_mode = read_as!(u8, buf, 41);
151 Self {
152 ni,
153 nj,
154 first_point_lat,
155 first_point_lon,
156 last_point_lat,
157 last_point_lon,
158 scanning_mode: ScanningMode(scanning_mode),
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 macro_rules! test_lat_lon_calculation_for_inconsistent_longitude_definitions {
168 ($((
169 $name:ident,
170 $grid:expr,
171 $expected_head:expr,
172 $expected_tail:expr
173 ),)*) => ($(
174 #[test]
175 fn $name() {
176 let grid = $grid;
177 let latlons = grid.latlons();
178 assert!(latlons.is_ok());
179
180 let latlons = latlons.unwrap();
181 let actual = latlons.clone().take(3).collect::<Vec<_>>();
182 let expected = $expected_head;
183 assert_eq!(actual, expected);
184
185 let (len, _) = latlons.size_hint();
186 let actual = latlons.skip(len - 3).collect::<Vec<_>>();
187 let expected = $expected_tail;
188 assert_eq!(actual, expected);
189 }
190 )*);
191 }
192
193 test_lat_lon_calculation_for_inconsistent_longitude_definitions! {
194 (
195 lat_lon_calculation_for_increasing_longitudes_and_positive_direction_scan,
196 LatLonGridDefinition {
197 ni: 1500,
198 nj: 751,
199 first_point_lat: -90000000,
200 first_point_lon: 0,
201 last_point_lat: 90000000,
202 last_point_lon: 359760000,
203 scanning_mode: ScanningMode(0b01000000),
204 },
205 vec![(-90.0, 0.0), (-90.0, 0.24), (-90.0, 0.48)],
206 vec![(90.0, 359.28), (90.0, 359.52), (90.0, 359.76)]
207 ),
208 (
209 lat_lon_calculation_for_decreasing_longitudes_and_positive_direction_scan,
212 LatLonGridDefinition {
213 ni: 1500,
214 nj: 751,
215 first_point_lat: -90000000,
216 first_point_lon: 180000000,
217 last_point_lat: 90000000,
218 last_point_lon: 179760000,
219 scanning_mode: ScanningMode(0b01000000),
220 },
221 vec![(-90.0, 180.0), (-90.0, 180.24), (-90.0, 180.48)],
222 vec![(90.0, 179.28003), (90.0, 179.52002), (90.0, 179.76001)]
223 ),
224 (
225 lat_lon_calculation_for_decreasing_longitudes_and_negative_direction_scan,
226 LatLonGridDefinition {
227 ni: 1500,
228 nj: 751,
229 first_point_lat: -90000000,
230 first_point_lon: 359760000,
231 last_point_lat: 90000000,
232 last_point_lon: 0,
233 scanning_mode: ScanningMode(0b11000000),
234 },
235 vec![(-90.0, 359.76), (-90.0, 359.52), (-90.0, 359.28)],
236 vec![(90.0, 0.48), (90.0, 0.24), (90.0, 0.0)]
237 ),
238 (
239 lat_lon_calculation_for_increasing_longitudes_and_negative_direction_scan,
240 LatLonGridDefinition {
241 ni: 1500,
242 nj: 751,
243 first_point_lat: -90000000,
244 first_point_lon: 179760000,
245 last_point_lat: 90000000,
246 last_point_lon: 180000000,
247 scanning_mode: ScanningMode(0b11000000),
248 },
249 vec![(-90.0, 179.76001), (-90.0, 179.52002), (-90.0, 179.28003)],
250 vec![(90.0, 180.48), (90.0, 180.24), (90.0, 180.0)]
251 ),
252 }
253
254 macro_rules! test_consistencies_between_lat_lon_and_scanning_mode {
255 ($((
256 $name:ident,
257 $first_point_lat:expr,
258 $first_point_lon:expr,
259 $last_point_lat:expr,
260 $last_point_lon:expr,
261 $scanning_mode:expr,
262 $expected_for_j:expr
263 ),)*) => ($(
264 #[test]
265 fn $name() {
266 let grid = LatLonGridDefinition {
267 ni: 1,
268 nj: 1,
269 first_point_lat: $first_point_lat,
270 first_point_lon: $first_point_lon,
271 last_point_lat: $last_point_lat,
272 last_point_lon: $last_point_lon,
273 scanning_mode: ScanningMode($scanning_mode),
274 };
275 assert_eq!(grid.is_consistent_for_j(), $expected_for_j);
276 }
277 )*);
278 }
279
280 test_consistencies_between_lat_lon_and_scanning_mode! {
281 (
282 consistency_between_lat_decrease_and_scanning_mode_0b00000000,
283 37_000_000,
284 140_000_000,
285 36_000_000,
286 141_000_000,
287 0b00000000,
288 true
289 ),
290 (
291 consistency_between_lat_decrease_and_scanning_mode_0b01000000,
292 37_000_000,
293 140_000_000,
294 36_000_000,
295 141_000_000,
296 0b01000000,
297 false
298 ),
299 (
300 consistency_between_lat_increase_and_scanning_mode_0b00000000,
301 36_000_000,
302 140_000_000,
303 37_000_000,
304 141_000_000,
305 0b00000000,
306 false
307 ),
308 (
309 consistency_between_lat_increase_and_scanning_mode_0b01000000,
310 36_000_000,
311 140_000_000,
312 37_000_000,
313 141_000_000,
314 0b01000000,
315 true
316 ),
317 }
318}