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) -> ¶m_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 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}