Skip to main content

grib/grid/
latlon.rs

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