grib/grid/
latlon.rs

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    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
23    /// the i and j directions.
24    ///
25    /// Examples
26    ///
27    /// ```
28    /// let def = grib::LatLonGridDefinition {
29    ///     ni: 2,
30    ///     nj: 3,
31    ///     first_point_lat: 0,
32    ///     first_point_lon: 0,
33    ///     last_point_lat: 2_000_000,
34    ///     last_point_lon: 1_000_000,
35    ///     scanning_mode: grib::ScanningMode(0b01000000),
36    /// };
37    /// let shape = def.grid_shape();
38    /// assert_eq!(shape, (2, 3));
39    /// ```
40    pub fn grid_shape(&self) -> (usize, usize) {
41        (self.ni as usize, self.nj as usize)
42    }
43
44    /// Returns the grid type.
45    pub fn short_name(&self) -> &'static str {
46        "regular_ll"
47    }
48
49    /// Returns an iterator over `(i, j)` of grid points.
50    ///
51    /// Note that this is a low-level API and it is not checked that the number
52    /// of iterator iterations is consistent with the number of grid points
53    /// defined in the data.
54    ///
55    /// Examples
56    ///
57    /// ```
58    /// let def = grib::LatLonGridDefinition {
59    ///     ni: 2,
60    ///     nj: 3,
61    ///     first_point_lat: 0,
62    ///     first_point_lon: 0,
63    ///     last_point_lat: 2_000_000,
64    ///     last_point_lon: 1_000_000,
65    ///     scanning_mode: grib::ScanningMode(0b01000000),
66    /// };
67    /// let ij = def.ij();
68    /// assert!(ij.is_ok());
69    ///
70    /// let mut ij = ij.unwrap();
71    /// assert_eq!(ij.next(), Some((0, 0)));
72    /// assert_eq!(ij.next(), Some((1, 0)));
73    /// assert_eq!(ij.next(), Some((0, 1)));
74    /// ```
75    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    /// Returns an iterator over latitudes and longitudes of grid points in
87    /// degrees.
88    ///
89    /// Note that this is a low-level API and it is not checked that the number
90    /// of iterator iterations is consistent with the number of grid points
91    /// defined in the data.
92    ///
93    /// Examples
94    ///
95    /// ```
96    /// let def = grib::LatLonGridDefinition {
97    ///     ni: 2,
98    ///     nj: 3,
99    ///     first_point_lat: 0,
100    ///     first_point_lon: 0,
101    ///     last_point_lat: 2_000_000,
102    ///     last_point_lon: 1_000_000,
103    ///     scanning_mode: grib::ScanningMode(0b01000000),
104    /// };
105    /// let latlons = def.latlons();
106    /// assert!(latlons.is_ok());
107    ///
108    /// let mut latlons = latlons.unwrap();
109    /// assert_eq!(latlons.next(), Some((0.0, 0.0)));
110    /// assert_eq!(latlons.next(), Some((0.0, 1.0)));
111    /// assert_eq!(latlons.next(), Some((1.0, 0.0)));
112    /// ```
113    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            // grid point definition extracted from
210            // testdata/CMC_glb_TMP_ISBL_1_latlon.24x.24_2021051800_P000.grib2
211            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}