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 Mercator(MercatorGrid),
19 AlbersEqualArea(AlbersEqualAreaGrid),
21 LambertConformal(LambertConformalGrid),
23 PolarStereographic(PolarStereographicGrid),
25 Unsupported(u16),
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct LatLonGrid {
32 pub ni: u32,
33 pub nj: u32,
34 pub lat_first: i32,
35 pub lon_first: i32,
36 pub lat_last: i32,
37 pub lon_last: i32,
38 pub di: u32,
39 pub dj: u32,
40 pub scanning_mode: u8,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct MercatorGrid {
46 pub number_of_points: u32,
47 pub shape_of_earth: u8,
48 pub scale_factor_radius: u8,
49 pub scaled_value_radius: u32,
50 pub scale_factor_major_axis: u8,
51 pub scaled_value_major_axis: u32,
52 pub scale_factor_minor_axis: u8,
53 pub scaled_value_minor_axis: u32,
54 pub ni: u32,
55 pub nj: u32,
56 pub lat_first: i32,
57 pub lon_first: i32,
58 pub resolution_and_component_flags: u8,
59 pub lat_d: i32,
60 pub lat_last: i32,
61 pub lon_last: i32,
62 pub scanning_mode: u8,
63 pub orientation_of_grid: u32,
64 pub di: u32,
65 pub dj: u32,
66}
67
68#[derive(Debug, Clone, PartialEq)]
70pub struct LambertConformalGrid {
71 pub number_of_points: u32,
72 pub shape_of_earth: u8,
73 pub scale_factor_radius: u8,
74 pub scaled_value_radius: u32,
75 pub scale_factor_major_axis: u8,
76 pub scaled_value_major_axis: u32,
77 pub scale_factor_minor_axis: u8,
78 pub scaled_value_minor_axis: u32,
79 pub nx: u32,
80 pub ny: u32,
81 pub lat_first: i32,
82 pub lon_first: u32,
83 pub resolution_and_component_flags: u8,
84 pub lat_d: i32,
85 pub lon_v: u32,
86 pub dx: u32,
87 pub dy: u32,
88 pub projection_center_flag: u8,
89 pub scanning_mode: u8,
90 pub latin1: i32,
91 pub latin2: i32,
92 pub lat_southern_pole: i32,
93 pub lon_southern_pole: u32,
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub struct AlbersEqualAreaGrid {
99 pub number_of_points: u32,
100 pub shape_of_earth: u8,
101 pub scale_factor_radius: u8,
102 pub scaled_value_radius: u32,
103 pub scale_factor_major_axis: u8,
104 pub scaled_value_major_axis: u32,
105 pub scale_factor_minor_axis: u8,
106 pub scaled_value_minor_axis: u32,
107 pub nx: u32,
108 pub ny: u32,
109 pub lat_first: i32,
110 pub lon_first: u32,
111 pub resolution_and_component_flags: u8,
112 pub lat_d: i32,
113 pub lon_v: u32,
114 pub dx: u32,
115 pub dy: u32,
116 pub projection_center_flag: u8,
117 pub scanning_mode: u8,
118 pub latin1: i32,
119 pub latin2: i32,
120 pub lat_southern_pole: i32,
121 pub lon_southern_pole: u32,
122}
123
124#[derive(Debug, Clone, PartialEq)]
126pub struct PolarStereographicGrid {
127 pub number_of_points: u32,
128 pub shape_of_earth: u8,
129 pub scale_factor_radius: u8,
130 pub scaled_value_radius: u32,
131 pub scale_factor_major_axis: u8,
132 pub scaled_value_major_axis: u32,
133 pub scale_factor_minor_axis: u8,
134 pub scaled_value_minor_axis: u32,
135 pub nx: u32,
136 pub ny: u32,
137 pub lat_first: i32,
138 pub lon_first: u32,
139 pub resolution_and_component_flags: u8,
140 pub lat_d: i32,
141 pub lon_v: u32,
142 pub dx: u32,
143 pub dy: u32,
144 pub projection_center_flag: u8,
145 pub scanning_mode: u8,
146}
147
148impl GridDefinition {
149 pub fn template_number(&self) -> u16 {
154 match self {
155 Self::LatLon(_) => 0,
156 Self::Mercator(_) => 10,
157 Self::PolarStereographic(_) => 20,
158 Self::LambertConformal(_) => 30,
159 Self::AlbersEqualArea(_) => 31,
160 Self::Unsupported(template) => *template,
161 }
162 }
163
164 pub fn as_lat_lon(&self) -> Option<&LatLonGrid> {
165 match self {
166 Self::LatLon(grid) => Some(grid),
167 _ => None,
168 }
169 }
170
171 pub fn as_polar_stereographic(&self) -> Option<&PolarStereographicGrid> {
172 match self {
173 Self::PolarStereographic(grid) => Some(grid),
174 _ => None,
175 }
176 }
177
178 pub fn as_mercator(&self) -> Option<&MercatorGrid> {
179 match self {
180 Self::Mercator(grid) => Some(grid),
181 _ => None,
182 }
183 }
184
185 pub fn as_lambert_conformal(&self) -> Option<&LambertConformalGrid> {
186 match self {
187 Self::LambertConformal(grid) => Some(grid),
188 _ => None,
189 }
190 }
191
192 pub fn as_albers_equal_area(&self) -> Option<&AlbersEqualAreaGrid> {
193 match self {
194 Self::AlbersEqualArea(grid) => Some(grid),
195 _ => None,
196 }
197 }
198
199 pub fn unsupported_template(&self) -> Option<u16> {
200 match self {
201 Self::Unsupported(template) => Some(*template),
202 _ => None,
203 }
204 }
205
206 pub fn shape(&self) -> (usize, usize) {
207 match self {
208 Self::LatLon(g) => (g.ni as usize, g.nj as usize),
209 Self::Mercator(g) => (g.ni as usize, g.nj as usize),
210 Self::PolarStereographic(g) => (g.nx as usize, g.ny as usize),
211 Self::LambertConformal(g) => (g.nx as usize, g.ny as usize),
212 Self::AlbersEqualArea(g) => (g.nx as usize, g.ny as usize),
213 Self::Unsupported(_) => (0, 0),
214 }
215 }
216
217 pub fn shape_num_points(&self) -> Result<usize> {
218 match self {
219 Self::LatLon(g) => checked_grid_point_count(g.ni, g.nj),
220 Self::Mercator(g) => checked_grid_point_count(g.ni, g.nj),
221 Self::PolarStereographic(g) => checked_grid_point_count(g.nx, g.ny),
222 Self::LambertConformal(g) => checked_grid_point_count(g.nx, g.ny),
223 Self::AlbersEqualArea(g) => checked_grid_point_count(g.nx, g.ny),
224 Self::Unsupported(_) => Ok(0),
225 }
226 }
227
228 pub fn ndarray_shape(&self) -> Vec<usize> {
229 let (ni, nj) = self.shape();
230 match self {
231 Self::LatLon(_)
232 | Self::Mercator(_)
233 | Self::PolarStereographic(_)
234 | Self::LambertConformal(_)
235 | Self::AlbersEqualArea(_)
236 if ni > 0 && nj > 0 =>
237 {
238 vec![nj, ni]
239 }
240 _ => Vec::new(),
241 }
242 }
243
244 pub fn num_points(&self) -> usize {
245 self.checked_num_points().unwrap_or(usize::MAX)
246 }
247
248 pub fn checked_num_points(&self) -> Result<usize> {
249 match self {
250 Self::LatLon(_) => self.shape_num_points(),
251 Self::Mercator(g) => Ok(g.number_of_points as usize),
252 Self::PolarStereographic(g) => Ok(g.number_of_points as usize),
253 Self::LambertConformal(g) => Ok(g.number_of_points as usize),
254 Self::AlbersEqualArea(g) => Ok(g.number_of_points as usize),
255 Self::Unsupported(_) => Ok(0),
256 }
257 }
258
259 pub fn declared_num_points(&self) -> Option<usize> {
260 match self {
261 Self::Mercator(g) => Some(g.number_of_points as usize),
262 Self::PolarStereographic(g) => Some(g.number_of_points as usize),
263 Self::LambertConformal(g) => Some(g.number_of_points as usize),
264 Self::AlbersEqualArea(g) => Some(g.number_of_points as usize),
265 Self::LatLon(_) | Self::Unsupported(_) => None,
266 }
267 }
268
269 pub fn projected_x_coordinates(&self) -> Result<Option<Vec<f64>>> {
274 self.projected_x_coordinates_with_limit(None)
275 }
276
277 pub fn projected_x_coordinates_with_limit(
278 &self,
279 max_axis_points: Option<usize>,
280 ) -> Result<Option<Vec<f64>>> {
281 match self {
282 Self::Mercator(grid) => Ok(Some(grid.x_coordinates_with_limit(max_axis_points)?)),
283 Self::PolarStereographic(grid) => {
284 Ok(Some(grid.x_coordinates_with_limit(max_axis_points)?))
285 }
286 Self::LambertConformal(grid) => {
287 Ok(Some(grid.x_coordinates_with_limit(max_axis_points)?))
288 }
289 Self::AlbersEqualArea(grid) => {
290 Ok(Some(grid.x_coordinates_with_limit(max_axis_points)?))
291 }
292 _ => Ok(None),
293 }
294 }
295
296 pub fn projected_y_coordinates(&self) -> Result<Option<Vec<f64>>> {
301 self.projected_y_coordinates_with_limit(None)
302 }
303
304 pub fn projected_y_coordinates_with_limit(
305 &self,
306 max_axis_points: Option<usize>,
307 ) -> Result<Option<Vec<f64>>> {
308 match self {
309 Self::Mercator(grid) => Ok(Some(grid.y_coordinates_with_limit(max_axis_points)?)),
310 Self::PolarStereographic(grid) => {
311 Ok(Some(grid.y_coordinates_with_limit(max_axis_points)?))
312 }
313 Self::LambertConformal(grid) => {
314 Ok(Some(grid.y_coordinates_with_limit(max_axis_points)?))
315 }
316 Self::AlbersEqualArea(grid) => {
317 Ok(Some(grid.y_coordinates_with_limit(max_axis_points)?))
318 }
319 _ => Ok(None),
320 }
321 }
322
323 pub fn validate_supported_scan_order(&self) -> Result<()> {
324 match self {
325 Self::LatLon(grid) => grid.validate_supported_scan_order(),
326 Self::Mercator(grid) => grid.validate_supported_scan_order(),
327 Self::PolarStereographic(grid) => grid.validate_supported_scan_order(),
328 Self::LambertConformal(grid) => grid.validate_supported_scan_order(),
329 Self::AlbersEqualArea(grid) => grid.validate_supported_scan_order(),
330 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
331 }
332 }
333
334 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
335 match self {
336 Self::LatLon(grid) => grid.reorder_for_ndarray_in_place(values),
337 Self::Mercator(grid) => grid.reorder_for_ndarray_in_place(values),
338 Self::PolarStereographic(grid) => grid.reorder_for_ndarray_in_place(values),
339 Self::LambertConformal(grid) => grid.reorder_for_ndarray_in_place(values),
340 Self::AlbersEqualArea(grid) => grid.reorder_for_ndarray_in_place(values),
341 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
342 }
343 }
344
345 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
346 if section_bytes.len() < 14 {
347 return Err(Error::InvalidSection {
348 section: 3,
349 reason: format!("expected at least 14 bytes, got {}", section_bytes.len()),
350 });
351 }
352 if section_bytes[4] != 3 {
353 return Err(Error::InvalidSection {
354 section: section_bytes[4],
355 reason: "not a grid definition section".into(),
356 });
357 }
358
359 let template = u16::from_be_bytes(section_bytes[12..14].try_into().unwrap());
360 match template {
361 0 => parse_latlon(section_bytes),
362 10 => parse_mercator(section_bytes),
363 20 => parse_polar_stereographic(section_bytes),
364 30 => parse_lambert_conformal(section_bytes),
365 31 => parse_albers_equal_area(section_bytes),
366 _ => Ok(Self::Unsupported(template)),
367 }
368 }
369}
370
371impl LatLonGrid {
372 pub fn longitudes(&self) -> Result<Vec<f64>> {
373 self.longitudes_with_limit(None)
374 }
375
376 pub fn longitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
377 let step = self.di as f64 / 1_000_000.0;
378 let signed_step = if self.i_scans_positive() { step } else { -step };
379 let start = self.lon_first as f64 / 1_000_000.0;
380 linear_axis(
381 self.ni,
382 start,
383 signed_step,
384 max_axis_points,
385 "longitude axis",
386 )
387 }
388
389 pub fn latitudes(&self) -> Result<Vec<f64>> {
390 self.latitudes_with_limit(None)
391 }
392
393 pub fn latitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
394 let step = self.dj as f64 / 1_000_000.0;
395 let signed_step = if self.j_scans_positive() { step } else { -step };
396 let start = self.lat_first as f64 / 1_000_000.0;
397 linear_axis(
398 self.nj,
399 start,
400 signed_step,
401 max_axis_points,
402 "latitude axis",
403 )
404 }
405
406 pub fn reorder_for_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
407 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
408 Ok(values)
409 }
410
411 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
412 self.reorder_grib_scan_to_ndarray_in_place(values)
413 }
414
415 pub fn reorder_grib_scan_to_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
416 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
417 Ok(values)
418 }
419
420 pub fn reorder_grib_scan_to_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
421 transform_supported_scan_order_in_place(
422 values,
423 self.ni as usize,
424 self.nj as usize,
425 self.scanning_mode,
426 )
427 }
428
429 pub fn reorder_ndarray_to_grib_scan<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
430 self.reorder_ndarray_to_grib_scan_in_place(&mut values)?;
431 Ok(values)
432 }
433
434 pub fn reorder_ndarray_to_grib_scan_in_place<T>(&self, values: &mut [T]) -> Result<()> {
435 transform_supported_scan_order_in_place(
436 values,
437 self.ni as usize,
438 self.nj as usize,
439 self.scanning_mode,
440 )
441 }
442
443 pub fn validate_supported_scan_order(&self) -> Result<()> {
444 validate_supported_scan_order(self.scanning_mode)
445 }
446
447 fn i_scans_positive(&self) -> bool {
448 i_scans_positive(self.scanning_mode)
449 }
450
451 fn j_scans_positive(&self) -> bool {
452 j_scans_positive(self.scanning_mode)
453 }
454}
455
456impl MercatorGrid {
457 pub fn x_coordinates(&self) -> Result<Vec<f64>> {
458 self.x_coordinates_with_limit(None)
459 }
460
461 pub fn x_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
462 projected_x_coordinates(self.ni, self.di, self.scanning_mode, max_axis_points)
463 }
464
465 pub fn y_coordinates(&self) -> Result<Vec<f64>> {
466 self.y_coordinates_with_limit(None)
467 }
468
469 pub fn y_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
470 projected_y_coordinates(self.nj, self.dj, self.scanning_mode, max_axis_points)
471 }
472
473 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
474 transform_supported_scan_order_in_place(
475 values,
476 self.ni as usize,
477 self.nj as usize,
478 self.scanning_mode,
479 )
480 }
481
482 pub fn validate_supported_scan_order(&self) -> Result<()> {
483 validate_supported_scan_order(self.scanning_mode)
484 }
485}
486
487impl LambertConformalGrid {
488 pub fn x_coordinates(&self) -> Result<Vec<f64>> {
489 self.x_coordinates_with_limit(None)
490 }
491
492 pub fn x_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
493 projected_x_coordinates(self.nx, self.dx, self.scanning_mode, max_axis_points)
494 }
495
496 pub fn y_coordinates(&self) -> Result<Vec<f64>> {
497 self.y_coordinates_with_limit(None)
498 }
499
500 pub fn y_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
501 projected_y_coordinates(self.ny, self.dy, self.scanning_mode, max_axis_points)
502 }
503
504 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
505 transform_supported_scan_order_in_place(
506 values,
507 self.nx as usize,
508 self.ny as usize,
509 self.scanning_mode,
510 )
511 }
512
513 pub fn validate_supported_scan_order(&self) -> Result<()> {
514 validate_supported_scan_order(self.scanning_mode)
515 }
516}
517
518impl AlbersEqualAreaGrid {
519 pub fn x_coordinates(&self) -> Result<Vec<f64>> {
520 self.x_coordinates_with_limit(None)
521 }
522
523 pub fn x_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
524 projected_x_coordinates(self.nx, self.dx, self.scanning_mode, max_axis_points)
525 }
526
527 pub fn y_coordinates(&self) -> Result<Vec<f64>> {
528 self.y_coordinates_with_limit(None)
529 }
530
531 pub fn y_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
532 projected_y_coordinates(self.ny, self.dy, self.scanning_mode, max_axis_points)
533 }
534
535 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
536 transform_supported_scan_order_in_place(
537 values,
538 self.nx as usize,
539 self.ny as usize,
540 self.scanning_mode,
541 )
542 }
543
544 pub fn validate_supported_scan_order(&self) -> Result<()> {
545 validate_supported_scan_order(self.scanning_mode)
546 }
547}
548
549impl PolarStereographicGrid {
550 pub fn x_coordinates(&self) -> Result<Vec<f64>> {
551 self.x_coordinates_with_limit(None)
552 }
553
554 pub fn x_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
555 projected_x_coordinates(self.nx, self.dx, self.scanning_mode, max_axis_points)
556 }
557
558 pub fn y_coordinates(&self) -> Result<Vec<f64>> {
559 self.y_coordinates_with_limit(None)
560 }
561
562 pub fn y_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
563 projected_y_coordinates(self.ny, self.dy, self.scanning_mode, max_axis_points)
564 }
565
566 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
567 transform_supported_scan_order_in_place(
568 values,
569 self.nx as usize,
570 self.ny as usize,
571 self.scanning_mode,
572 )
573 }
574
575 pub fn validate_supported_scan_order(&self) -> Result<()> {
576 validate_supported_scan_order(self.scanning_mode)
577 }
578}
579
580fn transform_supported_scan_order_in_place<T>(
581 values: &mut [T],
582 ni: usize,
583 nj: usize,
584 scanning_mode: u8,
585) -> Result<()> {
586 validate_supported_scan_order(scanning_mode)?;
587 let expected = ni
588 .checked_mul(nj)
589 .ok_or_else(|| Error::Other("grid point count overflow".into()))?;
590 if values.len() != expected {
591 return Err(Error::DataLengthMismatch {
592 expected,
593 actual: values.len(),
594 });
595 }
596
597 if adjacent_rows_alternate_direction(scanning_mode) {
598 reverse_alternating_rows(values, ni, nj, i_scans_positive(scanning_mode));
599 }
600
601 Ok(())
602}
603
604fn validate_supported_scan_order(scanning_mode: u8) -> Result<()> {
605 if i_points_are_consecutive(scanning_mode) {
606 Ok(())
607 } else {
608 Err(Error::UnsupportedScanningMode(scanning_mode))
609 }
610}
611
612fn i_scans_positive(scanning_mode: u8) -> bool {
613 scanning_mode & 0b1000_0000 == 0
614}
615
616fn j_scans_positive(scanning_mode: u8) -> bool {
617 scanning_mode & 0b0100_0000 != 0
618}
619
620fn i_points_are_consecutive(scanning_mode: u8) -> bool {
621 scanning_mode & 0b0010_0000 == 0
622}
623
624fn adjacent_rows_alternate_direction(scanning_mode: u8) -> bool {
625 scanning_mode & 0b0001_0000 != 0
626}
627
628fn projected_x_coordinates(
629 count: u32,
630 spacing_millimetres: u32,
631 scanning_mode: u8,
632 max_axis_points: Option<usize>,
633) -> Result<Vec<f64>> {
634 projected_axis(
635 count,
636 spacing_millimetres,
637 i_scans_positive(scanning_mode),
638 max_axis_points,
639 "projected x axis",
640 )
641}
642
643fn projected_y_coordinates(
644 count: u32,
645 spacing_millimetres: u32,
646 scanning_mode: u8,
647 max_axis_points: Option<usize>,
648) -> Result<Vec<f64>> {
649 projected_axis(
650 count,
651 spacing_millimetres,
652 j_scans_positive(scanning_mode),
653 max_axis_points,
654 "projected y axis",
655 )
656}
657
658fn projected_axis(
659 count: u32,
660 spacing_millimetres: u32,
661 scans_positive: bool,
662 max_axis_points: Option<usize>,
663 what: &'static str,
664) -> Result<Vec<f64>> {
665 let step = f64::from(spacing_millimetres) / 1_000.0;
666 let signed_step = if scans_positive { step } else { -step };
667 linear_axis(count, 0.0, signed_step, max_axis_points, what)
668}
669
670fn linear_axis(
671 count: u32,
672 start: f64,
673 signed_step: f64,
674 max_axis_points: Option<usize>,
675 what: &'static str,
676) -> Result<Vec<f64>> {
677 let count = count as usize;
678 ensure_limit(what, count, max_axis_points)?;
679 let mut axis = Vec::new();
680 axis.try_reserve(count)
681 .map_err(|e| Error::Other(format!("failed to reserve {count} {what} coordinates: {e}")))?;
682 axis.extend((0..count).map(|index| start + signed_step * index as f64));
683 Ok(axis)
684}
685
686fn checked_grid_point_count(nx: u32, ny: u32) -> Result<usize> {
687 let count = u64::from(nx) * u64::from(ny);
688 usize::try_from(count)
689 .map_err(|_| Error::Other(format!("grid point count {count} does not fit in usize")))
690}
691
692fn ensure_limit(what: &'static str, requested: usize, limit: Option<usize>) -> Result<()> {
693 if let Some(limit) = limit {
694 if requested > limit {
695 return Err(Error::LimitExceeded {
696 what,
697 requested,
698 limit,
699 });
700 }
701 }
702 Ok(())
703}
704
705fn reverse_alternating_rows<T>(values: &mut [T], ni: usize, nj: usize, i_scans_positive: bool) {
706 for row in 0..nj {
707 let reverse = if i_scans_positive {
708 row % 2 == 1
709 } else {
710 row % 2 == 0
711 };
712 if reverse {
713 values[row * ni..(row + 1) * ni].reverse();
714 }
715 }
716}
717
718fn parse_latlon(data: &[u8]) -> Result<GridDefinition> {
719 if data.len() < 72 {
720 return Err(Error::InvalidSection {
721 section: 3,
722 reason: format!("template 3.0 requires 72 bytes, got {}", data.len()),
723 });
724 }
725
726 let ni = u32::from_be_bytes(data[30..34].try_into().unwrap());
727 let nj = u32::from_be_bytes(data[34..38].try_into().unwrap());
728 let lat_first = grib_i32(&data[46..50]).unwrap();
729 let lon_first = grib_i32(&data[50..54]).unwrap();
730 let lat_last = grib_i32(&data[55..59]).unwrap();
731 let lon_last = grib_i32(&data[59..63]).unwrap();
732 let di = u32::from_be_bytes(data[63..67].try_into().unwrap());
733 let dj = u32::from_be_bytes(data[67..71].try_into().unwrap());
734 let scanning_mode = data[71];
735
736 Ok(GridDefinition::LatLon(LatLonGrid {
737 ni,
738 nj,
739 lat_first,
740 lon_first,
741 lat_last,
742 lon_last,
743 di,
744 dj,
745 scanning_mode,
746 }))
747}
748
749fn parse_mercator(data: &[u8]) -> Result<GridDefinition> {
750 if data.len() < 72 {
751 return Err(Error::InvalidSection {
752 section: 3,
753 reason: format!("template 3.10 requires 72 bytes, got {}", data.len()),
754 });
755 }
756
757 Ok(GridDefinition::Mercator(MercatorGrid {
758 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
759 shape_of_earth: data[14],
760 scale_factor_radius: data[15],
761 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
762 scale_factor_major_axis: data[20],
763 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
764 scale_factor_minor_axis: data[25],
765 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
766 ni: u32::from_be_bytes(data[30..34].try_into().unwrap()),
767 nj: u32::from_be_bytes(data[34..38].try_into().unwrap()),
768 lat_first: grib_i32(&data[38..42]).unwrap(),
769 lon_first: grib_i32(&data[42..46]).unwrap(),
770 resolution_and_component_flags: data[46],
771 lat_d: grib_i32(&data[47..51]).unwrap(),
772 lat_last: grib_i32(&data[51..55]).unwrap(),
773 lon_last: grib_i32(&data[55..59]).unwrap(),
774 scanning_mode: data[59],
775 orientation_of_grid: u32::from_be_bytes(data[60..64].try_into().unwrap()),
776 di: u32::from_be_bytes(data[64..68].try_into().unwrap()),
777 dj: u32::from_be_bytes(data[68..72].try_into().unwrap()),
778 }))
779}
780
781fn parse_polar_stereographic(data: &[u8]) -> Result<GridDefinition> {
782 if data.len() < 65 {
783 return Err(Error::InvalidSection {
784 section: 3,
785 reason: format!("template 3.20 requires 65 bytes, got {}", data.len()),
786 });
787 }
788
789 Ok(GridDefinition::PolarStereographic(PolarStereographicGrid {
790 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
791 shape_of_earth: data[14],
792 scale_factor_radius: data[15],
793 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
794 scale_factor_major_axis: data[20],
795 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
796 scale_factor_minor_axis: data[25],
797 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
798 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
799 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
800 lat_first: grib_i32(&data[38..42]).unwrap(),
801 lon_first: u32::from_be_bytes(data[42..46].try_into().unwrap()),
802 resolution_and_component_flags: data[46],
803 lat_d: grib_i32(&data[47..51]).unwrap(),
804 lon_v: u32::from_be_bytes(data[51..55].try_into().unwrap()),
805 dx: u32::from_be_bytes(data[55..59].try_into().unwrap()),
806 dy: u32::from_be_bytes(data[59..63].try_into().unwrap()),
807 projection_center_flag: data[63],
808 scanning_mode: data[64],
809 }))
810}
811
812fn parse_albers_equal_area(data: &[u8]) -> Result<GridDefinition> {
813 if data.len() < 81 {
814 return Err(Error::InvalidSection {
815 section: 3,
816 reason: format!("template 3.31 requires 81 bytes, got {}", data.len()),
817 });
818 }
819
820 Ok(GridDefinition::AlbersEqualArea(AlbersEqualAreaGrid {
821 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
822 shape_of_earth: data[14],
823 scale_factor_radius: data[15],
824 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
825 scale_factor_major_axis: data[20],
826 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
827 scale_factor_minor_axis: data[25],
828 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
829 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
830 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
831 lat_first: grib_i32(&data[38..42]).unwrap(),
832 lon_first: u32::from_be_bytes(data[42..46].try_into().unwrap()),
833 resolution_and_component_flags: data[46],
834 lat_d: grib_i32(&data[47..51]).unwrap(),
835 lon_v: u32::from_be_bytes(data[51..55].try_into().unwrap()),
836 dx: u32::from_be_bytes(data[55..59].try_into().unwrap()),
837 dy: u32::from_be_bytes(data[59..63].try_into().unwrap()),
838 projection_center_flag: data[63],
839 scanning_mode: data[64],
840 latin1: grib_i32(&data[65..69]).unwrap(),
841 latin2: grib_i32(&data[69..73]).unwrap(),
842 lat_southern_pole: grib_i32(&data[73..77]).unwrap(),
843 lon_southern_pole: u32::from_be_bytes(data[77..81].try_into().unwrap()),
844 }))
845}
846
847fn parse_lambert_conformal(data: &[u8]) -> Result<GridDefinition> {
848 if data.len() < 81 {
849 return Err(Error::InvalidSection {
850 section: 3,
851 reason: format!("template 3.30 requires 81 bytes, got {}", data.len()),
852 });
853 }
854
855 Ok(GridDefinition::LambertConformal(LambertConformalGrid {
856 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
857 shape_of_earth: data[14],
858 scale_factor_radius: data[15],
859 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
860 scale_factor_major_axis: data[20],
861 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
862 scale_factor_minor_axis: data[25],
863 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
864 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
865 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
866 lat_first: grib_i32(&data[38..42]).unwrap(),
867 lon_first: u32::from_be_bytes(data[42..46].try_into().unwrap()),
868 resolution_and_component_flags: data[46],
869 lat_d: grib_i32(&data[47..51]).unwrap(),
870 lon_v: u32::from_be_bytes(data[51..55].try_into().unwrap()),
871 dx: u32::from_be_bytes(data[55..59].try_into().unwrap()),
872 dy: u32::from_be_bytes(data[59..63].try_into().unwrap()),
873 projection_center_flag: data[63],
874 scanning_mode: data[64],
875 latin1: grib_i32(&data[65..69]).unwrap(),
876 latin2: grib_i32(&data[69..73]).unwrap(),
877 lat_southern_pole: grib_i32(&data[73..77]).unwrap(),
878 lon_southern_pole: u32::from_be_bytes(data[77..81].try_into().unwrap()),
879 }))
880}
881
882#[cfg(test)]
883mod tests {
884 use super::{
885 AlbersEqualAreaGrid, GridDefinition, LambertConformalGrid, LatLonGrid, MercatorGrid,
886 PolarStereographicGrid,
887 };
888 use crate::binary::encode_wmo_i32;
889
890 #[test]
891 fn reports_latlon_shape() {
892 let grid = GridDefinition::LatLon(LatLonGrid {
893 ni: 3,
894 nj: 2,
895 lat_first: 50_000_000,
896 lon_first: -120_000_000,
897 lat_last: 49_000_000,
898 lon_last: -118_000_000,
899 di: 1_000_000,
900 dj: 1_000_000,
901 scanning_mode: 0,
902 });
903
904 assert_eq!(grid.shape(), (3, 2));
905 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
906 assert_eq!(grid.template_number(), 0);
907 assert!(grid.as_lat_lon().is_some());
908 assert!(grid.as_mercator().is_none());
909 assert!(grid.as_polar_stereographic().is_none());
910 assert!(grid.as_lambert_conformal().is_none());
911 assert!(grid.as_albers_equal_area().is_none());
912 assert_eq!(grid.unsupported_template(), None);
913 match grid {
914 GridDefinition::LatLon(ref latlon) => {
915 assert_eq!(latlon.longitudes().unwrap(), vec![-120.0, -119.0, -118.0]);
916 assert_eq!(latlon.latitudes().unwrap(), vec![50.0, 49.0]);
917 }
918 other => panic!("expected lat/lon grid, got {other:?}"),
919 }
920 }
921
922 #[test]
923 fn parses_mercator_template() {
924 let section = build_mercator_section(0);
925 let grid = GridDefinition::parse(§ion).unwrap();
926
927 assert_eq!(grid.shape(), (3, 2));
928 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
929 assert_eq!(grid.num_points(), 6);
930 assert_eq!(grid.template_number(), 10);
931 assert!(grid.as_lat_lon().is_none());
932 assert!(grid.as_mercator().is_some());
933 assert!(grid.as_lambert_conformal().is_none());
934 assert_eq!(grid.unsupported_template(), None);
935 match grid {
936 GridDefinition::Mercator(mercator) => {
937 assert_eq!(mercator.number_of_points, 6);
938 assert_eq!(mercator.shape_of_earth, 6);
939 assert_eq!(mercator.ni, 3);
940 assert_eq!(mercator.nj, 2);
941 assert_eq!(mercator.lat_first, -20_000_000);
942 assert_eq!(mercator.lon_first, -100_000_000);
943 assert_eq!(mercator.resolution_and_component_flags, 0x08);
944 assert_eq!(mercator.lat_d, 0);
945 assert_eq!(mercator.lat_last, 20_000_000);
946 assert_eq!(mercator.lon_last, -98_000_000);
947 assert_eq!(mercator.scanning_mode, 0);
948 assert_eq!(mercator.orientation_of_grid, 0);
949 assert_eq!(mercator.di, 1_000_000);
950 assert_eq!(mercator.dj, 2_000_000);
951 }
952 other => panic!("expected Mercator grid, got {other:?}"),
953 }
954 }
955
956 #[test]
957 fn parses_polar_stereographic_template() {
958 let section = build_polar_stereographic_section(0);
959 let grid = GridDefinition::parse(§ion).unwrap();
960
961 assert_eq!(grid.shape(), (3, 2));
962 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
963 assert_eq!(grid.num_points(), 6);
964 assert_eq!(grid.template_number(), 20);
965 assert!(grid.as_lat_lon().is_none());
966 assert!(grid.as_mercator().is_none());
967 assert!(grid.as_polar_stereographic().is_some());
968 assert!(grid.as_lambert_conformal().is_none());
969 assert!(grid.as_albers_equal_area().is_none());
970 assert_eq!(grid.unsupported_template(), None);
971 match grid {
972 GridDefinition::PolarStereographic(polar) => {
973 assert_eq!(polar.number_of_points, 6);
974 assert_eq!(polar.shape_of_earth, 6);
975 assert_eq!(polar.nx, 3);
976 assert_eq!(polar.ny, 2);
977 assert_eq!(polar.lat_first, 41_612_949);
978 assert_eq!(polar.lon_first, 185_117_126);
979 assert_eq!(polar.resolution_and_component_flags, 0x08);
980 assert_eq!(polar.lat_d, 60_000_000);
981 assert_eq!(polar.lon_v, 225_000_000);
982 assert_eq!(polar.dx, 3_000_000);
983 assert_eq!(polar.dy, 3_000_000);
984 assert_eq!(polar.projection_center_flag, 0);
985 assert_eq!(polar.scanning_mode, 0);
986 }
987 other => panic!("expected polar stereographic grid, got {other:?}"),
988 }
989 }
990
991 #[test]
992 fn parses_albers_equal_area_template() {
993 let section = build_albers_section();
994 let grid = GridDefinition::parse(§ion).unwrap();
995
996 assert_eq!(grid.shape(), (3, 2));
997 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
998 assert_eq!(grid.num_points(), 6);
999 assert_eq!(grid.template_number(), 31);
1000 assert!(grid.as_lat_lon().is_none());
1001 assert!(grid.as_albers_equal_area().is_some());
1002 assert_eq!(grid.unsupported_template(), None);
1003 match grid {
1004 GridDefinition::AlbersEqualArea(albers) => {
1005 assert_eq!(albers.number_of_points, 6);
1006 assert_eq!(albers.shape_of_earth, 6);
1007 assert_eq!(albers.nx, 3);
1008 assert_eq!(albers.ny, 2);
1009 assert_eq!(albers.lat_first, 23_000_000);
1010 assert_eq!(albers.lon_first, 240_000_000);
1011 assert_eq!(albers.resolution_and_component_flags, 0x08);
1012 assert_eq!(albers.lat_d, 25_000_000);
1013 assert_eq!(albers.lon_v, 265_000_000);
1014 assert_eq!(albers.dx, 4_000_000);
1015 assert_eq!(albers.dy, 5_000_000);
1016 assert_eq!(albers.projection_center_flag, 0);
1017 assert_eq!(albers.scanning_mode, 0);
1018 assert_eq!(albers.latin1, 29_500_000);
1019 assert_eq!(albers.latin2, 45_500_000);
1020 assert_eq!(albers.lat_southern_pole, -90_000_000);
1021 assert_eq!(albers.lon_southern_pole, 0);
1022 }
1023 other => panic!("expected Albers equal-area grid, got {other:?}"),
1024 }
1025 }
1026
1027 #[test]
1028 fn parses_lambert_conformal_template() {
1029 let section = build_lambert_section();
1030 let grid = GridDefinition::parse(§ion).unwrap();
1031
1032 assert_eq!(grid.shape(), (3, 2));
1033 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1034 assert_eq!(grid.num_points(), 6);
1035 assert_eq!(grid.template_number(), 30);
1036 assert!(grid.as_lat_lon().is_none());
1037 assert!(grid.as_lambert_conformal().is_some());
1038 assert!(grid.as_albers_equal_area().is_none());
1039 assert_eq!(grid.unsupported_template(), None);
1040 match grid {
1041 GridDefinition::LambertConformal(lambert) => {
1042 assert_eq!(lambert.number_of_points, 6);
1043 assert_eq!(lambert.shape_of_earth, 1);
1044 assert_eq!(lambert.scaled_value_radius, 6_371_200);
1045 assert_eq!(lambert.nx, 3);
1046 assert_eq!(lambert.ny, 2);
1047 assert_eq!(lambert.lat_first, 12_190_000);
1048 assert_eq!(lambert.lon_first, 226_541_000);
1049 assert_eq!(lambert.resolution_and_component_flags, 0x08);
1050 assert_eq!(lambert.lat_d, 25_000_000);
1051 assert_eq!(lambert.lon_v, 265_000_000);
1052 assert_eq!(lambert.dx, 2_539_703);
1053 assert_eq!(lambert.dy, 2_539_703);
1054 assert_eq!(lambert.projection_center_flag, 0);
1055 assert_eq!(lambert.scanning_mode, 0);
1056 assert_eq!(lambert.latin1, 25_000_000);
1057 assert_eq!(lambert.latin2, 25_000_000);
1058 assert_eq!(lambert.lat_southern_pole, -90_000_000);
1059 assert_eq!(lambert.lon_southern_pole, 0);
1060 }
1061 other => panic!("expected Lambert conformal grid, got {other:?}"),
1062 }
1063 }
1064
1065 #[test]
1066 fn reports_unsupported_template_helpers() {
1067 let grid = GridDefinition::Unsupported(3_276);
1068
1069 assert_eq!(grid.template_number(), 3_276);
1070 assert!(grid.as_lat_lon().is_none());
1071 assert!(grid.as_mercator().is_none());
1072 assert!(grid.as_polar_stereographic().is_none());
1073 assert!(grid.as_lambert_conformal().is_none());
1074 assert!(grid.as_albers_equal_area().is_none());
1075 assert_eq!(grid.unsupported_template(), Some(3_276));
1076 }
1077
1078 #[test]
1079 fn normalizes_alternating_row_scan() {
1080 let grid = LatLonGrid {
1081 ni: 3,
1082 nj: 2,
1083 lat_first: 0,
1084 lon_first: 0,
1085 lat_last: 0,
1086 lon_last: 0,
1087 di: 1,
1088 dj: 1,
1089 scanning_mode: 0b0001_0000,
1090 };
1091
1092 let ordered = grid
1093 .reorder_for_ndarray(vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0])
1094 .unwrap();
1095 assert_eq!(ordered, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1096 }
1097
1098 #[test]
1099 fn converts_ndarray_order_to_alternating_scan_order() {
1100 let grid = LatLonGrid {
1101 ni: 3,
1102 nj: 2,
1103 lat_first: 0,
1104 lon_first: 0,
1105 lat_last: 0,
1106 lon_last: 0,
1107 di: 1,
1108 dj: 1,
1109 scanning_mode: 0b0001_0000,
1110 };
1111
1112 let scan_order = grid
1113 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
1114 .unwrap();
1115 assert_eq!(scan_order, vec![1, 2, 3, 6, 5, 4]);
1116 }
1117
1118 #[test]
1119 fn normalizes_polar_stereographic_alternating_row_scan() {
1120 let grid = PolarStereographicGrid {
1121 number_of_points: 6,
1122 shape_of_earth: 6,
1123 scale_factor_radius: 0,
1124 scaled_value_radius: 0,
1125 scale_factor_major_axis: 0,
1126 scaled_value_major_axis: 0,
1127 scale_factor_minor_axis: 0,
1128 scaled_value_minor_axis: 0,
1129 nx: 3,
1130 ny: 2,
1131 lat_first: 0,
1132 lon_first: 0,
1133 resolution_and_component_flags: 0,
1134 lat_d: 0,
1135 lon_v: 0,
1136 dx: 1,
1137 dy: 1,
1138 projection_center_flag: 0,
1139 scanning_mode: 0b0001_0000,
1140 };
1141
1142 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1143 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
1144 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1145 }
1146
1147 #[test]
1148 fn normalizes_mercator_alternating_row_scan() {
1149 let grid = MercatorGrid {
1150 number_of_points: 6,
1151 shape_of_earth: 6,
1152 scale_factor_radius: 0,
1153 scaled_value_radius: 0,
1154 scale_factor_major_axis: 0,
1155 scaled_value_major_axis: 0,
1156 scale_factor_minor_axis: 0,
1157 scaled_value_minor_axis: 0,
1158 ni: 3,
1159 nj: 2,
1160 lat_first: 0,
1161 lon_first: 0,
1162 resolution_and_component_flags: 0,
1163 lat_d: 0,
1164 lat_last: 0,
1165 lon_last: 0,
1166 scanning_mode: 0b0001_0000,
1167 orientation_of_grid: 0,
1168 di: 1,
1169 dj: 1,
1170 };
1171
1172 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1173 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
1174 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1175 }
1176
1177 #[test]
1178 fn normalizes_lambert_alternating_row_scan() {
1179 let grid = LambertConformalGrid {
1180 number_of_points: 6,
1181 shape_of_earth: 1,
1182 scale_factor_radius: 0,
1183 scaled_value_radius: 6_371_200,
1184 scale_factor_major_axis: 0,
1185 scaled_value_major_axis: 0,
1186 scale_factor_minor_axis: 0,
1187 scaled_value_minor_axis: 0,
1188 nx: 3,
1189 ny: 2,
1190 lat_first: 0,
1191 lon_first: 0,
1192 resolution_and_component_flags: 0,
1193 lat_d: 0,
1194 lon_v: 0,
1195 dx: 1,
1196 dy: 1,
1197 projection_center_flag: 0,
1198 scanning_mode: 0b0001_0000,
1199 latin1: 0,
1200 latin2: 0,
1201 lat_southern_pole: 0,
1202 lon_southern_pole: 0,
1203 };
1204
1205 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1206 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
1207 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1208 }
1209
1210 #[test]
1211 fn normalizes_albers_alternating_row_scan() {
1212 let grid = AlbersEqualAreaGrid {
1213 number_of_points: 6,
1214 shape_of_earth: 6,
1215 scale_factor_radius: 0,
1216 scaled_value_radius: 0,
1217 scale_factor_major_axis: 0,
1218 scaled_value_major_axis: 0,
1219 scale_factor_minor_axis: 0,
1220 scaled_value_minor_axis: 0,
1221 nx: 3,
1222 ny: 2,
1223 lat_first: 0,
1224 lon_first: 0,
1225 resolution_and_component_flags: 0,
1226 lat_d: 0,
1227 lon_v: 0,
1228 dx: 1,
1229 dy: 1,
1230 projection_center_flag: 0,
1231 scanning_mode: 0b0001_0000,
1232 latin1: 0,
1233 latin2: 0,
1234 lat_southern_pole: 0,
1235 lon_southern_pole: 0,
1236 };
1237
1238 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1239 grid.reorder_for_ndarray_in_place(&mut values).unwrap();
1240 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1241 }
1242
1243 #[test]
1244 fn generates_projected_coordinate_offsets() {
1245 let mercator = GridDefinition::Mercator(MercatorGrid {
1246 number_of_points: 6,
1247 shape_of_earth: 6,
1248 scale_factor_radius: 0,
1249 scaled_value_radius: 0,
1250 scale_factor_major_axis: 0,
1251 scaled_value_major_axis: 0,
1252 scale_factor_minor_axis: 0,
1253 scaled_value_minor_axis: 0,
1254 ni: 3,
1255 nj: 2,
1256 lat_first: 0,
1257 lon_first: 0,
1258 resolution_and_component_flags: 0,
1259 lat_d: 0,
1260 lat_last: 0,
1261 lon_last: 0,
1262 scanning_mode: 0,
1263 orientation_of_grid: 0,
1264 di: 1_000_000,
1265 dj: 2_000_000,
1266 });
1267 assert_eq!(
1268 mercator.projected_x_coordinates().unwrap().unwrap(),
1269 vec![0.0, 1_000.0, 2_000.0]
1270 );
1271 assert_eq!(
1272 mercator.projected_y_coordinates().unwrap().unwrap(),
1273 vec![-0.0, -2_000.0]
1274 );
1275
1276 let polar = GridDefinition::PolarStereographic(PolarStereographicGrid {
1277 number_of_points: 6,
1278 shape_of_earth: 6,
1279 scale_factor_radius: 0,
1280 scaled_value_radius: 0,
1281 scale_factor_major_axis: 0,
1282 scaled_value_major_axis: 0,
1283 scale_factor_minor_axis: 0,
1284 scaled_value_minor_axis: 0,
1285 nx: 3,
1286 ny: 2,
1287 lat_first: 0,
1288 lon_first: 0,
1289 resolution_and_component_flags: 0,
1290 lat_d: 0,
1291 lon_v: 0,
1292 dx: 3_000_000,
1293 dy: 6_000_000,
1294 projection_center_flag: 0,
1295 scanning_mode: 0,
1296 });
1297 assert_eq!(
1298 polar.projected_x_coordinates().unwrap().unwrap(),
1299 vec![0.0, 3_000.0, 6_000.0]
1300 );
1301 assert_eq!(
1302 polar.projected_y_coordinates().unwrap().unwrap(),
1303 vec![-0.0, -6_000.0]
1304 );
1305
1306 let lambert = GridDefinition::LambertConformal(LambertConformalGrid {
1307 number_of_points: 6,
1308 shape_of_earth: 1,
1309 scale_factor_radius: 0,
1310 scaled_value_radius: 6_371_200,
1311 scale_factor_major_axis: 0,
1312 scaled_value_major_axis: 0,
1313 scale_factor_minor_axis: 0,
1314 scaled_value_minor_axis: 0,
1315 nx: 3,
1316 ny: 2,
1317 lat_first: 0,
1318 lon_first: 0,
1319 resolution_and_component_flags: 0,
1320 lat_d: 0,
1321 lon_v: 0,
1322 dx: 2_539_703,
1323 dy: 2_539_703,
1324 projection_center_flag: 0,
1325 scanning_mode: 0b1100_0000,
1326 latin1: 0,
1327 latin2: 0,
1328 lat_southern_pole: 0,
1329 lon_southern_pole: 0,
1330 });
1331 assert_eq!(
1332 lambert.projected_x_coordinates().unwrap().unwrap(),
1333 vec![-0.0, -2_539.703, -5_079.406]
1334 );
1335 assert_eq!(
1336 lambert.projected_y_coordinates().unwrap().unwrap(),
1337 vec![0.0, 2_539.703]
1338 );
1339
1340 let albers = GridDefinition::AlbersEqualArea(AlbersEqualAreaGrid {
1341 number_of_points: 6,
1342 shape_of_earth: 6,
1343 scale_factor_radius: 0,
1344 scaled_value_radius: 0,
1345 scale_factor_major_axis: 0,
1346 scaled_value_major_axis: 0,
1347 scale_factor_minor_axis: 0,
1348 scaled_value_minor_axis: 0,
1349 nx: 3,
1350 ny: 2,
1351 lat_first: 0,
1352 lon_first: 0,
1353 resolution_and_component_flags: 0,
1354 lat_d: 0,
1355 lon_v: 0,
1356 dx: 4_000_000,
1357 dy: 5_000_000,
1358 projection_center_flag: 0,
1359 scanning_mode: 0b1100_0000,
1360 latin1: 0,
1361 latin2: 0,
1362 lat_southern_pole: 0,
1363 lon_southern_pole: 0,
1364 });
1365 assert_eq!(
1366 albers.projected_x_coordinates().unwrap().unwrap(),
1367 vec![-0.0, -4_000.0, -8_000.0]
1368 );
1369 assert_eq!(
1370 albers.projected_y_coordinates().unwrap().unwrap(),
1371 vec![0.0, 5_000.0]
1372 );
1373 }
1374
1375 #[test]
1376 fn preserves_non_alternating_scan_modes_in_current_reader_order() {
1377 for scanning_mode in [0b0000_0000, 0b1000_0000, 0b0100_0000, 0b1100_0000] {
1378 let grid = LatLonGrid {
1379 ni: 3,
1380 nj: 2,
1381 lat_first: 0,
1382 lon_first: 0,
1383 lat_last: 0,
1384 lon_last: 0,
1385 di: 1,
1386 dj: 1,
1387 scanning_mode,
1388 };
1389
1390 let values = vec![1, 2, 3, 4, 5, 6];
1391 assert_eq!(
1392 grid.reorder_grib_scan_to_ndarray(values.clone()).unwrap(),
1393 values
1394 );
1395 assert_eq!(
1396 grid.reorder_ndarray_to_grib_scan(values.clone()).unwrap(),
1397 values
1398 );
1399 }
1400 }
1401
1402 #[test]
1403 fn rejects_j_consecutive_scan_order() {
1404 let grid = LatLonGrid {
1405 ni: 3,
1406 nj: 2,
1407 lat_first: 0,
1408 lon_first: 0,
1409 lat_last: 0,
1410 lon_last: 0,
1411 di: 1,
1412 dj: 1,
1413 scanning_mode: 0b0010_0000,
1414 };
1415
1416 let err = grid
1417 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
1418 .unwrap_err();
1419 assert!(matches!(
1420 err,
1421 crate::Error::UnsupportedScanningMode(0b0010_0000)
1422 ));
1423 }
1424
1425 fn build_polar_stereographic_section(scanning_mode: u8) -> Vec<u8> {
1426 let mut section = vec![0u8; 65];
1427 section[..4].copy_from_slice(&65u32.to_be_bytes());
1428 section[4] = 3;
1429 section[6..10].copy_from_slice(&6u32.to_be_bytes());
1430 section[12..14].copy_from_slice(&20u16.to_be_bytes());
1431 section[14] = 6;
1432 section[30..34].copy_from_slice(&3u32.to_be_bytes());
1433 section[34..38].copy_from_slice(&2u32.to_be_bytes());
1434 section[38..42].copy_from_slice(&encode_wmo_i32(41_612_949).unwrap());
1435 section[42..46].copy_from_slice(&185_117_126u32.to_be_bytes());
1436 section[46] = 0x08;
1437 section[47..51].copy_from_slice(&encode_wmo_i32(60_000_000).unwrap());
1438 section[51..55].copy_from_slice(&225_000_000u32.to_be_bytes());
1439 section[55..59].copy_from_slice(&3_000_000u32.to_be_bytes());
1440 section[59..63].copy_from_slice(&3_000_000u32.to_be_bytes());
1441 section[64] = scanning_mode;
1442 section
1443 }
1444
1445 fn build_mercator_section(scanning_mode: u8) -> Vec<u8> {
1446 let mut section = vec![0u8; 72];
1447 section[..4].copy_from_slice(&72u32.to_be_bytes());
1448 section[4] = 3;
1449 section[6..10].copy_from_slice(&6u32.to_be_bytes());
1450 section[12..14].copy_from_slice(&10u16.to_be_bytes());
1451 section[14] = 6;
1452 section[30..34].copy_from_slice(&3u32.to_be_bytes());
1453 section[34..38].copy_from_slice(&2u32.to_be_bytes());
1454 section[38..42].copy_from_slice(&encode_wmo_i32(-20_000_000).unwrap());
1455 section[42..46].copy_from_slice(&encode_wmo_i32(-100_000_000).unwrap());
1456 section[46] = 0x08;
1457 section[47..51].copy_from_slice(&encode_wmo_i32(0).unwrap());
1458 section[51..55].copy_from_slice(&encode_wmo_i32(20_000_000).unwrap());
1459 section[55..59].copy_from_slice(&encode_wmo_i32(-98_000_000).unwrap());
1460 section[59] = scanning_mode;
1461 section[64..68].copy_from_slice(&1_000_000u32.to_be_bytes());
1462 section[68..72].copy_from_slice(&2_000_000u32.to_be_bytes());
1463 section
1464 }
1465
1466 fn build_lambert_section() -> Vec<u8> {
1467 let mut section = vec![0u8; 81];
1468 section[..4].copy_from_slice(&81u32.to_be_bytes());
1469 section[4] = 3;
1470 section[6..10].copy_from_slice(&6u32.to_be_bytes());
1471 section[12..14].copy_from_slice(&30u16.to_be_bytes());
1472 section[14] = 1;
1473 section[16..20].copy_from_slice(&6_371_200u32.to_be_bytes());
1474 section[30..34].copy_from_slice(&3u32.to_be_bytes());
1475 section[34..38].copy_from_slice(&2u32.to_be_bytes());
1476 section[38..42].copy_from_slice(&encode_wmo_i32(12_190_000).unwrap());
1477 section[42..46].copy_from_slice(&226_541_000u32.to_be_bytes());
1478 section[46] = 0x08;
1479 section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
1480 section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
1481 section[55..59].copy_from_slice(&2_539_703u32.to_be_bytes());
1482 section[59..63].copy_from_slice(&2_539_703u32.to_be_bytes());
1483 section[65..69].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
1484 section[69..73].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
1485 section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
1486 section
1487 }
1488
1489 fn build_albers_section() -> Vec<u8> {
1490 let mut section = vec![0u8; 81];
1491 section[..4].copy_from_slice(&81u32.to_be_bytes());
1492 section[4] = 3;
1493 section[6..10].copy_from_slice(&6u32.to_be_bytes());
1494 section[12..14].copy_from_slice(&31u16.to_be_bytes());
1495 section[14] = 6;
1496 section[30..34].copy_from_slice(&3u32.to_be_bytes());
1497 section[34..38].copy_from_slice(&2u32.to_be_bytes());
1498 section[38..42].copy_from_slice(&encode_wmo_i32(23_000_000).unwrap());
1499 section[42..46].copy_from_slice(&240_000_000u32.to_be_bytes());
1500 section[46] = 0x08;
1501 section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
1502 section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
1503 section[55..59].copy_from_slice(&4_000_000u32.to_be_bytes());
1504 section[59..63].copy_from_slice(&5_000_000u32.to_be_bytes());
1505 section[65..69].copy_from_slice(&encode_wmo_i32(29_500_000).unwrap());
1506 section[69..73].copy_from_slice(&encode_wmo_i32(45_500_000).unwrap());
1507 section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
1508 section
1509 }
1510}