Skip to main content

grib/grid/
latlon.rs

1use super::{
2    GridPointIndexIterator, Unrotate,
3    helpers::{RegularGridIterator, evenly_spaced_degrees, evenly_spaced_longitudes},
4};
5use crate::{
6    GridPointIndex, LatLons,
7    def::grib2::template::{Template3_1, param_set},
8    error::GribError,
9    grid::AngleUnit,
10};
11
12impl crate::GridShortName for param_set::LatLonGrid {
13    fn short_name(&self) -> &'static str {
14        "regular_ll"
15    }
16}
17
18impl GridPointIndex for param_set::LatLonGrid {
19    fn grid_shape(&self) -> (usize, usize) {
20        (self.grid.ni as usize, self.grid.nj as usize)
21    }
22
23    fn scanning_mode(&self) -> &param_set::ScanningMode {
24        &self.scanning_mode
25    }
26}
27
28impl LatLons for param_set::LatLonGrid {
29    type Iter<'a> = RegularGridIterator;
30
31    fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError> {
32        if !self.is_consistent_for_j() {
33            return Err(GribError::InvalidValueError(
34                "Latitudes for first/last grid points are not consistent with scanning mode"
35                    .to_owned(),
36            ));
37        }
38
39        let ij = self.ij()?;
40        let angle_units = self.angle_unit() as f32;
41        let lat = evenly_spaced_degrees(
42            self.grid.first_point_lat as f32,
43            self.grid.last_point_lat as f32,
44            (self.grid.nj - 1) as usize,
45            angle_units,
46        );
47        let lon = evenly_spaced_longitudes(
48            self.grid.first_point_lon,
49            self.grid.last_point_lon,
50            (self.grid.ni - 1) as usize,
51            angle_units,
52            self.scanning_mode,
53        );
54
55        let iter = RegularGridIterator::new(lat, lon, ij);
56        Ok(iter)
57    }
58}
59
60impl AngleUnit for param_set::LatLonGrid {
61    fn angle_unit(&self) -> f64 {
62        self.grid.angle_unit()
63    }
64}
65
66impl param_set::LatLonGrid {
67    pub(crate) fn is_consistent_for_j(&self) -> bool {
68        let lat_diff = self.grid.last_point_lat - self.grid.first_point_lat;
69        !((lat_diff > 0) ^ self.scanning_mode.scans_positively_for_j())
70    }
71}
72
73impl crate::GridShortName for Template3_1 {
74    fn short_name(&self) -> &'static str {
75        "rotated_ll"
76    }
77}
78
79impl GridPointIndex for Template3_1 {
80    fn grid_shape(&self) -> (usize, usize) {
81        self.lat_lon.grid_shape()
82    }
83
84    fn scanning_mode(&self) -> &crate::def::grib2::template::param_set::ScanningMode {
85        self.lat_lon.scanning_mode()
86    }
87
88    fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
89        self.lat_lon.ij()
90    }
91}
92
93impl LatLons for Template3_1 {
94    type Iter<'a>
95        = Unrotate<RegularGridIterator>
96    where
97        Self: 'a;
98
99    fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError> {
100        let iter = Unrotate::new(
101            self.lat_lon.latlons_unchecked()?,
102            &self.rotation,
103            self.angle_unit() as f32,
104        );
105        Ok(iter)
106    }
107}
108
109impl AngleUnit for Template3_1 {
110    fn angle_unit(&self) -> f64 {
111        self.lat_lon.grid.angle_unit()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::grid::helpers::test_helpers::assert_coord_almost_eq;
119
120    macro_rules! test_lat_lon_calculation_for_inconsistent_longitude_definitions {
121        ($((
122            $name:ident,
123            $grid:expr,
124            $scanning_mode:expr,
125            $expected_head:expr,
126            $expected_tail:expr
127        ),)*) => ($(
128            #[test]
129            fn $name() {
130                let grid = param_set::LatLonGrid {
131                    grid: $grid,
132                    i_direction_inc: 0xffffffff,
133                    j_direction_inc: 0xffffffff,
134                    scanning_mode: $scanning_mode,
135                };
136                let latlons = grid.latlons();
137                assert!(latlons.is_ok());
138
139                let delta = 1e-4;
140
141                let latlons = latlons.unwrap();
142                let actual = latlons.clone().take(3).collect::<Vec<_>>();
143                let expected = $expected_head;
144                for (a, e) in actual.iter().zip(expected) {
145                    assert_coord_almost_eq(*a, e, delta);
146                }
147
148                let (len, _) = latlons.size_hint();
149                let actual = latlons.skip(len - 3).collect::<Vec<_>>();
150                let expected = $expected_tail;
151                for (a, e) in actual.iter().zip(expected) {
152                    assert_coord_almost_eq(*a, e, delta);
153                }
154            }
155        )*);
156    }
157
158    test_lat_lon_calculation_for_inconsistent_longitude_definitions! {
159        (
160            lat_lon_calculation_for_increasing_longitudes_and_positive_direction_scan,
161            param_set::Grid {
162                ni: 1500,
163                nj: 751,
164                initial_production_domain_basic_angle: 0,
165                basic_angle_subdivisions: 0xffffffff,
166                first_point_lat: -90000000,
167                first_point_lon: 0,
168                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
169                last_point_lat: 90000000,
170                last_point_lon: 359760000,
171            },
172            param_set::ScanningMode(0b01000000),
173            vec![(-90.0, 0.0), (-90.0, 0.24), (-90.0, 0.48)],
174            vec![(90.0, -0.72), (90.0, -0.48), (90.0, -0.24)]
175        ),
176        (
177            // grid point definition extracted from
178            // testdata/CMC_glb_TMP_ISBL_1_latlon.24x.24_2021051800_P000.grib2
179            lat_lon_calculation_for_decreasing_longitudes_and_positive_direction_scan,
180            param_set::Grid {
181                ni: 1500,
182                nj: 751,
183                initial_production_domain_basic_angle: 0,
184                basic_angle_subdivisions: 0xffffffff,
185                first_point_lat: -90000000,
186                first_point_lon: 180000000,
187                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
188                last_point_lat: 90000000,
189                last_point_lon: 179760000,
190            },
191            param_set::ScanningMode(0b01000000),
192            vec![(-90.0, -180.0), (-90.0, -179.76), (-90.0, -179.52)],
193            vec![(90.0, 179.28003), (90.0, 179.52002), (90.0, 179.76001)]
194        ),
195        (
196            lat_lon_calculation_for_decreasing_longitudes_and_negative_direction_scan,
197            param_set::Grid {
198                ni: 1500,
199                nj: 751,
200                initial_production_domain_basic_angle: 0,
201                basic_angle_subdivisions: 0xffffffff,
202                first_point_lat: -90000000,
203                first_point_lon: 359760000,
204                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
205                last_point_lat: 90000000,
206                last_point_lon: 0,
207            },
208            param_set::ScanningMode(0b11000000),
209            vec![(-90.0, -0.24), (-90.0, -0.48), (-90.0, -0.72)],
210            vec![(90.0, 0.48), (90.0, 0.24), (90.0, 0.0)]
211        ),
212        (
213            lat_lon_calculation_for_increasing_longitudes_and_negative_direction_scan,
214            param_set::Grid {
215                ni: 1500,
216                nj: 751,
217                initial_production_domain_basic_angle: 0,
218                basic_angle_subdivisions: 0xffffffff,
219                first_point_lat: -90000000,
220                first_point_lon: 179760000,
221                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
222                last_point_lat: 90000000,
223                last_point_lon: 180000000,
224            },
225            param_set::ScanningMode(0b11000000),
226            vec![(-90.0, 179.76001), (-90.0, 179.52002), (-90.0, 179.28003)],
227            vec![(90.0, -179.52), (90.0, -179.76), (90.0, -180.0)]
228        ),
229    }
230
231    macro_rules! test_consistencies_between_lat_lon_and_scanning_mode {
232        ($((
233            $name:ident,
234            $first_point_lat:expr,
235            $first_point_lon:expr,
236            $last_point_lat:expr,
237            $last_point_lon:expr,
238            $scanning_mode:expr,
239            $expected_for_j:expr
240        ),)*) => ($(
241            #[test]
242            fn $name() {
243                let grid = param_set::LatLonGrid {
244                    grid: param_set::Grid {
245                        ni: 1,
246                        nj: 1,
247                        initial_production_domain_basic_angle: 0,
248                        basic_angle_subdivisions: 0xffffffff,
249                        first_point_lat: $first_point_lat,
250                        first_point_lon: $first_point_lon,
251                        resolution_and_component_flags:
252                            param_set::ResolutionAndComponentFlags(0b00110000),
253                        last_point_lat: $last_point_lat,
254                        last_point_lon: $last_point_lon,
255                    },
256                    i_direction_inc: 0xffffffff,
257                    j_direction_inc: 0xffffffff,
258                    scanning_mode: param_set::ScanningMode($scanning_mode),
259                };
260                assert_eq!(grid.is_consistent_for_j(), $expected_for_j);
261            }
262        )*);
263    }
264
265    test_consistencies_between_lat_lon_and_scanning_mode! {
266        (
267            consistency_between_lat_decrease_and_scanning_mode_0b00000000,
268            37_000_000,
269            140_000_000,
270            36_000_000,
271            141_000_000,
272            0b00000000,
273            true
274        ),
275        (
276            consistency_between_lat_decrease_and_scanning_mode_0b01000000,
277            37_000_000,
278            140_000_000,
279            36_000_000,
280            141_000_000,
281            0b01000000,
282            false
283        ),
284        (
285            consistency_between_lat_increase_and_scanning_mode_0b00000000,
286            36_000_000,
287            140_000_000,
288            37_000_000,
289            141_000_000,
290            0b00000000,
291            false
292        ),
293        (
294            consistency_between_lat_increase_and_scanning_mode_0b01000000,
295            36_000_000,
296            140_000_000,
297            37_000_000,
298            141_000_000,
299            0b01000000,
300            true
301        ),
302    }
303}