Skip to main content

grib/
grid.rs

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        // In the future, we should switch the implementation like this:
88        //
89        // ```
90        // let buf = &value.payload;
91        // let mut pos = 0;
92        // let payload = crate::def::grib2::Section3Payload::try_from_slice(buf, &mut pos)
93        //     .map_err(|e| GribError::Unknown(e.to_owned()))?;
94        // let template = match payload.template {
95        // ..
96        // }
97        // ```
98        //
99        // However, since the current implementation of the templates has many
100        // limitations, to prevent errors, the template reading process is implemented
101        // as follows.
102        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
141/// A functionality to return a short name of the grid system.
142pub trait GridShortName {
143    /// Returns the grid type.
144    ///
145    /// The grid types are denoted as short strings based on `gridType` used in
146    /// ecCodes.
147    ///
148    /// This is provided primarily for debugging and simple notation purposes.
149    /// It is better to use enum variants instead of the string notation to
150    /// determine the grid type.
151    fn short_name(&self) -> &'static str;
152}
153
154/// A functionality to generate an iterator over latitude/longitude of grid
155/// points.
156pub trait LatLons {
157    type Iter<'a>: Iterator<Item = (f32, f32)>
158    where
159        Self: 'a;
160
161    /// Computes and returns an iterator over latitudes and longitudes of grid
162    /// points in degrees. Unlike the return values from [`LatLons::latlons`],
163    /// the longitude values from thie method do not necessarily fall within
164    /// the range `[-180°, 180°]`.
165    fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError>;
166
167    /// Computes and returns an iterator over latitudes and longitudes of grid
168    /// points in degrees. The returned longitude values are converted to fall
169    /// within the range `[-180°, 180°]`.
170    ///
171    /// The order of lat/lon data of grid points is the same as the order of the
172    /// grid point values, defined by the scanning mode
173    /// ([`ScanningMode`](`crate::def::grib2::template::param_set::ScanningMode`)) in the data.
174    #[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/// An iterator over latitudes and longitudes of grid points in a submessage.
186///
187/// This `struct` is created by the [`latlons`] method on [`LatLons`]
188/// implemented for [`SubMessage`]. See its documentation for more.
189///
190/// [`latlons`]: crate::context::SubMessage::latlons
191/// [`SubMessage`]: crate::context::SubMessage
192#[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
240/// A functionality to generate an iterator over 2D index `(i, j)` of grid
241/// points.
242///
243/// # Examples
244///
245/// ```
246/// use grib::{GridPointIndex, def::grib2::template::param_set::ScanningMode};
247///
248/// struct Grid {
249///     ni: u32,
250///     nj: u32,
251///     scanning_mode: ScanningMode,
252/// }
253///
254/// impl GridPointIndex for Grid {
255///     fn grid_shape(&self) -> (usize, usize) {
256///         (self.ni as usize, self.nj as usize)
257///     }
258///
259///     fn scanning_mode(&self) -> &ScanningMode {
260///         &self.scanning_mode
261///     }
262/// }
263///
264/// let grid_2x3 = Grid {
265///     ni: 2,
266///     nj: 3,
267///     scanning_mode: ScanningMode(0b01000000),
268/// };
269/// assert_eq!(grid_2x3.grid_shape(), (2, 3));
270///
271/// let mut iter = grid_2x3.ij().unwrap();
272/// assert_eq!(iter.next(), Some((0, 0)));
273/// assert_eq!(iter.next(), Some((1, 0)));
274/// assert_eq!(iter.next(), Some((0, 1)));
275/// assert_eq!(iter.next(), Some((1, 1)));
276/// assert_eq!(iter.next(), Some((0, 2)));
277/// assert_eq!(iter.next(), Some((1, 2)));
278/// assert_eq!(iter.next(), None);
279/// ```
280pub trait GridPointIndex {
281    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
282    /// the i and j directions.
283    fn grid_shape(&self) -> (usize, usize);
284
285    /// Returns [`ScanningMode`] used for the iteration.
286    fn scanning_mode(&self) -> &ScanningMode;
287
288    /// Returns an iterator over 2D index `(i, j)` of grid points.
289    fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
290        GridPointIndexIterator::new(self.grid_shape(), *self.scanning_mode())
291    }
292}
293
294/// An iterator over 2D index `(i, j)` of grid points.
295///
296/// This `struct` is created by the [`GridPointIndex::ij`] method. See its
297/// documentation for more.
298#[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        // data taken from submessage #0.0 of
396        // `Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin.xz`
397        // in `testdata`
398        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;