1use grib_template_helpers::TryFromSlice;
2use helpers::RegularGridIterator;
3
4pub use self::{gaussian::compute_gaussian_latitudes, rotated_ll::Unrotate};
5use crate::{
6 GribError, GridDefinition,
7 def::grib2::template::{
8 Template3_0, Template3_1, Template3_20, Template3_30, Template3_40,
9 param_set::{Grid, ScanningMode},
10 },
11};
12
13#[derive(Debug, PartialEq)]
14pub enum GridDefinitionTemplateValues {
15 Template0(Template3_0),
16 Template1(Template3_1),
17 Template20(Template3_20),
18 Template30(Template3_30),
19 Template40(Template3_40),
20}
21
22impl GridShortName for GridDefinitionTemplateValues {
23 fn short_name(&self) -> &'static str {
24 match self {
25 Self::Template0(def) => def.lat_lon.short_name(),
26 Self::Template1(def) => def.short_name(),
27 Self::Template20(def) => def.short_name(),
28 Self::Template30(def) => def.short_name(),
29 Self::Template40(def) => def.gaussian.short_name(),
30 }
31 }
32}
33
34impl GridPointIndex for GridDefinitionTemplateValues {
35 fn grid_shape(&self) -> (usize, usize) {
36 match self {
37 Self::Template0(def) => def.lat_lon.grid_shape(),
38 Self::Template1(def) => def.grid_shape(),
39 Self::Template20(def) => def.grid_shape(),
40 Self::Template30(def) => def.grid_shape(),
41 Self::Template40(def) => def.gaussian.grid_shape(),
42 }
43 }
44
45 fn scanning_mode(&self) -> &ScanningMode {
46 match self {
47 Self::Template0(def) => def.lat_lon.scanning_mode(),
48 Self::Template1(def) => def.scanning_mode(),
49 Self::Template20(def) => def.scanning_mode(),
50 Self::Template30(def) => def.scanning_mode(),
51 Self::Template40(def) => def.gaussian.scanning_mode(),
52 }
53 }
54}
55
56impl LatLons for GridDefinitionTemplateValues {
57 type Iter<'a>
58 = GridPointLatLons
59 where
60 Self: 'a;
61
62 fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError> {
63 let iter = match self {
64 Self::Template0(def) => GridPointLatLons::from(def.lat_lon.latlons_unchecked()?),
65 Self::Template1(def) => GridPointLatLons::from(def.latlons_unchecked()?),
66 #[cfg(feature = "gridpoints-proj")]
67 Self::Template20(def) => GridPointLatLons::from(def.latlons_unchecked()?),
68 #[cfg(feature = "gridpoints-proj")]
69 Self::Template30(def) => GridPointLatLons::from(def.latlons_unchecked()?),
70 Self::Template40(def) => GridPointLatLons::from(def.gaussian.latlons_unchecked()?),
71 #[cfg(not(feature = "gridpoints-proj"))]
72 _ => {
73 return Err(GribError::NotSupported(
74 "lat/lon computation support for the template is dropped in this build"
75 .to_owned(),
76 ));
77 }
78 };
79 Ok(iter)
80 }
81}
82
83impl TryFrom<&GridDefinition> for GridDefinitionTemplateValues {
84 type Error = GribError;
85
86 fn try_from(value: &GridDefinition) -> Result<Self, Self::Error> {
87 let buf = &value.payload[9..];
103 let mut pos = 0;
104 let num = value.grid_tmpl_num();
105 let template = match num {
106 0 => GridDefinitionTemplateValues::Template0(
107 Template3_0::try_from_slice(buf, &mut pos)
108 .map_err(|e| GribError::Unknown(e.to_owned()))?,
109 ),
110 1 => GridDefinitionTemplateValues::Template1(
111 Template3_1::try_from_slice(buf, &mut pos)
112 .map_err(|e| GribError::Unknown(e.to_owned()))?,
113 ),
114 20 => GridDefinitionTemplateValues::Template20(
115 Template3_20::try_from_slice(buf, &mut pos)
116 .map_err(|e| GribError::Unknown(e.to_owned()))?,
117 ),
118 30 => GridDefinitionTemplateValues::Template30(
119 Template3_30::try_from_slice(buf, &mut pos)
120 .map_err(|e| GribError::Unknown(e.to_owned()))?,
121 ),
122 40 => GridDefinitionTemplateValues::Template40(
123 Template3_40::try_from_slice(buf, &mut pos)
124 .map_err(|e| GribError::Unknown(e.to_owned()))?,
125 ),
126 _ => {
127 return Err(GribError::NotSupported(format!(
128 "lat/lon computation support for the template {num} is dropped in this build"
129 )));
130 }
131 };
132 if buf.len() > pos {
133 return Err(GribError::NotSupported(
134 "template with list of number of points".to_owned(),
135 ));
136 }
137 Ok(template)
138 }
139}
140
141pub trait GridShortName {
143 fn short_name(&self) -> &'static str;
152}
153
154pub trait LatLons {
157 type Iter<'a>: Iterator<Item = (f32, f32)>
158 where
159 Self: 'a;
160
161 fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError>;
166
167 #[allow(clippy::type_complexity)]
175 fn latlons<'a>(
176 &'a self,
177 ) -> Result<std::iter::Map<Self::Iter<'a>, fn((f32, f32)) -> (f32, f32)>, GribError> {
178 let iter = self
179 .latlons_unchecked()?
180 .map(helpers::normalize_latlon as fn((f32, f32)) -> (f32, f32));
181 Ok(iter)
182 }
183}
184
185#[derive(Clone)]
193pub struct GridPointLatLons(LatLonsWrapper);
194
195impl Iterator for GridPointLatLons {
196 type Item = (f32, f32);
197
198 fn next(&mut self) -> Option<Self::Item> {
199 match self {
200 Self(LatLonsWrapper::SigR(iter)) => iter.next(),
201 Self(LatLonsWrapper::SigUR(iter)) => iter.next(),
202 Self(LatLonsWrapper::SigIf(iter)) => iter.next(),
203 }
204 }
205
206 fn size_hint(&self) -> (usize, Option<usize>) {
207 match self {
208 Self(LatLonsWrapper::SigR(iter)) => iter.size_hint(),
209 Self(LatLonsWrapper::SigUR(iter)) => iter.size_hint(),
210 Self(LatLonsWrapper::SigIf(iter)) => iter.size_hint(),
211 }
212 }
213}
214
215impl From<RegularGridIterator> for GridPointLatLons {
216 fn from(value: RegularGridIterator) -> Self {
217 Self(LatLonsWrapper::SigR(value))
218 }
219}
220
221impl From<Unrotate<RegularGridIterator>> for GridPointLatLons {
222 fn from(value: Unrotate<RegularGridIterator>) -> Self {
223 Self(LatLonsWrapper::SigUR(value))
224 }
225}
226
227impl From<std::vec::IntoIter<(f32, f32)>> for GridPointLatLons {
228 fn from(value: std::vec::IntoIter<(f32, f32)>) -> Self {
229 Self(LatLonsWrapper::SigIf(value))
230 }
231}
232
233#[derive(Clone)]
234enum LatLonsWrapper {
235 SigR(RegularGridIterator),
236 SigUR(Unrotate<RegularGridIterator>),
237 SigIf(std::vec::IntoIter<(f32, f32)>),
238}
239
240pub trait GridPointIndex {
281 fn grid_shape(&self) -> (usize, usize);
284
285 fn scanning_mode(&self) -> &ScanningMode;
287
288 fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
290 GridPointIndexIterator::new(self.grid_shape(), *self.scanning_mode())
291 }
292}
293
294#[derive(Clone)]
299pub struct GridPointIndexIterator {
300 major_len: usize,
301 minor_len: usize,
302 scanning_mode: ScanningMode,
303 major_pos: usize,
304 minor_pos: usize,
305 increments: bool,
306}
307
308impl GridPointIndexIterator {
309 pub(crate) fn new(
310 (i_len, j_len): (usize, usize),
311 scanning_mode: ScanningMode,
312 ) -> Result<Self, GribError> {
313 if scanning_mode.has_unsupported_flags() {
314 let ScanningMode(mode) = scanning_mode;
315 return Err(GribError::NotSupported(format!("scanning mode {mode}")));
316 }
317
318 let (major_len, minor_len) = if scanning_mode.is_consecutive_for_i() {
319 (j_len, i_len)
320 } else {
321 (i_len, j_len)
322 };
323
324 Ok(Self {
325 major_len,
326 minor_len,
327 scanning_mode,
328 minor_pos: 0,
329 major_pos: 0,
330 increments: true,
331 })
332 }
333}
334
335impl Iterator for GridPointIndexIterator {
336 type Item = (usize, usize);
337
338 fn next(&mut self) -> Option<Self::Item> {
339 if self.major_pos == self.major_len {
340 return None;
341 }
342
343 let minor = if self.increments {
344 self.minor_pos
345 } else {
346 self.minor_len - self.minor_pos - 1
347 };
348 let major = self.major_pos;
349
350 self.minor_pos += 1;
351 if self.minor_pos == self.minor_len {
352 self.major_pos += 1;
353 self.minor_pos = 0;
354 if self.scanning_mode.scans_alternating_rows() {
355 self.increments = !self.increments;
356 }
357 }
358
359 if self.scanning_mode.is_consecutive_for_i() {
360 Some((minor, major))
361 } else {
362 Some((major, minor))
363 }
364 }
365
366 fn size_hint(&self) -> (usize, Option<usize>) {
367 let len = (self.major_len - self.major_pos) * self.minor_len - self.minor_pos;
368 (len, Some(len))
369 }
370}
371
372pub(crate) trait AngleUnit {
373 fn angle_unit(&self) -> f64;
374}
375
376impl AngleUnit for Grid {
377 fn angle_unit(&self) -> f64 {
378 let basic_angle = self.initial_production_domain_basic_angle;
379 let sub_angle = self.basic_angle_subdivisions;
380 if basic_angle == 0 {
381 1e-6
382 } else {
383 basic_angle as f64 / sub_angle as f64
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::def::grib2::template::param_set::EarthShape;
392
393 #[test]
394 fn grid_definition_template_0() {
395 let data = GridDefinition::from_payload(
399 vec![
400 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff,
401 0xff, 0x01, 0x03, 0xcd, 0x39, 0xfa, 0x01, 0x03, 0xc9, 0xf6, 0xa3, 0x00, 0x00, 0x01,
402 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x02,
403 0xdb, 0xc9, 0x3d, 0x07, 0x09, 0x7d, 0xa4, 0x30, 0x01, 0x31, 0xcf, 0xc3, 0x08, 0xef,
404 0xdd, 0x5c, 0x00, 0x01, 0xe8, 0x48, 0x00, 0x01, 0x45, 0x85, 0x00,
405 ]
406 .into_boxed_slice(),
407 )
408 .unwrap();
409
410 let actual = GridDefinitionTemplateValues::try_from(&data).unwrap();
411 let expected = GridDefinitionTemplateValues::Template0(Template3_0 {
412 earth: EarthShape {
413 shape: 4,
414 spherical_earth_radius_scale_factor: 0xff,
415 spherical_earth_radius_scaled_value: 0xffffffff,
416 major_axis_scale_factor: 1,
417 major_axis_scaled_value: 63781370,
418 minor_axis_scale_factor: 1,
419 minor_axis_scaled_value: 63567523,
420 },
421 lat_lon: crate::def::grib2::template::param_set::LatLonGrid {
422 grid: crate::def::grib2::template::param_set::Grid {
423 ni: 256,
424 nj: 336,
425 initial_production_domain_basic_angle: 0,
426 basic_angle_subdivisions: 0xffffffff,
427 first_point_lat: 47958333,
428 first_point_lon: 118062500,
429 resolution_and_component_flags:
430 crate::def::grib2::template::param_set::ResolutionAndComponentFlags(
431 0b00110000,
432 ),
433 last_point_lat: 20041667,
434 last_point_lon: 149937500,
435 },
436 i_direction_inc: 125000,
437 j_direction_inc: 83333,
438 scanning_mode: crate::def::grib2::template::param_set::ScanningMode(0b00000000),
439 },
440 });
441 assert_eq!(actual, expected);
442 }
443}
444
445mod earth;
446mod flags;
447mod gaussian;
448mod helpers;
449mod lambert;
450mod latlon;
451mod polar_stereographic;
452mod rotated_ll;