Skip to main content

grib/encoder/
grid.rs

1use crate::def::grib2::template::param_set;
2
3/// An auxiliary struct for creating the definition of a latitude/longitude grid
4/// ([`def::grib2::template::param_set::LatLonGrid`](`crate::def::grib2::template::param_set::LatLonGrid`))
5/// in a more intuitive way.
6///
7/// You can specify any value within the range of -360 degrees to 360 degrees
8/// for the longitude values of the first and last points. However, to
9/// distinguish the grid area, set the longitude value at the western end of the
10/// grid to a value smaller than that at the eastern end.
11///
12/// For example:
13///
14/// - For a grid extending eastward from 0° to 5°W, specify the first point’s
15///   longitude as 0.0 and the last point’s longitude as 355.0.
16/// - For a grid extending westward from 0° to 5°W, specify the first point’s
17///   longitude as 0.0 and the last point’s longitude as -5.0.
18/// - For a grid extending eastward from 180° to 175°W, specify the first
19///   point’s longitude as -180.0 and the last point’s longitude as 175.0.
20/// - For a grid extending westward from 180° to 175°W, specify the first
21///   point’s longitude as 180.0 and the last point’s longitude as 175.0.
22///
23/// Longitude values are automatically normalized during conversion to
24/// [`def::grib2::template::param_set::LatLonGrid`](`crate::def::grib2::template::param_set::LatLonGrid`)
25/// to comply with GRIB2 specification.
26pub struct LatLonGrid {
27    /// Shape of the grid, in the form `(ni, nj)`.
28    pub shape: (usize, usize),
29    /// First coordinate point, in the form `(lat, lon)`. The order in which you
30    /// specify the "first" point should correspond to the order of the array of
31    /// grid point values.
32    pub first_point: (f64, f64),
33    /// The coordinate point diagonally opposite the first coordinate point, in
34    /// the form `(lat, lon)`.
35    pub last_point: (f64, f64),
36    /// Whether the grid point values consecutive in the `i`-direction
37    /// (longitude direction) or not.
38    pub i_consecutive: bool,
39}
40
41impl LatLonGrid {
42    /// Returns the number of points of the grid.
43    pub fn num_points(&self) -> usize {
44        self.shape.0 * self.shape.1
45    }
46
47    /// Returns the scanning mode of the grid.
48    pub fn scanning_mode(&self) -> param_set::ScanningMode {
49        let mut scan_mode: u8 = 0b00000000;
50        if self.first_point.1 > self.last_point.1 {
51            scan_mode |= 0b10000000;
52        }
53        if self.first_point.0 < self.last_point.0 {
54            scan_mode |= 0b01000000;
55        }
56        if !self.i_consecutive {
57            scan_mode |= 0b00100000;
58        }
59        param_set::ScanningMode(scan_mode)
60    }
61}
62
63impl From<&LatLonGrid> for param_set::LatLonGrid {
64    fn from(value: &LatLonGrid) -> Self {
65        let (ni, nj) = (value.shape.0 as u32, value.shape.1 as u32);
66
67        let (first_point_lat, first_point_lon) = (
68            lat_in_microdegrees(value.first_point.0),
69            lon_in_microdegrees(value.first_point.1),
70        );
71        let (last_point_lat, last_point_lon) = (
72            lat_in_microdegrees(value.last_point.0),
73            lon_in_microdegrees(value.last_point.1),
74        );
75
76        let i_direction_inc =
77            inc_in_microdegrees(value.first_point.1, value.last_point.1, value.shape.0 - 1);
78        let j_direction_inc =
79            inc_in_microdegrees(value.first_point.0, value.last_point.0, value.shape.1 - 1);
80        let scanning_mode = value.scanning_mode();
81
82        param_set::LatLonGrid {
83            grid: param_set::Grid {
84                ni,
85                nj,
86                initial_production_domain_basic_angle: 0,
87                basic_angle_subdivisions: 0xffffffff,
88                first_point_lat,
89                first_point_lon,
90                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
91                last_point_lat,
92                last_point_lon,
93            },
94            i_direction_inc,
95            j_direction_inc,
96            scanning_mode,
97        }
98    }
99}
100
101fn lat_in_microdegrees(val_in_degrees: f64) -> i32 {
102    microdegrees(val_in_degrees)
103}
104
105fn lon_in_microdegrees(val_in_degrees: f64) -> u32 {
106    microdegrees(normalized_longitude(val_in_degrees)) as u32
107}
108
109fn inc_in_microdegrees(first: f64, last: f64, n_spacing: usize) -> u32 {
110    let diff = if first < last {
111        last - first
112    } else {
113        first - last
114    };
115    microdegrees(diff) as u32 / n_spacing as u32
116}
117
118fn microdegrees(val_in_degrees: f64) -> i32 {
119    (val_in_degrees * 1e6) as i32
120}
121
122fn normalized_longitude(val: f64) -> f64 {
123    if val < 0. { val + 360. } else { val }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    macro_rules! test_lat_lon_grid_definition {
131        ($(($name:ident, $input:expr, $expected:expr),)*) => ($(
132            #[test]
133            fn $name() {
134                let input = $input;
135                let params = crate::def::grib2::template::param_set::LatLonGrid::from(&input);
136                let actual = (
137                    (params.grid.ni, params.grid.nj),
138                    (params.grid.first_point_lat, params.grid.first_point_lon),
139                    (params.grid.last_point_lat, params.grid.last_point_lon),
140                    (params.i_direction_inc, params.j_direction_inc),
141                    params.scanning_mode,
142                );
143                assert_eq!(actual, $expected);
144            }
145        )*);
146    }
147
148    test_lat_lon_grid_definition! {
149        (
150            lat_lon_grid_definition_for_neg_lat_inc_and_pos_lon_inc,
151            LatLonGrid {
152                shape: (201, 101),
153                first_point: (40., 130.),
154                last_point: (30., 140.),
155                i_consecutive: true,
156            },
157            (
158                (201, 101),
159                (40000000, 130000000),
160                (30000000, 140000000),
161                (50000, 100000),
162                param_set::ScanningMode(0b00000000),
163            )
164        ),
165        (
166            lat_lon_grid_definition_for_neg_lat_inc_and_neg_lon_inc,
167            LatLonGrid {
168                shape: (201, 101),
169                first_point: (40., 140.),
170                last_point: (30., 130.),
171                i_consecutive: true,
172            },
173            (
174                (201, 101),
175                (40000000, 140000000),
176                (30000000, 130000000),
177                (50000, 100000),
178                param_set::ScanningMode(0b10000000),
179            )
180        ),
181        (
182            lat_lon_grid_definition_for_pos_lat_inc_and_pos_lon_inc,
183            LatLonGrid {
184                shape: (201, 101),
185                first_point: (30., 130.),
186                last_point: (40., 140.),
187                i_consecutive: true,
188            },
189            (
190                (201, 101),
191                (30000000, 130000000),
192                (40000000, 140000000),
193                (50000, 100000),
194                param_set::ScanningMode(0b01000000),
195            )
196        ),
197        (
198            lat_lon_grid_definition_for_pos_lat_inc_and_neg_lon_inc,
199            LatLonGrid {
200                shape: (201, 101),
201                first_point: (30., 140.),
202                last_point: (40., 130.),
203                i_consecutive: true,
204            },
205            (
206                (201, 101),
207                (30000000, 140000000),
208                (40000000, 130000000),
209                (50000, 100000),
210                param_set::ScanningMode(0b11000000),
211            )
212        ),
213        (
214            lat_lon_grid_definition_for_eastward_from_0_deg,
215            LatLonGrid {
216                shape: (2, 2),
217                first_point: (40., 0.),
218                last_point: (30., 355.),
219                i_consecutive: true,
220            },
221            (
222                (2, 2),
223                (40000000, 0),
224                (30000000, 355000000),
225                (355000000, 10000000),
226                param_set::ScanningMode(0b00000000),
227            )
228        ),
229        (
230            lat_lon_grid_definition_for_westward_from_0_deg,
231            LatLonGrid {
232                shape: (2, 2),
233                first_point: (40., 0.),
234                last_point: (30., -5.),
235                i_consecutive: true,
236            },
237            (
238                (2, 2),
239                (40000000, 0),
240                (30000000, 355000000),
241                (5000000, 10000000),
242                param_set::ScanningMode(0b10000000),
243            )
244        ),
245        (
246            lat_lon_grid_definition_for_eastward_from_180_deg,
247            LatLonGrid {
248                shape: (2, 2),
249                first_point: (40., -180.),
250                last_point: (30., 175.),
251                i_consecutive: true,
252            },
253            (
254                (2, 2),
255                (40000000, 180000000),
256                (30000000, 175000000),
257                (355000000, 10000000),
258                param_set::ScanningMode(0b00000000),
259            )
260        ),
261        (
262            lat_lon_grid_definition_for_westward_from_180_deg,
263            LatLonGrid {
264                shape: (2, 2),
265                first_point: (40., 180.),
266                last_point: (30., 175.),
267                i_consecutive: true,
268            },
269            (
270                (2, 2),
271                (40000000, 180000000),
272                (30000000, 175000000),
273                (5000000, 10000000),
274                param_set::ScanningMode(0b10000000),
275            )
276        ),
277    }
278
279    #[test]
280    fn normalized_longitude_value() {
281        assert_eq!(normalized_longitude(-360.), 0.);
282        assert_eq!(normalized_longitude(-180.), 180.);
283        assert_eq!(normalized_longitude(0.), 0.);
284        assert_eq!(normalized_longitude(180.), 180.);
285        assert_eq!(normalized_longitude(360.), 360.);
286    }
287}