1use crate::error::{Error, Result};
4use crate::util::grib_i32;
5
6#[derive(Debug, Clone, PartialEq)]
13#[non_exhaustive]
14pub enum GridDefinition {
15 LatLon(LatLonGrid),
17 LambertConformal(LambertConformalGrid),
19 PolarStereographic(PolarStereographicGrid),
21 Unsupported(u16),
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct LatLonGrid {
28 pub ni: u32,
29 pub nj: u32,
30 pub lat_first: i32,
31 pub lon_first: i32,
32 pub lat_last: i32,
33 pub lon_last: i32,
34 pub di: u32,
35 pub dj: u32,
36 pub scanning_mode: u8,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct LambertConformalGrid {
42 pub number_of_points: u32,
43 pub shape_of_earth: u8,
44 pub scale_factor_radius: u8,
45 pub scaled_value_radius: u32,
46 pub scale_factor_major_axis: u8,
47 pub scaled_value_major_axis: u32,
48 pub scale_factor_minor_axis: u8,
49 pub scaled_value_minor_axis: u32,
50 pub nx: u32,
51 pub ny: u32,
52 pub lat_first: i32,
53 pub lon_first: u32,
54 pub resolution_and_component_flags: u8,
55 pub lat_d: i32,
56 pub lon_v: u32,
57 pub dx: u32,
58 pub dy: u32,
59 pub projection_center_flag: u8,
60 pub scanning_mode: u8,
61 pub latin1: i32,
62 pub latin2: i32,
63 pub lat_southern_pole: i32,
64 pub lon_southern_pole: u32,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct PolarStereographicGrid {
70 pub number_of_points: u32,
71 pub shape_of_earth: u8,
72 pub scale_factor_radius: u8,
73 pub scaled_value_radius: u32,
74 pub scale_factor_major_axis: u8,
75 pub scaled_value_major_axis: u32,
76 pub scale_factor_minor_axis: u8,
77 pub scaled_value_minor_axis: u32,
78 pub nx: u32,
79 pub ny: u32,
80 pub lat_first: i32,
81 pub lon_first: u32,
82 pub resolution_and_component_flags: u8,
83 pub lat_d: i32,
84 pub lon_v: u32,
85 pub dx: u32,
86 pub dy: u32,
87 pub projection_center_flag: u8,
88 pub scanning_mode: u8,
89}
90
91impl GridDefinition {
92 pub fn template_number(&self) -> u16 {
97 match self {
98 Self::LatLon(_) => 0,
99 Self::PolarStereographic(_) => 20,
100 Self::LambertConformal(_) => 30,
101 Self::Unsupported(template) => *template,
102 }
103 }
104
105 pub fn as_lat_lon(&self) -> Option<&LatLonGrid> {
106 match self {
107 Self::LatLon(grid) => Some(grid),
108 _ => None,
109 }
110 }
111
112 pub fn as_polar_stereographic(&self) -> Option<&PolarStereographicGrid> {
113 match self {
114 Self::PolarStereographic(grid) => Some(grid),
115 _ => None,
116 }
117 }
118
119 pub fn as_lambert_conformal(&self) -> Option<&LambertConformalGrid> {
120 match self {
121 Self::LambertConformal(grid) => Some(grid),
122 _ => None,
123 }
124 }
125
126 pub fn unsupported_template(&self) -> Option<u16> {
127 match self {
128 Self::Unsupported(template) => Some(*template),
129 _ => None,
130 }
131 }
132
133 pub fn shape(&self) -> (usize, usize) {
134 match self {
135 Self::LatLon(g) => (g.ni as usize, g.nj as usize),
136 Self::PolarStereographic(g) => (g.nx as usize, g.ny as usize),
137 Self::LambertConformal(g) => (g.nx as usize, g.ny as usize),
138 Self::Unsupported(_) => (0, 0),
139 }
140 }
141
142 pub fn ndarray_shape(&self) -> Vec<usize> {
143 let (ni, nj) = self.shape();
144 match self {
145 Self::LatLon(_) | Self::PolarStereographic(_) | Self::LambertConformal(_)
146 if ni > 0 && nj > 0 =>
147 {
148 vec![nj, ni]
149 }
150 _ => Vec::new(),
151 }
152 }
153
154 pub fn num_points(&self) -> usize {
155 match self {
156 Self::LatLon(_) => {
157 let (ni, nj) = self.shape();
158 ni.saturating_mul(nj)
159 }
160 Self::PolarStereographic(g) => g.number_of_points as usize,
161 Self::LambertConformal(g) => g.number_of_points as usize,
162 Self::Unsupported(_) => 0,
163 }
164 }
165
166 pub fn validate_supported_scan_order(&self) -> Result<()> {
167 match self {
168 Self::LatLon(grid) => grid.validate_supported_scan_order(),
169 Self::PolarStereographic(grid) => grid.validate_supported_scan_order(),
170 Self::LambertConformal(grid) => grid.validate_supported_scan_order(),
171 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
172 }
173 }
174
175 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
176 match self {
177 Self::LatLon(grid) => grid.reorder_for_ndarray_in_place(values),
178 Self::PolarStereographic(grid) => grid.reorder_for_ndarray_in_place(values),
179 Self::LambertConformal(grid) => grid.reorder_for_ndarray_in_place(values),
180 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
181 }
182 }
183
184 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
185 if section_bytes.len() < 14 {
186 return Err(Error::InvalidSection {
187 section: 3,
188 reason: format!("expected at least 14 bytes, got {}", section_bytes.len()),
189 });
190 }
191 if section_bytes[4] != 3 {
192 return Err(Error::InvalidSection {
193 section: section_bytes[4],
194 reason: "not a grid definition section".into(),
195 });
196 }
197
198 let template = u16::from_be_bytes(section_bytes[12..14].try_into().unwrap());
199 match template {
200 0 => parse_latlon(section_bytes),
201 20 => parse_polar_stereographic(section_bytes),
202 30 => parse_lambert_conformal(section_bytes),
203 _ => Ok(Self::Unsupported(template)),
204 }
205 }
206}
207
208impl LatLonGrid {
209 pub fn longitudes(&self) -> Vec<f64> {
210 let step = self.di as f64 / 1_000_000.0;
211 let signed_step = if self.i_scans_positive() { step } else { -step };
212 let start = self.lon_first as f64 / 1_000_000.0;
213 (0..self.ni)
214 .map(|index| start + signed_step * index as f64)
215 .collect()
216 }
217
218 pub fn latitudes(&self) -> Vec<f64> {
219 let step = self.dj as f64 / 1_000_000.0;
220 let signed_step = if self.j_scans_positive() { step } else { -step };
221 let start = self.lat_first as f64 / 1_000_000.0;
222 (0..self.nj)
223 .map(|index| start + signed_step * index as f64)
224 .collect()
225 }
226
227 pub fn reorder_for_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
228 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
229 Ok(values)
230 }
231
232 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
233 self.reorder_grib_scan_to_ndarray_in_place(values)
234 }
235
236 pub fn reorder_grib_scan_to_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
237 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
238 Ok(values)
239 }
240
241 pub fn reorder_grib_scan_to_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
242 transform_supported_scan_order_in_place(
243 values,
244 self.ni as usize,
245 self.nj as usize,
246 self.scanning_mode,
247 )
248 }
249
250 pub fn reorder_ndarray_to_grib_scan<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
251 self.reorder_ndarray_to_grib_scan_in_place(&mut values)?;
252 Ok(values)
253 }
254
255 pub fn reorder_ndarray_to_grib_scan_in_place<T>(&self, values: &mut [T]) -> Result<()> {
256 transform_supported_scan_order_in_place(
257 values,
258 self.ni as usize,
259 self.nj as usize,
260 self.scanning_mode,
261 )
262 }
263
264 pub fn validate_supported_scan_order(&self) -> Result<()> {
265 validate_supported_scan_order(self.scanning_mode)
266 }
267
268 fn i_scans_positive(&self) -> bool {
269 i_scans_positive(self.scanning_mode)
270 }
271
272 fn j_scans_positive(&self) -> bool {
273 j_scans_positive(self.scanning_mode)
274 }
275}
276
277impl LambertConformalGrid {
278 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
279 transform_supported_scan_order_in_place(
280 values,
281 self.nx as usize,
282 self.ny as usize,
283 self.scanning_mode,
284 )
285 }
286
287 pub fn validate_supported_scan_order(&self) -> Result<()> {
288 validate_supported_scan_order(self.scanning_mode)
289 }
290}
291
292impl PolarStereographicGrid {
293 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
294 transform_supported_scan_order_in_place(
295 values,
296 self.nx as usize,
297 self.ny as usize,
298 self.scanning_mode,
299 )
300 }
301
302 pub fn validate_supported_scan_order(&self) -> Result<()> {
303 validate_supported_scan_order(self.scanning_mode)
304 }
305}
306
307fn transform_supported_scan_order_in_place<T>(
308 values: &mut [T],
309 ni: usize,
310 nj: usize,
311 scanning_mode: u8,
312) -> Result<()> {
313 validate_supported_scan_order(scanning_mode)?;
314 if values.len() != ni * nj {
315 return Err(Error::DataLengthMismatch {
316 expected: ni * nj,
317 actual: values.len(),
318 });
319 }
320
321 if adjacent_rows_alternate_direction(scanning_mode) {
322 reverse_alternating_rows(values, ni, nj, i_scans_positive(scanning_mode));
323 }
324
325 Ok(())
326}
327
328fn validate_supported_scan_order(scanning_mode: u8) -> Result<()> {
329 if i_points_are_consecutive(scanning_mode) {
330 Ok(())
331 } else {
332 Err(Error::UnsupportedScanningMode(scanning_mode))
333 }
334}
335
336fn i_scans_positive(scanning_mode: u8) -> bool {
337 scanning_mode & 0b1000_0000 == 0
338}
339
340fn j_scans_positive(scanning_mode: u8) -> bool {
341 scanning_mode & 0b0100_0000 != 0
342}
343
344fn i_points_are_consecutive(scanning_mode: u8) -> bool {
345 scanning_mode & 0b0010_0000 == 0
346}
347
348fn adjacent_rows_alternate_direction(scanning_mode: u8) -> bool {
349 scanning_mode & 0b0001_0000 != 0
350}
351
352fn reverse_alternating_rows<T>(values: &mut [T], ni: usize, nj: usize, i_scans_positive: bool) {
353 for row in 0..nj {
354 let reverse = if i_scans_positive {
355 row % 2 == 1
356 } else {
357 row % 2 == 0
358 };
359 if reverse {
360 values[row * ni..(row + 1) * ni].reverse();
361 }
362 }
363}
364
365fn parse_latlon(data: &[u8]) -> Result<GridDefinition> {
366 if data.len() < 72 {
367 return Err(Error::InvalidSection {
368 section: 3,
369 reason: format!("template 3.0 requires 72 bytes, got {}", data.len()),
370 });
371 }
372
373 let ni = u32::from_be_bytes(data[30..34].try_into().unwrap());
374 let nj = u32::from_be_bytes(data[34..38].try_into().unwrap());
375 let lat_first = grib_i32(&data[46..50]).unwrap();
376 let lon_first = grib_i32(&data[50..54]).unwrap();
377 let lat_last = grib_i32(&data[55..59]).unwrap();
378 let lon_last = grib_i32(&data[59..63]).unwrap();
379 let di = u32::from_be_bytes(data[63..67].try_into().unwrap());
380 let dj = u32::from_be_bytes(data[67..71].try_into().unwrap());
381 let scanning_mode = data[71];
382
383 Ok(GridDefinition::LatLon(LatLonGrid {
384 ni,
385 nj,
386 lat_first,
387 lon_first,
388 lat_last,
389 lon_last,
390 di,
391 dj,
392 scanning_mode,
393 }))
394}
395
396fn parse_polar_stereographic(data: &[u8]) -> Result<GridDefinition> {
397 if data.len() < 65 {
398 return Err(Error::InvalidSection {
399 section: 3,
400 reason: format!("template 3.20 requires 65 bytes, got {}", data.len()),
401 });
402 }
403
404 Ok(GridDefinition::PolarStereographic(PolarStereographicGrid {
405 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
406 shape_of_earth: data[14],
407 scale_factor_radius: data[15],
408 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
409 scale_factor_major_axis: data[20],
410 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
411 scale_factor_minor_axis: data[25],
412 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
413 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
414 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
415 lat_first: grib_i32(&data[38..42]).unwrap(),
416 lon_first: u32::from_be_bytes(data[42..46].try_into().unwrap()),
417 resolution_and_component_flags: data[46],
418 lat_d: grib_i32(&data[47..51]).unwrap(),
419 lon_v: u32::from_be_bytes(data[51..55].try_into().unwrap()),
420 dx: u32::from_be_bytes(data[55..59].try_into().unwrap()),
421 dy: u32::from_be_bytes(data[59..63].try_into().unwrap()),
422 projection_center_flag: data[63],
423 scanning_mode: data[64],
424 }))
425}
426
427fn parse_lambert_conformal(data: &[u8]) -> Result<GridDefinition> {
428 if data.len() < 81 {
429 return Err(Error::InvalidSection {
430 section: 3,
431 reason: format!("template 3.30 requires 81 bytes, got {}", data.len()),
432 });
433 }
434
435 Ok(GridDefinition::LambertConformal(LambertConformalGrid {
436 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
437 shape_of_earth: data[14],
438 scale_factor_radius: data[15],
439 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
440 scale_factor_major_axis: data[20],
441 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
442 scale_factor_minor_axis: data[25],
443 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
444 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
445 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
446 lat_first: grib_i32(&data[38..42]).unwrap(),
447 lon_first: u32::from_be_bytes(data[42..46].try_into().unwrap()),
448 resolution_and_component_flags: data[46],
449 lat_d: grib_i32(&data[47..51]).unwrap(),
450 lon_v: u32::from_be_bytes(data[51..55].try_into().unwrap()),
451 dx: u32::from_be_bytes(data[55..59].try_into().unwrap()),
452 dy: u32::from_be_bytes(data[59..63].try_into().unwrap()),
453 projection_center_flag: data[63],
454 scanning_mode: data[64],
455 latin1: grib_i32(&data[65..69]).unwrap(),
456 latin2: grib_i32(&data[69..73]).unwrap(),
457 lat_southern_pole: grib_i32(&data[73..77]).unwrap(),
458 lon_southern_pole: u32::from_be_bytes(data[77..81].try_into().unwrap()),
459 }))
460}
461
462#[cfg(test)]
463mod tests {
464 use super::{GridDefinition, LambertConformalGrid, LatLonGrid, PolarStereographicGrid};
465 use crate::binary::encode_wmo_i32;
466
467 #[test]
468 fn reports_latlon_shape() {
469 let grid = GridDefinition::LatLon(LatLonGrid {
470 ni: 3,
471 nj: 2,
472 lat_first: 50_000_000,
473 lon_first: -120_000_000,
474 lat_last: 49_000_000,
475 lon_last: -118_000_000,
476 di: 1_000_000,
477 dj: 1_000_000,
478 scanning_mode: 0,
479 });
480
481 assert_eq!(grid.shape(), (3, 2));
482 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
483 assert_eq!(grid.template_number(), 0);
484 assert!(grid.as_lat_lon().is_some());
485 assert!(grid.as_polar_stereographic().is_none());
486 assert!(grid.as_lambert_conformal().is_none());
487 assert_eq!(grid.unsupported_template(), None);
488 match grid {
489 GridDefinition::LatLon(ref latlon) => {
490 assert_eq!(latlon.longitudes(), vec![-120.0, -119.0, -118.0]);
491 assert_eq!(latlon.latitudes(), vec![50.0, 49.0]);
492 }
493 other => panic!("expected lat/lon grid, got {other:?}"),
494 }
495 }
496
497 #[test]
498 fn parses_polar_stereographic_template() {
499 let section = build_polar_stereographic_section(0);
500 let grid = GridDefinition::parse(§ion).unwrap();
501
502 assert_eq!(grid.shape(), (3, 2));
503 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
504 assert_eq!(grid.num_points(), 6);
505 assert_eq!(grid.template_number(), 20);
506 assert!(grid.as_lat_lon().is_none());
507 assert!(grid.as_polar_stereographic().is_some());
508 assert!(grid.as_lambert_conformal().is_none());
509 assert_eq!(grid.unsupported_template(), None);
510 match grid {
511 GridDefinition::PolarStereographic(polar) => {
512 assert_eq!(polar.number_of_points, 6);
513 assert_eq!(polar.shape_of_earth, 6);
514 assert_eq!(polar.nx, 3);
515 assert_eq!(polar.ny, 2);
516 assert_eq!(polar.lat_first, 41_612_949);
517 assert_eq!(polar.lon_first, 185_117_126);
518 assert_eq!(polar.resolution_and_component_flags, 0x08);
519 assert_eq!(polar.lat_d, 60_000_000);
520 assert_eq!(polar.lon_v, 225_000_000);
521 assert_eq!(polar.dx, 3_000_000);
522 assert_eq!(polar.dy, 3_000_000);
523 assert_eq!(polar.projection_center_flag, 0);
524 assert_eq!(polar.scanning_mode, 0);
525 }
526 other => panic!("expected polar stereographic grid, got {other:?}"),
527 }
528 }
529
530 #[test]
531 fn parses_lambert_conformal_template() {
532 let section = build_lambert_section();
533 let grid = GridDefinition::parse(§ion).unwrap();
534
535 assert_eq!(grid.shape(), (3, 2));
536 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
537 assert_eq!(grid.num_points(), 6);
538 assert_eq!(grid.template_number(), 30);
539 assert!(grid.as_lat_lon().is_none());
540 assert!(grid.as_lambert_conformal().is_some());
541 assert_eq!(grid.unsupported_template(), None);
542 match grid {
543 GridDefinition::LambertConformal(lambert) => {
544 assert_eq!(lambert.number_of_points, 6);
545 assert_eq!(lambert.shape_of_earth, 1);
546 assert_eq!(lambert.scaled_value_radius, 6_371_200);
547 assert_eq!(lambert.nx, 3);
548 assert_eq!(lambert.ny, 2);
549 assert_eq!(lambert.lat_first, 12_190_000);
550 assert_eq!(lambert.lon_first, 226_541_000);
551 assert_eq!(lambert.resolution_and_component_flags, 0x08);
552 assert_eq!(lambert.lat_d, 25_000_000);
553 assert_eq!(lambert.lon_v, 265_000_000);
554 assert_eq!(lambert.dx, 2_539_703);
555 assert_eq!(lambert.dy, 2_539_703);
556 assert_eq!(lambert.projection_center_flag, 0);
557 assert_eq!(lambert.scanning_mode, 0);
558 assert_eq!(lambert.latin1, 25_000_000);
559 assert_eq!(lambert.latin2, 25_000_000);
560 assert_eq!(lambert.lat_southern_pole, -90_000_000);
561 assert_eq!(lambert.lon_southern_pole, 0);
562 }
563 other => panic!("expected Lambert conformal grid, got {other:?}"),
564 }
565 }
566
567 #[test]
568 fn reports_unsupported_template_helpers() {
569 let grid = GridDefinition::Unsupported(3_276);
570
571 assert_eq!(grid.template_number(), 3_276);
572 assert!(grid.as_lat_lon().is_none());
573 assert!(grid.as_polar_stereographic().is_none());
574 assert!(grid.as_lambert_conformal().is_none());
575 assert_eq!(grid.unsupported_template(), Some(3_276));
576 }
577
578 #[test]
579 fn normalizes_alternating_row_scan() {
580 let grid = LatLonGrid {
581 ni: 3,
582 nj: 2,
583 lat_first: 0,
584 lon_first: 0,
585 lat_last: 0,
586 lon_last: 0,
587 di: 1,
588 dj: 1,
589 scanning_mode: 0b0001_0000,
590 };
591
592 let ordered = grid
593 .reorder_for_ndarray(vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0])
594 .unwrap();
595 assert_eq!(ordered, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
596 }
597
598 #[test]
599 fn converts_ndarray_order_to_alternating_scan_order() {
600 let grid = LatLonGrid {
601 ni: 3,
602 nj: 2,
603 lat_first: 0,
604 lon_first: 0,
605 lat_last: 0,
606 lon_last: 0,
607 di: 1,
608 dj: 1,
609 scanning_mode: 0b0001_0000,
610 };
611
612 let scan_order = grid
613 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
614 .unwrap();
615 assert_eq!(scan_order, vec![1, 2, 3, 6, 5, 4]);
616 }
617
618 #[test]
619 fn normalizes_polar_stereographic_alternating_row_scan() {
620 let grid = PolarStereographicGrid {
621 number_of_points: 6,
622 shape_of_earth: 6,
623 scale_factor_radius: 0,
624 scaled_value_radius: 0,
625 scale_factor_major_axis: 0,
626 scaled_value_major_axis: 0,
627 scale_factor_minor_axis: 0,
628 scaled_value_minor_axis: 0,
629 nx: 3,
630 ny: 2,
631 lat_first: 0,
632 lon_first: 0,
633 resolution_and_component_flags: 0,
634 lat_d: 0,
635 lon_v: 0,
636 dx: 1,
637 dy: 1,
638 projection_center_flag: 0,
639 scanning_mode: 0b0001_0000,
640 };
641
642 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
643 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
644 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
645 }
646
647 #[test]
648 fn normalizes_lambert_alternating_row_scan() {
649 let grid = LambertConformalGrid {
650 number_of_points: 6,
651 shape_of_earth: 1,
652 scale_factor_radius: 0,
653 scaled_value_radius: 6_371_200,
654 scale_factor_major_axis: 0,
655 scaled_value_major_axis: 0,
656 scale_factor_minor_axis: 0,
657 scaled_value_minor_axis: 0,
658 nx: 3,
659 ny: 2,
660 lat_first: 0,
661 lon_first: 0,
662 resolution_and_component_flags: 0,
663 lat_d: 0,
664 lon_v: 0,
665 dx: 1,
666 dy: 1,
667 projection_center_flag: 0,
668 scanning_mode: 0b0001_0000,
669 latin1: 0,
670 latin2: 0,
671 lat_southern_pole: 0,
672 lon_southern_pole: 0,
673 };
674
675 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
676 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
677 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
678 }
679
680 #[test]
681 fn preserves_non_alternating_scan_modes_in_current_reader_order() {
682 for scanning_mode in [0b0000_0000, 0b1000_0000, 0b0100_0000, 0b1100_0000] {
683 let grid = LatLonGrid {
684 ni: 3,
685 nj: 2,
686 lat_first: 0,
687 lon_first: 0,
688 lat_last: 0,
689 lon_last: 0,
690 di: 1,
691 dj: 1,
692 scanning_mode,
693 };
694
695 let values = vec![1, 2, 3, 4, 5, 6];
696 assert_eq!(
697 grid.reorder_grib_scan_to_ndarray(values.clone()).unwrap(),
698 values
699 );
700 assert_eq!(
701 grid.reorder_ndarray_to_grib_scan(values.clone()).unwrap(),
702 values
703 );
704 }
705 }
706
707 #[test]
708 fn rejects_j_consecutive_scan_order() {
709 let grid = LatLonGrid {
710 ni: 3,
711 nj: 2,
712 lat_first: 0,
713 lon_first: 0,
714 lat_last: 0,
715 lon_last: 0,
716 di: 1,
717 dj: 1,
718 scanning_mode: 0b0010_0000,
719 };
720
721 let err = grid
722 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
723 .unwrap_err();
724 assert!(matches!(
725 err,
726 crate::Error::UnsupportedScanningMode(0b0010_0000)
727 ));
728 }
729
730 fn build_polar_stereographic_section(scanning_mode: u8) -> Vec<u8> {
731 let mut section = vec![0u8; 65];
732 section[..4].copy_from_slice(&65u32.to_be_bytes());
733 section[4] = 3;
734 section[6..10].copy_from_slice(&6u32.to_be_bytes());
735 section[12..14].copy_from_slice(&20u16.to_be_bytes());
736 section[14] = 6;
737 section[30..34].copy_from_slice(&3u32.to_be_bytes());
738 section[34..38].copy_from_slice(&2u32.to_be_bytes());
739 section[38..42].copy_from_slice(&encode_wmo_i32(41_612_949).unwrap());
740 section[42..46].copy_from_slice(&185_117_126u32.to_be_bytes());
741 section[46] = 0x08;
742 section[47..51].copy_from_slice(&encode_wmo_i32(60_000_000).unwrap());
743 section[51..55].copy_from_slice(&225_000_000u32.to_be_bytes());
744 section[55..59].copy_from_slice(&3_000_000u32.to_be_bytes());
745 section[59..63].copy_from_slice(&3_000_000u32.to_be_bytes());
746 section[64] = scanning_mode;
747 section
748 }
749
750 fn build_lambert_section() -> Vec<u8> {
751 let mut section = vec![0u8; 81];
752 section[..4].copy_from_slice(&81u32.to_be_bytes());
753 section[4] = 3;
754 section[6..10].copy_from_slice(&6u32.to_be_bytes());
755 section[12..14].copy_from_slice(&30u16.to_be_bytes());
756 section[14] = 1;
757 section[16..20].copy_from_slice(&6_371_200u32.to_be_bytes());
758 section[30..34].copy_from_slice(&3u32.to_be_bytes());
759 section[34..38].copy_from_slice(&2u32.to_be_bytes());
760 section[38..42].copy_from_slice(&encode_wmo_i32(12_190_000).unwrap());
761 section[42..46].copy_from_slice(&226_541_000u32.to_be_bytes());
762 section[46] = 0x08;
763 section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
764 section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
765 section[55..59].copy_from_slice(&2_539_703u32.to_be_bytes());
766 section[59..63].copy_from_slice(&2_539_703u32.to_be_bytes());
767 section[65..69].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
768 section[69..73].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
769 section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
770 section
771 }
772}