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 pub fn grid_shape(&self) -> (usize, usize) {
56 (self.ni as usize, self.nj as usize)
57 }
58
59 pub fn short_name(&self) -> &'static str {
61 "lambert"
62 }
63
64 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 #[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 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}