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    pub fn latlons(&self) -> Result<std::vec::IntoIter<(f32, f32)>, GribError> {
122        let lad = self.lad as f64 * 1e-6;
123        let lov = self.lov as f64 * 1e-6;
124        let latin1 = self.latin1 as f64 * 1e-6;
125        let latin2 = self.latin2 as f64 * 1e-6;
126        let (a, b) = self.earth_shape.radii().ok_or_else(|| {
127            GribError::NotSupported(format!(
128                "unknown value of Code Table 3.2 (shape of the Earth): {}",
129                self.earth_shape.shape_of_the_earth
130            ))
131        })?;
132        let proj_def = format!(
133            "+a={a} +b={b} +proj=lcc +lat_0={lad} +lon_0={lov} +lat_1={latin1} +lat_2={latin2}"
134        );
135
136        let dx = self.dx as f64 * 1e-3;
137        let dy = self.dy as f64 * 1e-3;
138        let dx = if !self.scanning_mode.scans_positively_for_i() && dx > 0. {
139            -dx
140        } else {
141            dx
142        };
143        let dy = if !self.scanning_mode.scans_positively_for_j() && dy > 0. {
144            -dy
145        } else {
146            dy
147        };
148
149        super::helpers::latlons_from_projection_definition_and_first_point(
150            &proj_def,
151            (
152                self.first_point_lat as f64 * 1e-6,
153                self.first_point_lon as f64 * 1e-6,
154            ),
155            (dx, dy),
156            self.ij()?,
157        )
158    }
159
160    pub(crate) fn from_buf(buf: &[u8]) -> Self {
161        let earth_shape = EarthShapeDefinition::from_buf(buf);
162        let ni = read_as!(u32, buf, 16);
163        let nj = read_as!(u32, buf, 20);
164        let first_point_lat = read_as!(u32, buf, 24).as_grib_int();
165        let first_point_lon = read_as!(u32, buf, 28).as_grib_int();
166        let lad = read_as!(u32, buf, 33).as_grib_int();
167        let lov = read_as!(u32, buf, 37).as_grib_int();
168        let dx = read_as!(u32, buf, 41);
169        let dy = read_as!(u32, buf, 45);
170        let scanning_mode = read_as!(u8, buf, 50);
171        let latin1 = read_as!(u32, buf, 51).as_grib_int();
172        let latin2 = read_as!(u32, buf, 55).as_grib_int();
173        Self {
174            earth_shape,
175            ni,
176            nj,
177            first_point_lat,
178            first_point_lon,
179            lad,
180            lov,
181            dx,
182            dy,
183            scanning_mode: ScanningMode(scanning_mode),
184            latin1,
185            latin2,
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use std::io::{BufReader, Read};
193
194    use super::*;
195
196    #[test]
197    fn lambert_grid_definition_from_buf() -> Result<(), Box<dyn std::error::Error>> {
198        let mut buf = Vec::new();
199
200        let f = std::fs::File::open("testdata/ds.critfireo.bin.xz")?;
201        let f = BufReader::new(f);
202        let mut f = xz2::bufread::XzDecoder::new(f);
203        f.read_to_end(&mut buf)?;
204
205        let actual = LambertGridDefinition::from_buf(&buf[0x83..]);
206        let expected = LambertGridDefinition {
207            earth_shape: EarthShapeDefinition {
208                shape_of_the_earth: 1,
209                scale_factor_of_radius_of_spherical_earth: 0,
210                scaled_value_of_radius_of_spherical_earth: 6371200,
211                scale_factor_of_earth_major_axis: 0,
212                scaled_value_of_earth_major_axis: 0,
213                scale_factor_of_earth_minor_axis: 0,
214                scaled_value_of_earth_minor_axis: 0,
215            },
216            ni: 2145,
217            nj: 1377,
218            first_point_lat: 20190000,
219            first_point_lon: 238449996,
220            lad: 25000000,
221            lov: 265000000,
222            dx: 2539703,
223            dy: 2539703,
224            scanning_mode: ScanningMode(0b01010000),
225            latin1: 25000000,
226            latin2: 25000000,
227        };
228        assert_eq!(actual, expected);
229
230        Ok(())
231    }
232
233    #[cfg(feature = "gridpoints-proj")]
234    #[test]
235    fn lambert_grid_latlon_computation() -> Result<(), Box<dyn std::error::Error>> {
236        use crate::grid::helpers::test_helpers::assert_coord_almost_eq;
237        let grid_def = LambertGridDefinition {
238            earth_shape: EarthShapeDefinition {
239                shape_of_the_earth: 1,
240                scale_factor_of_radius_of_spherical_earth: 0,
241                scaled_value_of_radius_of_spherical_earth: 6371200,
242                scale_factor_of_earth_major_axis: 0,
243                scaled_value_of_earth_major_axis: 0,
244                scale_factor_of_earth_minor_axis: 0,
245                scaled_value_of_earth_minor_axis: 0,
246            },
247            ni: 2145,
248            nj: 1377,
249            first_point_lat: 20190000,
250            first_point_lon: 238449996,
251            lad: 25000000,
252            lov: 265000000,
253            dx: 2539703,
254            dy: 2539703,
255            scanning_mode: ScanningMode(0b01010000),
256            latin1: 25000000,
257            latin2: 25000000,
258        };
259        let latlons = grid_def.latlons()?.collect::<Vec<_>>();
260
261        // Following lat/lon values are taken from the calculation results using pygrib.
262        let delta = 1e-10;
263        assert_coord_almost_eq(latlons[0], (20.19, -121.550004), delta);
264        assert_coord_almost_eq(latlons[1], (20.19442682, -121.52621665), delta);
265        assert_coord_almost_eq(
266            latlons[latlons.len() - 2],
267            (50.10756403, -60.91298217),
268            delta,
269        );
270        assert_coord_almost_eq(
271            latlons[latlons.len() - 1],
272            (50.1024611, -60.88202274),
273            delta,
274        );
275
276        Ok(())
277    }
278}