1use crate::def::grib2::template::param_set;
2
3pub struct LatLonGrid {
27 pub shape: (usize, usize),
29 pub first_point: (f64, f64),
33 pub last_point: (f64, f64),
36 pub i_consecutive: bool,
39}
40
41impl LatLonGrid {
42 pub fn num_points(&self) -> usize {
44 self.shape.0 * self.shape.1
45 }
46
47 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}