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, param_set::ScanningMode,
9    },
10};
11
12#[derive(Debug, PartialEq)]
13pub enum GridDefinitionTemplateValues {
14    Template0(Template3_0),
15    Template1(Template3_1),
16    Template20(Template3_20),
17    Template30(Template3_30),
18    Template40(Template3_40),
19}
20
21impl GridShortName for GridDefinitionTemplateValues {
22    fn short_name(&self) -> &'static str {
23        match self {
24            Self::Template0(def) => def.lat_lon.short_name(),
25            Self::Template1(def) => def.short_name(),
26            Self::Template20(def) => def.short_name(),
27            Self::Template30(def) => def.short_name(),
28            Self::Template40(def) => def.gaussian.short_name(),
29        }
30    }
31}
32
33impl GridPointIndex for GridDefinitionTemplateValues {
34    fn grid_shape(&self) -> (usize, usize) {
35        match self {
36            Self::Template0(def) => def.lat_lon.grid_shape(),
37            Self::Template1(def) => def.grid_shape(),
38            Self::Template20(def) => def.grid_shape(),
39            Self::Template30(def) => def.grid_shape(),
40            Self::Template40(def) => def.gaussian.grid_shape(),
41        }
42    }
43
44    fn scanning_mode(&self) -> &ScanningMode {
45        match self {
46            Self::Template0(def) => def.lat_lon.scanning_mode(),
47            Self::Template1(def) => def.scanning_mode(),
48            Self::Template20(def) => def.scanning_mode(),
49            Self::Template30(def) => def.scanning_mode(),
50            Self::Template40(def) => def.gaussian.scanning_mode(),
51        }
52    }
53}
54
55impl LatLons for GridDefinitionTemplateValues {
56    type Iter<'a>
57        = GridPointLatLons
58    where
59        Self: 'a;
60
61    fn latlons<'a>(&'a self) -> Result<Self::Iter<'a>, GribError> {
62        let iter = match self {
63            Self::Template0(def) => GridPointLatLons::from(def.lat_lon.latlons()?),
64            Self::Template1(def) => GridPointLatLons::from(def.latlons()?),
65            #[cfg(feature = "gridpoints-proj")]
66            Self::Template20(def) => GridPointLatLons::from(def.latlons()?),
67            #[cfg(feature = "gridpoints-proj")]
68            Self::Template30(def) => GridPointLatLons::from(def.latlons()?),
69            Self::Template40(def) => GridPointLatLons::from(def.gaussian.latlons()?),
70            #[cfg(not(feature = "gridpoints-proj"))]
71            _ => {
72                return Err(GribError::NotSupported(
73                    "lat/lon computation support for the template is dropped in this build"
74                        .to_owned(),
75                ));
76            }
77        };
78        Ok(iter)
79    }
80}
81
82impl TryFrom<&GridDefinition> for GridDefinitionTemplateValues {
83    type Error = GribError;
84
85    fn try_from(value: &GridDefinition) -> Result<Self, Self::Error> {
86        // In the future, we should switch the implementation like this:
87        //
88        // ```
89        // let buf = &value.payload;
90        // let mut pos = 0;
91        // let payload = crate::def::grib2::Section3Payload::try_from_slice(buf, &mut pos)
92        //     .map_err(|e| GribError::Unknown(e.to_owned()))?;
93        // let template = match payload.template {
94        // ..
95        // }
96        // ```
97        //
98        // However, since the current implementation of the templates has many
99        // limitations, to prevent errors, the template reading process is implemented
100        // as follows.
101        let buf = &value.payload[9..];
102        let mut pos = 0;
103        let num = value.grid_tmpl_num();
104        let template = match num {
105            0 => GridDefinitionTemplateValues::Template0(
106                Template3_0::try_from_slice(buf, &mut pos)
107                    .map_err(|e| GribError::Unknown(e.to_owned()))?,
108            ),
109            1 => GridDefinitionTemplateValues::Template1(
110                Template3_1::try_from_slice(buf, &mut pos)
111                    .map_err(|e| GribError::Unknown(e.to_owned()))?,
112            ),
113            20 => GridDefinitionTemplateValues::Template20(
114                Template3_20::try_from_slice(buf, &mut pos)
115                    .map_err(|e| GribError::Unknown(e.to_owned()))?,
116            ),
117            30 => GridDefinitionTemplateValues::Template30(
118                Template3_30::try_from_slice(buf, &mut pos)
119                    .map_err(|e| GribError::Unknown(e.to_owned()))?,
120            ),
121            40 => GridDefinitionTemplateValues::Template40(
122                Template3_40::try_from_slice(buf, &mut pos)
123                    .map_err(|e| GribError::Unknown(e.to_owned()))?,
124            ),
125            _ => {
126                return Err(GribError::NotSupported(format!(
127                    "lat/lon computation support for the template {num} is dropped in this build"
128                )));
129            }
130        };
131        if buf.len() > pos {
132            return Err(GribError::NotSupported(
133                "template with list of number of points".to_owned(),
134            ));
135        }
136        Ok(template)
137    }
138}
139
140/// A functionality to return a short name of the grid system.
141pub trait GridShortName {
142    /// Returns the grid type.
143    ///
144    /// The grid types are denoted as short strings based on `gridType` used in
145    /// ecCodes.
146    ///
147    /// This is provided primarily for debugging and simple notation purposes.
148    /// It is better to use enum variants instead of the string notation to
149    /// determine the grid type.
150    fn short_name(&self) -> &'static str;
151}
152
153/// A functionality to generate an iterator over latitude/longitude of grid
154/// points.
155pub trait LatLons {
156    type Iter<'a>: Iterator<Item = (f32, f32)>
157    where
158        Self: 'a;
159
160    /// Computes and returns an iterator over latitudes and longitudes of grid
161    /// points in degrees.
162    ///
163    /// The order of lat/lon data of grid points is the same as the order of the
164    /// grid point values, defined by the scanning mode
165    /// ([`ScanningMode`](`crate::def::grib2::template::param_set::ScanningMode`)) in the data.
166    fn latlons<'a>(&'a self) -> Result<Self::Iter<'a>, GribError>;
167}
168
169/// An iterator over latitudes and longitudes of grid points in a submessage.
170///
171/// This `struct` is created by the [`latlons`] method on [`LatLons`]
172/// implemented for [`SubMessage`]. See its documentation for more.
173///
174/// [`latlons`]: crate::context::SubMessage::latlons
175/// [`SubMessage`]: crate::context::SubMessage
176#[derive(Clone)]
177pub struct GridPointLatLons(LatLonsWrapper);
178
179impl Iterator for GridPointLatLons {
180    type Item = (f32, f32);
181
182    fn next(&mut self) -> Option<Self::Item> {
183        match self {
184            Self(LatLonsWrapper::SigR(iter)) => iter.next(),
185            Self(LatLonsWrapper::SigUR(iter)) => iter.next(),
186            Self(LatLonsWrapper::SigIf(iter)) => iter.next(),
187        }
188    }
189
190    fn size_hint(&self) -> (usize, Option<usize>) {
191        match self {
192            Self(LatLonsWrapper::SigR(iter)) => iter.size_hint(),
193            Self(LatLonsWrapper::SigUR(iter)) => iter.size_hint(),
194            Self(LatLonsWrapper::SigIf(iter)) => iter.size_hint(),
195        }
196    }
197}
198
199impl From<RegularGridIterator> for GridPointLatLons {
200    fn from(value: RegularGridIterator) -> Self {
201        Self(LatLonsWrapper::SigR(value))
202    }
203}
204
205impl From<Unrotate<RegularGridIterator>> for GridPointLatLons {
206    fn from(value: Unrotate<RegularGridIterator>) -> Self {
207        Self(LatLonsWrapper::SigUR(value))
208    }
209}
210
211impl From<std::vec::IntoIter<(f32, f32)>> for GridPointLatLons {
212    fn from(value: std::vec::IntoIter<(f32, f32)>) -> Self {
213        Self(LatLonsWrapper::SigIf(value))
214    }
215}
216
217#[derive(Clone)]
218enum LatLonsWrapper {
219    SigR(RegularGridIterator),
220    SigUR(Unrotate<RegularGridIterator>),
221    SigIf(std::vec::IntoIter<(f32, f32)>),
222}
223
224/// A functionality to generate an iterator over 2D index `(i, j)` of grid
225/// points.
226///
227/// # Examples
228///
229/// ```
230/// use grib::{GridPointIndex, def::grib2::template::param_set::ScanningMode};
231///
232/// struct Grid {
233///     ni: u32,
234///     nj: u32,
235///     scanning_mode: ScanningMode,
236/// }
237///
238/// impl GridPointIndex for Grid {
239///     fn grid_shape(&self) -> (usize, usize) {
240///         (self.ni as usize, self.nj as usize)
241///     }
242///
243///     fn scanning_mode(&self) -> &ScanningMode {
244///         &self.scanning_mode
245///     }
246/// }
247///
248/// let grid_2x3 = Grid {
249///     ni: 2,
250///     nj: 3,
251///     scanning_mode: ScanningMode(0b01000000),
252/// };
253/// assert_eq!(grid_2x3.grid_shape(), (2, 3));
254///
255/// let mut iter = grid_2x3.ij().unwrap();
256/// assert_eq!(iter.next(), Some((0, 0)));
257/// assert_eq!(iter.next(), Some((1, 0)));
258/// assert_eq!(iter.next(), Some((0, 1)));
259/// assert_eq!(iter.next(), Some((1, 1)));
260/// assert_eq!(iter.next(), Some((0, 2)));
261/// assert_eq!(iter.next(), Some((1, 2)));
262/// assert_eq!(iter.next(), None);
263/// ```
264pub trait GridPointIndex {
265    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
266    /// the i and j directions.
267    fn grid_shape(&self) -> (usize, usize);
268
269    /// Returns [`ScanningMode`] used for the iteration.
270    fn scanning_mode(&self) -> &ScanningMode;
271
272    /// Returns an iterator over 2D index `(i, j)` of grid points.
273    fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
274        GridPointIndexIterator::new(self.grid_shape(), *self.scanning_mode())
275    }
276}
277
278/// An iterator over 2D index `(i, j)` of grid points.
279///
280/// This `struct` is created by the [`GridPointIndex::ij`] method. See its
281/// documentation for more.
282#[derive(Clone)]
283pub struct GridPointIndexIterator {
284    major_len: usize,
285    minor_len: usize,
286    scanning_mode: ScanningMode,
287    major_pos: usize,
288    minor_pos: usize,
289    increments: bool,
290}
291
292impl GridPointIndexIterator {
293    pub(crate) fn new(
294        (i_len, j_len): (usize, usize),
295        scanning_mode: ScanningMode,
296    ) -> Result<Self, GribError> {
297        if scanning_mode.has_unsupported_flags() {
298            let ScanningMode(mode) = scanning_mode;
299            return Err(GribError::NotSupported(format!("scanning mode {mode}")));
300        }
301
302        let (major_len, minor_len) = if scanning_mode.is_consecutive_for_i() {
303            (j_len, i_len)
304        } else {
305            (i_len, j_len)
306        };
307
308        Ok(Self {
309            major_len,
310            minor_len,
311            scanning_mode,
312            minor_pos: 0,
313            major_pos: 0,
314            increments: true,
315        })
316    }
317}
318
319impl Iterator for GridPointIndexIterator {
320    type Item = (usize, usize);
321
322    fn next(&mut self) -> Option<Self::Item> {
323        if self.major_pos == self.major_len {
324            return None;
325        }
326
327        let minor = if self.increments {
328            self.minor_pos
329        } else {
330            self.minor_len - self.minor_pos - 1
331        };
332        let major = self.major_pos;
333
334        self.minor_pos += 1;
335        if self.minor_pos == self.minor_len {
336            self.major_pos += 1;
337            self.minor_pos = 0;
338            if self.scanning_mode.scans_alternating_rows() {
339                self.increments = !self.increments;
340            }
341        }
342
343        if self.scanning_mode.is_consecutive_for_i() {
344            Some((minor, major))
345        } else {
346            Some((major, minor))
347        }
348    }
349
350    fn size_hint(&self) -> (usize, Option<usize>) {
351        let len = (self.major_len - self.major_pos) * self.minor_len - self.minor_pos;
352        (len, Some(len))
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use crate::def::grib2::template::param_set::EarthShape;
360
361    #[test]
362    fn grid_definition_template_0() {
363        // data taken from submessage #0.0 of
364        // `Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin.xz`
365        // in `testdata`
366        let data = GridDefinition::from_payload(
367            vec![
368                0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff,
369                0xff, 0x01, 0x03, 0xcd, 0x39, 0xfa, 0x01, 0x03, 0xc9, 0xf6, 0xa3, 0x00, 0x00, 0x01,
370                0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x02,
371                0xdb, 0xc9, 0x3d, 0x07, 0x09, 0x7d, 0xa4, 0x30, 0x01, 0x31, 0xcf, 0xc3, 0x08, 0xef,
372                0xdd, 0x5c, 0x00, 0x01, 0xe8, 0x48, 0x00, 0x01, 0x45, 0x85, 0x00,
373            ]
374            .into_boxed_slice(),
375        )
376        .unwrap();
377
378        let actual = GridDefinitionTemplateValues::try_from(&data).unwrap();
379        let expected = GridDefinitionTemplateValues::Template0(Template3_0 {
380            earth: EarthShape {
381                shape: 4,
382                spherical_earth_radius_scale_factor: 0xff,
383                spherical_earth_radius_scaled_value: 0xffffffff,
384                major_axis_scale_factor: 1,
385                major_axis_scaled_value: 63781370,
386                minor_axis_scale_factor: 1,
387                minor_axis_scaled_value: 63567523,
388            },
389            lat_lon: crate::def::grib2::template::param_set::LatLonGrid {
390                grid: crate::def::grib2::template::param_set::Grid {
391                    ni: 256,
392                    nj: 336,
393                    initial_production_domain_basic_angle: 0,
394                    basic_angle_subdivisions: 0xffffffff,
395                    first_point_lat: 47958333,
396                    first_point_lon: 118062500,
397                    resolution_and_component_flags:
398                        crate::def::grib2::template::param_set::ResolutionAndComponentFlags(
399                            0b00110000,
400                        ),
401                    last_point_lat: 20041667,
402                    last_point_lon: 149937500,
403                },
404                i_direction_inc: 125000,
405                j_direction_inc: 83333,
406                scanning_mode: crate::def::grib2::template::param_set::ScanningMode(0b00000000),
407            },
408        });
409        assert_eq!(actual, expected);
410    }
411}
412
413mod earth;
414mod flags;
415mod gaussian;
416mod helpers;
417mod lambert;
418mod latlon;
419mod polar_stereographic;
420mod rotated_ll;