grib/grid/
lambert.rs

1use super::{earth::EarthShapeDefinition, GridPointIndexIterator, ScanningMode};
2use crate::{
3    error::GribError,
4    helpers::{read_as, GribInt},
5};
6
7#[derive(Debug, PartialEq, Eq)]
8pub struct LambertGridDefinition {
9    pub earth_shape: EarthShapeDefinition,
10    pub ni: u32,
11    pub nj: u32,
12    pub first_point_lat: i32,
13    pub first_point_lon: i32,
14    pub lad: i32,
15    pub lov: i32,
16    pub dx: u32,
17    pub dy: u32,
18    pub scanning_mode: ScanningMode,
19    pub latin1: i32,
20    pub latin2: i32,
21}
22
23impl LambertGridDefinition {
24    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
25    /// the i and j directions.
26    ///
27    /// Examples
28    ///
29    /// ```
30    /// let def = grib::LambertGridDefinition {
31    ///     earth_shape: grib::EarthShapeDefinition {
32    ///         shape_of_the_earth: 1,
33    ///         scale_factor_of_radius_of_spherical_earth: 0,
34    ///         scaled_value_of_radius_of_spherical_earth: 6371200,
35    ///         scale_factor_of_earth_major_axis: 0,
36    ///         scaled_value_of_earth_major_axis: 0,
37    ///         scale_factor_of_earth_minor_axis: 0,
38    ///         scaled_value_of_earth_minor_axis: 0,
39    ///     },
40    ///     ni: 2,
41    ///     nj: 3,
42    ///     first_point_lat: 0,
43    ///     first_point_lon: 0,
44    ///     lad: 0,
45    ///     lov: 0,
46    ///     dx: 1000,
47    ///     dy: 1000,
48    ///     scanning_mode: grib::ScanningMode(0b01000000),
49    ///     latin1: 0,
50    ///     latin2: 0,
51    /// };
52    /// let shape = def.grid_shape();
53    /// assert_eq!(shape, (2, 3));
54    /// ```
55    pub fn grid_shape(&self) -> (usize, usize) {
56        (self.ni as usize, self.nj as usize)
57    }
58
59    /// Returns the grid type.
60    pub fn short_name(&self) -> &'static str {
61        "lambert"
62    }
63
64    /// Returns an iterator over `(i, j)` of grid points.
65    ///
66    /// Note that this is a low-level API and it is not checked that the number
67    /// of iterator iterations is consistent with the number of grid points
68    /// defined in the data.
69    ///
70    /// Examples
71    ///
72    /// ```
73    /// let def = grib::LambertGridDefinition {
74    ///     earth_shape: grib::EarthShapeDefinition {
75    ///         shape_of_the_earth: 1,
76    ///         scale_factor_of_radius_of_spherical_earth: 0,
77    ///         scaled_value_of_radius_of_spherical_earth: 6371200,
78    ///         scale_factor_of_earth_major_axis: 0,
79    ///         scaled_value_of_earth_major_axis: 0,
80    ///         scale_factor_of_earth_minor_axis: 0,
81    ///         scaled_value_of_earth_minor_axis: 0,
82    ///     },
83    ///     ni: 2,
84    ///     nj: 3,
85    ///     first_point_lat: 0,
86    ///     first_point_lon: 0,
87    ///     lad: 0,
88    ///     lov: 0,
89    ///     dx: 1000,
90    ///     dy: 1000,
91    ///     scanning_mode: grib::ScanningMode(0b01000000),
92    ///     latin1: 0,
93    ///     latin2: 0,
94    /// };
95    /// let ij = def.ij();
96    /// assert!(ij.is_ok());
97    ///
98    /// let mut ij = ij.unwrap();
99    /// assert_eq!(ij.next(), Some((0, 0)));
100    /// assert_eq!(ij.next(), Some((1, 0)));
101    /// assert_eq!(ij.next(), Some((0, 1)));
102    /// ```
103    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
104        if self.scanning_mode.has_unsupported_flags() {
105            let ScanningMode(mode) = self.scanning_mode;
106            return Err(GribError::NotSupported(format!("scanning mode {mode}")));
107        }
108
109        let iter =
110            GridPointIndexIterator::new(self.ni as usize, self.nj as usize, self.scanning_mode);
111        Ok(iter)
112    }
113
114    /// Returns an iterator over latitudes and longitudes of grid points in
115    /// degrees.
116    ///
117    /// Note that this is a low-level API and it is not checked that the number
118    /// of iterator iterations is consistent with the number of grid points
119    /// defined in the data.
120    #[cfg(feature = "gridpoints-proj")]
121    #[cfg_attr(docsrs, doc(cfg(feature = "gridpoints-proj")))]
122    pub fn latlons(&self) -> Result<std::vec::IntoIter<(f32, f32)>, GribError> {
123        let lad = self.lad as f64 * 1e-6;
124        let lov = self.lov as f64 * 1e-6;
125        let latin1 = self.latin1 as f64 * 1e-6;
126        let latin2 = self.latin2 as f64 * 1e-6;
127        let (a, b) = self.earth_shape.radii().ok_or_else(|| {
128            GribError::NotSupported(format!(
129                "unknown value of Code Table 3.2 (shape of the Earth): {}",
130                self.earth_shape.shape_of_the_earth
131            ))
132        })?;
133        let proj_def = format!(
134            "+a={a} +b={b} +proj=lcc +lat_0={lad} +lon_0={lov} +lat_1={latin1} +lat_2={latin2}"
135        );
136
137        let dx = self.dx as f64 * 1e-3;
138        let dy = self.dy as f64 * 1e-3;
139        let dx = if !self.scanning_mode.scans_positively_for_i() && dx > 0. {
140            -dx
141        } else {
142            dx
143        };
144        let dy = if !self.scanning_mode.scans_positively_for_j() && dy > 0. {
145            -dy
146        } else {
147            dy
148        };
149
150        super::helpers::latlons_from_projection_definition_and_first_point(
151            &proj_def,
152            (
153                self.first_point_lat as f64 * 1e-6,
154                self.first_point_lon as f64 * 1e-6,
155            ),
156            (dx, dy),
157            self.ij()?,
158        )
159    }
160
161    pub(crate) fn from_buf(buf: &[u8]) -> Self {
162        let earth_shape = EarthShapeDefinition::from_buf(buf);
163        let ni = read_as!(u32, buf, 16);
164        let nj = read_as!(u32, buf, 20);
165        let first_point_lat = read_as!(u32, buf, 24).as_grib_int();
166        let first_point_lon = read_as!(u32, buf, 28).as_grib_int();
167        let lad = read_as!(u32, buf, 33).as_grib_int();
168        let lov = read_as!(u32, buf, 37).as_grib_int();
169        let dx = read_as!(u32, buf, 41);
170        let dy = read_as!(u32, buf, 45);
171        let scanning_mode = read_as!(u8, buf, 50);
172        let latin1 = read_as!(u32, buf, 51).as_grib_int();
173        let latin2 = read_as!(u32, buf, 55).as_grib_int();
174        Self {
175            earth_shape,
176            ni,
177            nj,
178            first_point_lat,
179            first_point_lon,
180            lad,
181            lov,
182            dx,
183            dy,
184            scanning_mode: ScanningMode(scanning_mode),
185            latin1,
186            latin2,
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use std::io::{BufReader, Read};
194
195    use super::*;
196
197    #[test]
198    fn lambert_grid_definition_from_buf() -> Result<(), Box<dyn std::error::Error>> {
199        let mut buf = Vec::new();
200
201        let f = std::fs::File::open("testdata/ds.critfireo.bin.xz")?;
202        let f = BufReader::new(f);
203        let mut f = xz2::bufread::XzDecoder::new(f);
204        f.read_to_end(&mut buf)?;
205
206        let actual = LambertGridDefinition::from_buf(&buf[0x83..]);
207        let expected = LambertGridDefinition {
208            earth_shape: EarthShapeDefinition {
209                shape_of_the_earth: 1,
210                scale_factor_of_radius_of_spherical_earth: 0,
211                scaled_value_of_radius_of_spherical_earth: 6371200,
212                scale_factor_of_earth_major_axis: 0,
213                scaled_value_of_earth_major_axis: 0,
214                scale_factor_of_earth_minor_axis: 0,
215                scaled_value_of_earth_minor_axis: 0,
216            },
217            ni: 2145,
218            nj: 1377,
219            first_point_lat: 20190000,
220            first_point_lon: 238449996,
221            lad: 25000000,
222            lov: 265000000,
223            dx: 2539703,
224            dy: 2539703,
225            scanning_mode: ScanningMode(0b01010000),
226            latin1: 25000000,
227            latin2: 25000000,
228        };
229        assert_eq!(actual, expected);
230
231        Ok(())
232    }
233
234    #[cfg(feature = "gridpoints-proj")]
235    #[test]
236    fn lambert_grid_latlon_computation() -> Result<(), Box<dyn std::error::Error>> {
237        use crate::grid::helpers::test_helpers::assert_coord_almost_eq;
238        let grid_def = LambertGridDefinition {
239            earth_shape: EarthShapeDefinition {
240                shape_of_the_earth: 1,
241                scale_factor_of_radius_of_spherical_earth: 0,
242                scaled_value_of_radius_of_spherical_earth: 6371200,
243                scale_factor_of_earth_major_axis: 0,
244                scaled_value_of_earth_major_axis: 0,
245                scale_factor_of_earth_minor_axis: 0,
246                scaled_value_of_earth_minor_axis: 0,
247            },
248            ni: 2145,
249            nj: 1377,
250            first_point_lat: 20190000,
251            first_point_lon: 238449996,
252            lad: 25000000,
253            lov: 265000000,
254            dx: 2539703,
255            dy: 2539703,
256            scanning_mode: ScanningMode(0b01010000),
257            latin1: 25000000,
258            latin2: 25000000,
259        };
260        let latlons = grid_def.latlons()?.collect::<Vec<_>>();
261
262        // Following lat/lon values are taken from the calculation results using pygrib.
263        let delta = 1e-10;
264        assert_coord_almost_eq(latlons[0], (20.19, -121.550004), delta);
265        assert_coord_almost_eq(latlons[1], (20.19442682, -121.52621665), delta);
266        assert_coord_almost_eq(
267            latlons[latlons.len() - 2],
268            (50.10756403, -60.91298217),
269            delta,
270        );
271        assert_coord_almost_eq(
272            latlons[latlons.len() - 1],
273            (50.1024611, -60.88202274),
274            delta,
275        );
276
277        Ok(())
278    }
279}