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 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 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}