1use super::{
2 helpers::{evenly_spaced_longitudes, RegularGridIterator},
3 GridPointIndexIterator, ScanningMode,
4};
5use crate::{
6 error::GribError,
7 helpers::{read_as, GribInt},
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub struct GaussianGridDefinition {
12 pub ni: u32,
13 pub nj: u32,
14 pub first_point_lat: i32,
15 pub first_point_lon: i32,
16 pub last_point_lat: i32,
17 pub last_point_lon: i32,
18 pub i_direction_inc: u32,
19 pub n: u32,
20 pub scanning_mode: ScanningMode,
21}
22
23const MAX_ITER: usize = 10;
24
25impl GaussianGridDefinition {
26 pub fn grid_shape(&self) -> (usize, usize) {
29 (self.ni as usize, self.nj as usize)
30 }
31
32 pub fn short_name(&self) -> &'static str {
34 "regular_gg"
35 }
36
37 pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
43 if self.scanning_mode.has_unsupported_flags() {
44 let ScanningMode(mode) = self.scanning_mode;
45 return Err(GribError::NotSupported(format!("scanning mode {mode}")));
46 }
47
48 let iter =
49 GridPointIndexIterator::new(self.ni as usize, self.nj as usize, self.scanning_mode);
50 Ok(iter)
51 }
52
53 pub fn latlons(&self) -> Result<RegularGridIterator, GribError> {
60 if !self.is_consistent_for_j() {
61 return Err(GribError::InvalidValueError(
62 "Latitudes for first/last grid points are not consistent with scanning mode"
63 .to_owned(),
64 ));
65 }
66
67 let ij = self.ij()?;
68 let mut lat = compute_gaussian_latitudes_in_degrees(self.nj as usize)
69 .map_err(|e| GribError::Unknown(e.to_owned()))?;
70 if self.scanning_mode.scans_positively_for_j() {
71 lat.reverse()
72 };
73 let lat = lat.into_iter().map(|v| v as f32).collect();
74 let lon = evenly_spaced_longitudes(
75 self.first_point_lon,
76 self.last_point_lon,
77 (self.ni - 1) as usize,
78 self.scanning_mode,
79 );
80
81 let iter = RegularGridIterator::new(lat, lon, ij);
82 Ok(iter)
83 }
84
85 pub(crate) fn is_consistent_for_j(&self) -> bool {
86 let lat_diff = self.last_point_lat - self.first_point_lat;
87 !((lat_diff > 0) ^ self.scanning_mode.scans_positively_for_j())
88 }
89
90 pub(crate) fn from_buf(buf: &[u8]) -> Self {
91 let ni = read_as!(u32, buf, 0);
92 let nj = read_as!(u32, buf, 4);
93 let first_point_lat = read_as!(u32, buf, 16).as_grib_int();
94 let first_point_lon = read_as!(u32, buf, 20).as_grib_int();
95 let last_point_lat = read_as!(u32, buf, 25).as_grib_int();
96 let last_point_lon = read_as!(u32, buf, 29).as_grib_int();
97 let i_direction_inc = read_as!(u32, buf, 33);
98 let n = read_as!(u32, buf, 37);
99 let scanning_mode = read_as!(u8, buf, 41);
100 Self {
101 ni,
102 nj,
103 first_point_lat,
104 first_point_lon,
105 last_point_lat,
106 last_point_lon,
107 i_direction_inc,
108 n,
109 scanning_mode: ScanningMode(scanning_mode),
110 }
111 }
112}
113
114fn compute_gaussian_latitudes_in_degrees(div: usize) -> Result<Vec<f64>, &'static str> {
115 let lat: Option<Vec<_>> = compute_gaussian_latitudes(div)
116 .map(|x| x.map(|i| i.to_degrees()))
117 .collect();
118 lat.ok_or("finding root for Legendre polynomial failed")
119}
120
121pub fn compute_gaussian_latitudes(div: usize) -> impl Iterator<Item = Option<f64>> {
144 legendre_roots_iterator(div).map(|x| x.map(|i| i.asin()))
145}
146
147fn legendre_roots_iterator(n: usize) -> impl Iterator<Item = Option<f64>> {
158 let coeff = 1.0_f64 - 1.0 / (8 * n * n) as f64 + 1.0 / (8 * n * n * n) as f64;
159 (0..n).map(move |i| {
160 let guess = coeff * ((4 * i + 3) as f64 * std::f64::consts::PI / (4 * n + 2) as f64).cos();
161 find_root(guess, |x| {
162 let (p_prev, p) = legendre_polynomial(n, x);
163 let fpx = legendre_polynomial_derivative(n, x, p_prev, p);
164 p / fpx
165 })
166 })
167}
168
169fn legendre_polynomial(n: usize, x: f64) -> (f64, f64) {
171 let mut p0 = 1.0;
172 let mut p1 = x;
173 for k in 2..=n {
174 let pk = ((2 * k - 1) as f64 * x * p1 - (k - 1) as f64 * p0) / k as f64;
175 p0 = p1;
176 p1 = pk;
177 }
178 (p0, p1)
179}
180
181fn legendre_polynomial_derivative(n: usize, x: f64, p_prev: f64, p: f64) -> f64 {
182 (n as f64 * (p_prev - x * p)) / (1.0 - x * x)
183}
184
185fn find_root<F>(initial_guess: f64, f: F) -> Option<f64>
187where
188 F: Fn(f64) -> f64,
189{
190 let mut count = MAX_ITER;
191 let mut x = initial_guess;
192 loop {
193 let dx = f(x);
194 x -= dx;
195 if dx.abs() < f64::EPSILON {
196 break;
197 }
198
199 if count > 0 {
200 count -= 1;
201 } else {
202 return None;
203 }
204 }
205 Some(x)
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::grid::helpers::test_helpers::assert_almost_eq;
212
213 #[test]
214 fn latlon_computation_for_real_world_gaussian_grid_compared_with_results_from_eccodes(
215 ) -> Result<(), Box<dyn std::error::Error>> {
216 use std::io::Read;
217
218 let mut buf = Vec::new();
219
220 let f = std::fs::File::open("testdata/gdas.t00z.sfluxgrbf000.grib2.0.xz")?;
221 let f = std::io::BufReader::new(f);
222 let mut f = xz2::bufread::XzDecoder::new(f);
223 f.read_to_end(&mut buf)?;
224
225 let f = std::io::Cursor::new(buf);
226 let grib2 = crate::from_reader(f)?;
227
228 let ((_, _), first_submessage) = grib2
229 .submessages()
230 .next()
231 .ok_or_else(|| Box::<dyn std::error::Error>::from("first submessage not found"))?;
232 let grid_shape = first_submessage.grid_shape()?;
233 assert_eq!(grid_shape, (3072, 1536));
234
235 let first_160_lats_expected = "
243 89.910325 89.794157 89.677304 89.560296 89.443229 89.326134 89.209022 89.091901
244 88.974774 88.857642 88.740506 88.623369 88.506229 88.389088 88.271946 88.154803
245 88.037660 87.920515 87.803370 87.686225 87.569079 87.451933 87.334787 87.217640
246 87.100493 86.983346 86.866199 86.749052 86.631904 86.514757 86.397609 86.280461
247 86.163313 86.046165 85.929017 85.811869 85.694721 85.577572 85.460424 85.343275
248 85.226127 85.108979 84.991830 84.874681 84.757533 84.640384 84.523236 84.406087
249 84.288938 84.171789 84.054641 83.937492 83.820343 83.703194 83.586045 83.468896
250 83.351747 83.234599 83.117450 83.000301 82.883152 82.766003 82.648854 82.531705
251 82.414556 82.297407 82.180258 82.063109 81.945960 81.828811 81.711662 81.594512
252 81.477363 81.360214 81.243065 81.125916 81.008767 80.891618 80.774469 80.657320
253 80.540171 80.423021 80.305872 80.188723 80.071574 79.954425 79.837276 79.720126
254 79.602977 79.485828 79.368679 79.251530 79.134381 79.017231 78.900082 78.782933
255 78.665784 78.548635 78.431485 78.314336 78.197187 78.080038 77.962888 77.845739
256 77.728590 77.611441 77.494292 77.377142 77.259993 77.142844 77.025695 76.908545
257 76.791396 76.674247 76.557098 76.439948 76.322799 76.205650 76.088501 75.971351
258 75.854202 75.737053 75.619904 75.502754 75.385605 75.268456 75.151306 75.034157
259 74.917008 74.799859 74.682709 74.565560 74.448411 74.331262 74.214112 74.096963
260 73.979814 73.862664 73.745515 73.628366 73.511217 73.394067 73.276918 73.159769
261 73.042619 72.925470 72.808321 72.691172 72.574022 72.456873 72.339724 72.222574
262 72.105425 71.988276 71.871126 71.753977 71.636828 71.519679 71.402529 71.285380
263 ";
264 let first_160_lats_expected = first_160_lats_expected
265 .split_whitespace()
266 .filter_map(|s| s.parse::<f32>().ok());
267
268 let delta = 1.0e-6;
269 let first_160_lats = first_submessage
270 .latlons()?
271 .map(|(lat, _lon)| lat)
272 .step_by(3072)
273 .take(160);
274 for (actual, expected) in first_160_lats.zip(first_160_lats_expected) {
275 assert_almost_eq!(actual, expected, delta);
276 }
277
278 let first_160_lons_expected = "
286 0.000000 0.117188 0.234375 0.351563 0.468750 0.585938 0.703125 0.820313
287 0.937500 1.054688 1.171875 1.289063 1.406250 1.523438 1.640625 1.757813
288 1.875000 1.992188 2.109375 2.226563 2.343750 2.460938 2.578125 2.695313
289 2.812500 2.929688 3.046875 3.164063 3.281250 3.398438 3.515625 3.632813
290 3.750000 3.867188 3.984375 4.101563 4.218750 4.335938 4.453125 4.570313
291 4.687500 4.804688 4.921875 5.039063 5.156250 5.273438 5.390625 5.507813
292 5.625000 5.742188 5.859375 5.976563 6.093750 6.210938 6.328125 6.445313
293 6.562500 6.679688 6.796875 6.914063 7.031250 7.148438 7.265625 7.382813
294 7.500000 7.617188 7.734375 7.851563 7.968750 8.085938 8.203125 8.320313
295 8.437500 8.554688 8.671875 8.789063 8.906250 9.023438 9.140625 9.257813
296 9.375000 9.492188 9.609375 9.726563 9.843750 9.960938 10.078125 10.195313
297 10.312500 10.429688 10.546875 10.664063 10.781250 10.898438 11.015625 11.132813
298 11.250000 11.367188 11.484375 11.601563 11.718750 11.835938 11.953125 12.070313
299 12.187500 12.304688 12.421875 12.539063 12.656250 12.773438 12.890625 13.007813
300 13.125000 13.242188 13.359375 13.476563 13.593750 13.710938 13.828125 13.945313
301 14.062500 14.179688 14.296875 14.414063 14.531250 14.648438 14.765625 14.882813
302 15.000000 15.117188 15.234375 15.351563 15.468750 15.585938 15.703125 15.820313
303 15.937500 16.054688 16.171875 16.289063 16.406250 16.523438 16.640625 16.757813
304 16.875000 16.992188 17.109375 17.226563 17.343750 17.460938 17.578125 17.695313
305 17.812500 17.929688 18.046875 18.164063 18.281250 18.398438 18.515625 18.632813
306 ";
307 let first_160_lons_expected = first_160_lons_expected
308 .split_whitespace()
309 .filter_map(|s| s.parse::<f32>().ok());
310
311 let delta = 2.0e-6;
312 let first_160_lons = first_submessage.latlons()?.map(|(_lat, lon)| lon).take(160);
313 for (actual, expected) in first_160_lons.zip(first_160_lons_expected) {
314 assert_almost_eq!(actual, expected, delta);
315 }
316
317 Ok(())
318 }
319
320 macro_rules! test_legendre_roots_iterator_with_analytical_solutions {
321 ($((
322 $name:ident,
323 $n:expr,
324 $expected:expr,
325 ),)*) => ($(
326 #[test]
327 fn $name() {
328 let actual = legendre_roots_iterator($n);
329 let expected = $expected.into_iter();
330 for (actual_val, expected_val) in actual.zip(expected) {
331 assert!(actual_val.is_some());
332 let actual_val = actual_val.unwrap();
333 assert_almost_eq!(actual_val, expected_val, f64::EPSILON);
334 }
335 }
336 )*);
337 }
338
339 test_legendre_roots_iterator_with_analytical_solutions! {
340 (
341 legendre_roots_iterator_for_n_being_2_compared_with_analytical_solutions,
342 2,
343 vec![1.0 / 3.0_f64.sqrt(), -1.0 / 3.0_f64.sqrt()],
344 ),
345 (
346 legendre_roots_iterator_for_n_being_5_compared_with_analytical_solutions,
347 5,
348 vec![
349 (5.0_f64 + 2.0 * (10.0_f64 / 7.0).sqrt()).sqrt() / 3.0,
350 (5.0_f64 - 2.0 * (10.0_f64 / 7.0).sqrt()).sqrt() / 3.0,
351 0.0,
352 - (5.0_f64 - 2.0 * (10.0_f64 / 7.0).sqrt()).sqrt() / 3.0,
353 - (5.0_f64 + 2.0 * (10.0_f64 / 7.0).sqrt()).sqrt() / 3.0,
354 ],
355 ),
356 }
357
358 #[test]
360 fn gaussian_latitudes_computation_compared_with_numerical_solutions() {
361 let n = 160;
362 let result = compute_gaussian_latitudes_in_degrees(n);
363 assert!(result.is_ok());
364
365 let actual = result.unwrap().into_iter().take(n / 2);
366 let expected = "
367 + 89.1416, 88.0294, 86.9108, 85.7906, 84.6699, 83.5489,
368 + 82.4278, 81.3066, 80.1853, 79.0640, 77.9426, 76.8212,
369 + 75.6998, 74.5784, 73.4570, 72.3356, 71.2141, 70.0927,
370 + 68.9712, 67.8498, 66.7283, 65.6069, 64.4854, 63.3639,
371 + 62.2425, 61.1210, 59.9995, 58.8780, 57.7566, 56.6351,
372 + 55.5136, 54.3921, 53.2707, 52.1492, 51.0277, 49.9062,
373 + 48.7847, 47.6632, 46.5418, 45.4203, 44.2988, 43.1773,
374 + 42.0558, 40.9343, 39.8129, 38.6914, 37.5699, 36.4484,
375 + 35.3269, 34.2054, 33.0839, 31.9624, 30.8410, 29.7195,
376 + 28.5980, 27.4765, 26.3550, 25.2335, 24.1120, 22.9905,
377 + 21.8690, 20.7476, 19.6261, 18.5046, 17.3831, 16.2616,
378 + 15.1401, 14.0186, 12.8971, 11.7756, 10.6542, 9.5327,
379 + 8.4112, 7.2897, 6.1682, 5.0467, 3.9252, 2.8037,
380 + 1.6822, 0.5607 /
381 ";
382 let expected = expected
383 .split(&['+', ' ', ',', '\n', '/'])
384 .filter_map(|s| s.parse::<f64>().ok());
385
386 let delta = 1.0e-4;
387 for (actual_val, expected_val) in actual.zip(expected) {
388 assert_almost_eq!(actual_val, expected_val, delta);
389 }
390 }
391
392 #[test]
393 fn finding_root() {
394 let actual = find_root(1.0, |x| {
395 let fx = x * x - 2.0;
396 let fpx = x * 2.0;
397 fx / fpx
398 });
399 assert!(actual.is_some());
400 let expected = 1.41421356;
401 assert_almost_eq!(actual.unwrap(), expected, 1.0e-8)
402 }
403
404 #[test]
405 fn failure_to_find_root() {
406 let actual = find_root(1000.0, |x| {
407 let fx = x * x - 2.0;
408 let fpx = x * 2.0;
409 fx / fpx
410 });
411 assert!(actual.is_none());
412 }
413}