1use crate::binary::decode_wmo_i32;
4use crate::error::{Error, Result};
5use crate::{ensure_limit, filled_vec};
6
7#[derive(Debug, Clone, PartialEq)]
14#[non_exhaustive]
15pub enum GridDefinition {
16 LatLon(LatLonGrid),
18 RotatedLatLon(RotatedLatLonGrid),
20 Mercator(MercatorGrid),
22 AlbersEqualArea(AlbersEqualAreaGrid),
24 LambertConformal(LambertConformalGrid),
26 PolarStereographic(PolarStereographicGrid),
28 RegularGaussian(RegularGaussianGrid),
30 Unsupported(u16),
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub struct LatLonGrid {
37 pub ni: u32,
38 pub nj: u32,
39 pub lat_first: i32,
40 pub lon_first: i32,
41 pub lat_last: i32,
42 pub lon_last: i32,
43 pub di: u32,
46 pub dj: u32,
49 pub scanning_mode: u8,
50}
51
52pub const MISSING_INCREMENT: u32 = u32::MAX;
57
58#[derive(Debug, Clone, PartialEq)]
60pub struct RotatedLatLonGrid {
61 pub grid: LatLonGrid,
63 pub lat_southern_pole: i32,
64 pub lon_southern_pole: u32,
65 pub angle_of_rotation: f32,
66}
67
68#[derive(Debug, Clone, PartialEq)]
70pub struct RegularGaussianGrid {
71 pub ni: u32,
72 pub nj: u32,
73 pub lat_first: i32,
74 pub lon_first: i32,
75 pub lat_last: i32,
76 pub lon_last: i32,
77 pub di: u32,
78 pub number_of_parallels: u32,
80 pub scanning_mode: u8,
81}
82
83#[derive(Debug, Clone, PartialEq)]
85pub struct MercatorGrid {
86 pub core: ProjectedGridCore,
87 pub lat_d: i32,
88 pub lat_last: i32,
89 pub lon_last: i32,
90 pub orientation_of_grid: i32,
91}
92
93#[derive(Debug, Clone, PartialEq)]
99pub struct ProjectedGridCore {
100 pub number_of_points: u32,
101 pub shape_of_earth: u8,
102 pub scale_factor_radius: u8,
103 pub scaled_value_radius: u32,
104 pub scale_factor_major_axis: u8,
105 pub scaled_value_major_axis: u32,
106 pub scale_factor_minor_axis: u8,
107 pub scaled_value_minor_axis: u32,
108 pub nx: u32,
109 pub ny: u32,
110 pub lat_first: i32,
111 pub lon_first: i32,
112 pub resolution_and_component_flags: u8,
113 pub dx: u32,
114 pub dy: u32,
115 pub scanning_mode: u8,
116}
117
118#[derive(Debug, Clone, PartialEq)]
120pub struct LambertConformalGrid {
121 pub core: ProjectedGridCore,
122 pub lat_d: i32,
123 pub lon_v: i32,
124 pub projection_center_flag: u8,
125 pub latin1: i32,
126 pub latin2: i32,
127 pub lat_southern_pole: i32,
128 pub lon_southern_pole: i32,
129}
130
131#[derive(Debug, Clone, PartialEq)]
133pub struct AlbersEqualAreaGrid {
134 pub core: ProjectedGridCore,
135 pub lat_d: i32,
136 pub lon_v: i32,
137 pub projection_center_flag: u8,
138 pub latin1: i32,
139 pub latin2: i32,
140 pub lat_southern_pole: i32,
141 pub lon_southern_pole: i32,
142}
143
144#[derive(Debug, Clone, PartialEq)]
146pub struct PolarStereographicGrid {
147 pub core: ProjectedGridCore,
148 pub lat_d: i32,
149 pub lon_v: i32,
150 pub projection_center_flag: u8,
151}
152
153impl GridDefinition {
154 pub fn template_number(&self) -> u16 {
159 match self {
160 Self::LatLon(_) => 0,
161 Self::RotatedLatLon(_) => 1,
162 Self::Mercator(_) => 10,
163 Self::PolarStereographic(_) => 20,
164 Self::LambertConformal(_) => 30,
165 Self::AlbersEqualArea(_) => 31,
166 Self::RegularGaussian(_) => 40,
167 Self::Unsupported(template) => *template,
168 }
169 }
170
171 pub fn as_lat_lon(&self) -> Option<&LatLonGrid> {
172 match self {
173 Self::LatLon(grid) => Some(grid),
174 _ => None,
175 }
176 }
177
178 pub fn as_rotated_lat_lon(&self) -> Option<&RotatedLatLonGrid> {
179 match self {
180 Self::RotatedLatLon(grid) => Some(grid),
181 _ => None,
182 }
183 }
184
185 pub fn as_regular_gaussian(&self) -> Option<&RegularGaussianGrid> {
186 match self {
187 Self::RegularGaussian(grid) => Some(grid),
188 _ => None,
189 }
190 }
191
192 pub fn as_polar_stereographic(&self) -> Option<&PolarStereographicGrid> {
193 match self {
194 Self::PolarStereographic(grid) => Some(grid),
195 _ => None,
196 }
197 }
198
199 pub fn as_mercator(&self) -> Option<&MercatorGrid> {
200 match self {
201 Self::Mercator(grid) => Some(grid),
202 _ => None,
203 }
204 }
205
206 pub fn as_lambert_conformal(&self) -> Option<&LambertConformalGrid> {
207 match self {
208 Self::LambertConformal(grid) => Some(grid),
209 _ => None,
210 }
211 }
212
213 pub fn as_albers_equal_area(&self) -> Option<&AlbersEqualAreaGrid> {
214 match self {
215 Self::AlbersEqualArea(grid) => Some(grid),
216 _ => None,
217 }
218 }
219
220 pub fn projected_core(&self) -> Option<&ProjectedGridCore> {
221 match self {
222 Self::Mercator(grid) => Some(&grid.core),
223 Self::PolarStereographic(grid) => Some(&grid.core),
224 Self::LambertConformal(grid) => Some(&grid.core),
225 Self::AlbersEqualArea(grid) => Some(&grid.core),
226 Self::LatLon(_)
227 | Self::RotatedLatLon(_)
228 | Self::RegularGaussian(_)
229 | Self::Unsupported(_) => None,
230 }
231 }
232
233 pub fn unsupported_template(&self) -> Option<u16> {
234 match self {
235 Self::Unsupported(template) => Some(*template),
236 _ => None,
237 }
238 }
239
240 pub fn shape(&self) -> (usize, usize) {
241 match self {
242 Self::LatLon(g) => (g.ni as usize, g.nj as usize),
243 Self::RotatedLatLon(g) => (g.grid.ni as usize, g.grid.nj as usize),
244 Self::Mercator(g) => (g.core.nx as usize, g.core.ny as usize),
245 Self::PolarStereographic(g) => (g.core.nx as usize, g.core.ny as usize),
246 Self::LambertConformal(g) => (g.core.nx as usize, g.core.ny as usize),
247 Self::AlbersEqualArea(g) => (g.core.nx as usize, g.core.ny as usize),
248 Self::RegularGaussian(g) => (g.ni as usize, g.nj as usize),
249 Self::Unsupported(_) => (0, 0),
250 }
251 }
252
253 pub fn scanning_mode(&self) -> Option<u8> {
255 match self {
256 Self::LatLon(g) => Some(g.scanning_mode),
257 Self::RotatedLatLon(g) => Some(g.grid.scanning_mode),
258 Self::Mercator(g) => Some(g.core.scanning_mode),
259 Self::PolarStereographic(g) => Some(g.core.scanning_mode),
260 Self::LambertConformal(g) => Some(g.core.scanning_mode),
261 Self::AlbersEqualArea(g) => Some(g.core.scanning_mode),
262 Self::RegularGaussian(g) => Some(g.scanning_mode),
263 Self::Unsupported(_) => None,
264 }
265 }
266
267 pub fn scan_line_shape(&self) -> Result<(usize, usize)> {
272 let (ni, nj) = self.shape();
273 let scanning_mode = self
274 .scanning_mode()
275 .ok_or_else(|| Error::UnsupportedGridTemplate(self.template_number()))?;
276 if i_points_are_consecutive(scanning_mode) {
277 Ok((ni, nj))
278 } else {
279 Ok((nj, ni))
280 }
281 }
282
283 pub fn shape_num_points(&self) -> Result<usize> {
284 match self {
285 Self::LatLon(g) => checked_grid_point_count(g.ni, g.nj),
286 Self::RotatedLatLon(g) => checked_grid_point_count(g.grid.ni, g.grid.nj),
287 Self::Mercator(g) => checked_grid_point_count(g.core.nx, g.core.ny),
288 Self::PolarStereographic(g) => checked_grid_point_count(g.core.nx, g.core.ny),
289 Self::LambertConformal(g) => checked_grid_point_count(g.core.nx, g.core.ny),
290 Self::AlbersEqualArea(g) => checked_grid_point_count(g.core.nx, g.core.ny),
291 Self::RegularGaussian(g) => checked_grid_point_count(g.ni, g.nj),
292 Self::Unsupported(_) => Ok(0),
293 }
294 }
295
296 pub fn ndarray_shape(&self) -> Vec<usize> {
297 let (ni, nj) = self.shape();
298 match self {
299 Self::LatLon(_)
300 | Self::RotatedLatLon(_)
301 | Self::RegularGaussian(_)
302 | Self::Mercator(_)
303 | Self::PolarStereographic(_)
304 | Self::LambertConformal(_)
305 | Self::AlbersEqualArea(_)
306 if ni > 0 && nj > 0 =>
307 {
308 vec![nj, ni]
309 }
310 _ => Vec::new(),
311 }
312 }
313
314 pub fn num_points(&self) -> usize {
315 self.checked_num_points().unwrap_or(usize::MAX)
316 }
317
318 pub fn checked_num_points(&self) -> Result<usize> {
319 match self {
320 Self::LatLon(_) => self.shape_num_points(),
321 Self::RotatedLatLon(_) => self.shape_num_points(),
322 Self::Mercator(g) => Ok(g.core.number_of_points as usize),
323 Self::PolarStereographic(g) => Ok(g.core.number_of_points as usize),
324 Self::LambertConformal(g) => Ok(g.core.number_of_points as usize),
325 Self::AlbersEqualArea(g) => Ok(g.core.number_of_points as usize),
326 Self::RegularGaussian(_) => self.shape_num_points(),
327 Self::Unsupported(_) => Ok(0),
328 }
329 }
330
331 pub fn declared_num_points(&self) -> Option<usize> {
332 match self {
333 Self::Mercator(g) => Some(g.core.number_of_points as usize),
334 Self::PolarStereographic(g) => Some(g.core.number_of_points as usize),
335 Self::LambertConformal(g) => Some(g.core.number_of_points as usize),
336 Self::AlbersEqualArea(g) => Some(g.core.number_of_points as usize),
337 Self::LatLon(_)
338 | Self::RotatedLatLon(_)
339 | Self::RegularGaussian(_)
340 | Self::Unsupported(_) => None,
341 }
342 }
343
344 pub fn latitudes(&self) -> Result<Option<Vec<f64>>> {
346 self.latitudes_with_limit(None)
347 }
348
349 pub fn latitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Option<Vec<f64>>> {
350 match self {
351 Self::LatLon(grid) => Ok(Some(grid.latitudes_with_limit(max_axis_points)?)),
352 Self::RegularGaussian(grid) => Ok(Some(grid.latitudes_with_limit(max_axis_points)?)),
353 _ => Ok(None),
354 }
355 }
356
357 pub fn longitudes(&self) -> Result<Option<Vec<f64>>> {
359 self.longitudes_with_limit(None)
360 }
361
362 pub fn longitudes_with_limit(
363 &self,
364 max_axis_points: Option<usize>,
365 ) -> Result<Option<Vec<f64>>> {
366 match self {
367 Self::LatLon(grid) => Ok(Some(grid.longitudes_with_limit(max_axis_points)?)),
368 Self::RegularGaussian(grid) => Ok(Some(grid.longitudes_with_limit(max_axis_points)?)),
369 _ => Ok(None),
370 }
371 }
372
373 pub fn projected_x_coordinates(&self) -> Result<Option<Vec<f64>>> {
378 self.projected_x_coordinates_with_limit(None)
379 }
380
381 pub fn projected_x_coordinates_with_limit(
382 &self,
383 max_axis_points: Option<usize>,
384 ) -> Result<Option<Vec<f64>>> {
385 match self {
386 Self::Mercator(grid) => Ok(Some(grid.core.x_coordinates_with_limit(max_axis_points)?)),
387 Self::PolarStereographic(grid) => {
388 Ok(Some(grid.core.x_coordinates_with_limit(max_axis_points)?))
389 }
390 Self::LambertConformal(grid) => {
391 Ok(Some(grid.core.x_coordinates_with_limit(max_axis_points)?))
392 }
393 Self::AlbersEqualArea(grid) => {
394 Ok(Some(grid.core.x_coordinates_with_limit(max_axis_points)?))
395 }
396 _ => Ok(None),
397 }
398 }
399
400 pub fn projected_y_coordinates(&self) -> Result<Option<Vec<f64>>> {
405 self.projected_y_coordinates_with_limit(None)
406 }
407
408 pub fn projected_y_coordinates_with_limit(
409 &self,
410 max_axis_points: Option<usize>,
411 ) -> Result<Option<Vec<f64>>> {
412 match self {
413 Self::Mercator(grid) => Ok(Some(grid.core.y_coordinates_with_limit(max_axis_points)?)),
414 Self::PolarStereographic(grid) => {
415 Ok(Some(grid.core.y_coordinates_with_limit(max_axis_points)?))
416 }
417 Self::LambertConformal(grid) => {
418 Ok(Some(grid.core.y_coordinates_with_limit(max_axis_points)?))
419 }
420 Self::AlbersEqualArea(grid) => {
421 Ok(Some(grid.core.y_coordinates_with_limit(max_axis_points)?))
422 }
423 _ => Ok(None),
424 }
425 }
426
427 pub fn validate_supported_scan_order(&self) -> Result<()> {
428 match self {
429 Self::LatLon(grid) => grid.validate_supported_scan_order(),
430 Self::RotatedLatLon(grid) => grid.grid.validate_supported_scan_order(),
431 Self::Mercator(grid) => grid.core.validate_supported_scan_order(),
432 Self::PolarStereographic(grid) => grid.core.validate_supported_scan_order(),
433 Self::LambertConformal(grid) => grid.core.validate_supported_scan_order(),
434 Self::AlbersEqualArea(grid) => grid.core.validate_supported_scan_order(),
435 Self::RegularGaussian(grid) => grid.validate_supported_scan_order(),
436 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
437 }
438 }
439
440 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
443 match self {
444 Self::LatLon(grid) => grid.reorder_for_ndarray_in_place(values),
445 Self::RotatedLatLon(grid) => grid.grid.reorder_for_ndarray_in_place(values),
446 Self::Mercator(grid) => grid.core.reorder_for_ndarray_in_place(values),
447 Self::PolarStereographic(grid) => grid.core.reorder_for_ndarray_in_place(values),
448 Self::LambertConformal(grid) => grid.core.reorder_for_ndarray_in_place(values),
449 Self::AlbersEqualArea(grid) => grid.core.reorder_for_ndarray_in_place(values),
450 Self::RegularGaussian(grid) => transform_supported_scan_order_in_place(
451 values,
452 grid.ni as usize,
453 grid.nj as usize,
454 grid.scanning_mode,
455 ),
456 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
457 }
458 }
459
460 pub fn normalize_north_up_in_place<T>(&self, values: &mut [T]) -> Result<()> {
464 match self {
465 Self::LatLon(grid) => normalize_north_up_in_place(
466 values,
467 grid.ni as usize,
468 grid.nj as usize,
469 grid.scanning_mode,
470 ),
471 Self::RotatedLatLon(grid) => normalize_north_up_in_place(
472 values,
473 grid.grid.ni as usize,
474 grid.grid.nj as usize,
475 grid.grid.scanning_mode,
476 ),
477 Self::Mercator(grid) => normalize_north_up_in_place(
478 values,
479 grid.core.nx as usize,
480 grid.core.ny as usize,
481 grid.core.scanning_mode,
482 ),
483 Self::PolarStereographic(grid) => normalize_north_up_in_place(
484 values,
485 grid.core.nx as usize,
486 grid.core.ny as usize,
487 grid.core.scanning_mode,
488 ),
489 Self::LambertConformal(grid) => normalize_north_up_in_place(
490 values,
491 grid.core.nx as usize,
492 grid.core.ny as usize,
493 grid.core.scanning_mode,
494 ),
495 Self::AlbersEqualArea(grid) => normalize_north_up_in_place(
496 values,
497 grid.core.nx as usize,
498 grid.core.ny as usize,
499 grid.core.scanning_mode,
500 ),
501 Self::RegularGaussian(grid) => normalize_north_up_in_place(
502 values,
503 grid.ni as usize,
504 grid.nj as usize,
505 grid.scanning_mode,
506 ),
507 Self::Unsupported(template) => Err(Error::UnsupportedGridTemplate(*template)),
508 }
509 }
510
511 pub fn parse(section_bytes: &[u8]) -> Result<Self> {
512 if section_bytes.len() < 14 {
513 return Err(Error::InvalidSection {
514 section: 3,
515 reason: format!("expected at least 14 bytes, got {}", section_bytes.len()),
516 });
517 }
518 if section_bytes[4] != 3 {
519 return Err(Error::InvalidSection {
520 section: section_bytes[4],
521 reason: "not a grid definition section".into(),
522 });
523 }
524
525 let template = u16::from_be_bytes(section_bytes[12..14].try_into().unwrap());
526 match template {
527 0 => parse_latlon(section_bytes),
528 1 => parse_rotated_latlon(section_bytes),
529 10 => parse_mercator(section_bytes),
530 20 => parse_polar_stereographic(section_bytes),
531 30 => parse_lambert_conformal(section_bytes),
532 31 => parse_albers_equal_area(section_bytes),
533 40 => parse_regular_gaussian(section_bytes),
534 _ => Ok(Self::Unsupported(template)),
535 }
536 }
537}
538
539impl LatLonGrid {
540 pub fn longitudes(&self) -> Result<Vec<f64>> {
541 self.longitudes_with_limit(None)
542 }
543
544 pub fn longitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
545 let step = self.longitude_increment_degrees();
546 let signed_step = if self.i_scans_positive() { step } else { -step };
547 let start = self.lon_first as f64 / 1_000_000.0;
548 linear_axis(
549 self.ni,
550 start,
551 signed_step,
552 max_axis_points,
553 "longitude axis",
554 )
555 }
556
557 pub fn latitudes(&self) -> Result<Vec<f64>> {
558 self.latitudes_with_limit(None)
559 }
560
561 pub fn latitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
562 let step = self.latitude_increment_degrees();
563 let signed_step = if self.j_scans_positive() { step } else { -step };
564 let start = self.lat_first as f64 / 1_000_000.0;
565 linear_axis(
566 self.nj,
567 start,
568 signed_step,
569 max_axis_points,
570 "latitude axis",
571 )
572 }
573
574 pub fn reorder_for_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
575 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
576 Ok(values)
577 }
578
579 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
580 self.reorder_grib_scan_to_ndarray_in_place(values)
581 }
582
583 pub fn reorder_grib_scan_to_ndarray<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
584 self.reorder_grib_scan_to_ndarray_in_place(&mut values)?;
585 Ok(values)
586 }
587
588 pub fn reorder_grib_scan_to_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
589 transform_supported_scan_order_in_place(
590 values,
591 self.ni as usize,
592 self.nj as usize,
593 self.scanning_mode,
594 )
595 }
596
597 pub fn reorder_ndarray_to_grib_scan<T>(&self, mut values: Vec<T>) -> Result<Vec<T>> {
598 self.reorder_ndarray_to_grib_scan_in_place(&mut values)?;
599 Ok(values)
600 }
601
602 pub fn reorder_ndarray_to_grib_scan_in_place<T>(&self, values: &mut [T]) -> Result<()> {
603 transform_supported_scan_order_in_place(
604 values,
605 self.ni as usize,
606 self.nj as usize,
607 self.scanning_mode,
608 )
609 }
610
611 pub fn validate_supported_scan_order(&self) -> Result<()> {
612 validate_supported_scan_order(self.scanning_mode)
613 }
614
615 fn longitude_increment_degrees(&self) -> f64 {
618 if !increment_is_missing(self.di, self.ni) {
619 return f64::from(self.di) / 1_000_000.0;
620 }
621 if self.ni < 2 {
622 return 0.0;
623 }
624 let mut span = if self.i_scans_positive() {
625 i64::from(self.lon_last) - i64::from(self.lon_first)
626 } else {
627 i64::from(self.lon_first) - i64::from(self.lon_last)
628 };
629 if span <= 0 {
630 span += 360_000_000;
631 }
632 span as f64 / 1_000_000.0 / f64::from(self.ni - 1)
633 }
634
635 fn latitude_increment_degrees(&self) -> f64 {
638 if !increment_is_missing(self.dj, self.nj) {
639 return f64::from(self.dj) / 1_000_000.0;
640 }
641 if self.nj < 2 {
642 return 0.0;
643 }
644 let span = (i64::from(self.lat_last) - i64::from(self.lat_first)).unsigned_abs();
645 span as f64 / 1_000_000.0 / f64::from(self.nj - 1)
646 }
647
648 fn i_scans_positive(&self) -> bool {
649 i_scans_positive(self.scanning_mode)
650 }
651
652 fn j_scans_positive(&self) -> bool {
653 j_scans_positive(self.scanning_mode)
654 }
655}
656
657fn increment_is_missing(increment: u32, count: u32) -> bool {
660 increment == MISSING_INCREMENT || (increment == 0 && count > 1)
661}
662
663impl RotatedLatLonGrid {
664 pub fn rotated_longitudes(&self) -> Result<Vec<f64>> {
665 self.grid.longitudes()
666 }
667
668 pub fn rotated_longitudes_with_limit(
669 &self,
670 max_axis_points: Option<usize>,
671 ) -> Result<Vec<f64>> {
672 self.grid.longitudes_with_limit(max_axis_points)
673 }
674
675 pub fn rotated_latitudes(&self) -> Result<Vec<f64>> {
676 self.grid.latitudes()
677 }
678
679 pub fn rotated_latitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
680 self.grid.latitudes_with_limit(max_axis_points)
681 }
682}
683
684impl RegularGaussianGrid {
685 pub fn longitudes(&self) -> Result<Vec<f64>> {
686 self.longitudes_with_limit(None)
687 }
688
689 pub fn longitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
690 let step = self.longitude_increment_degrees()?;
691 let signed_step = if i_scans_positive(self.scanning_mode) {
692 step
693 } else {
694 -step
695 };
696 linear_axis(
697 self.ni,
698 f64::from(self.lon_first) / 1_000_000.0,
699 signed_step,
700 max_axis_points,
701 "Gaussian longitude axis",
702 )
703 }
704
705 pub fn latitudes(&self) -> Result<Vec<f64>> {
706 self.latitudes_with_limit(None)
707 }
708
709 pub fn latitudes_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
710 let requested = self.nj as usize;
711 ensure_limit("Gaussian latitude axis", requested, max_axis_points)?;
712 let all_latitudes = gaussian_latitudes(self.number_of_parallels, max_axis_points)?;
713 if requested > all_latitudes.len() {
714 return Err(Error::InvalidSection {
715 section: 3,
716 reason: format!(
717 "Gaussian grid requests {} rows but N={} defines only {} parallels",
718 self.nj,
719 self.number_of_parallels,
720 all_latitudes.len()
721 ),
722 });
723 }
724 if requested == 0 {
725 return Ok(Vec::new());
726 }
727
728 let first = f64::from(self.lat_first) / 1_000_000.0;
729 let first_index = all_latitudes
730 .iter()
731 .enumerate()
732 .min_by(|(_, left), (_, right)| {
733 (*left - first).abs().total_cmp(&(*right - first).abs())
734 })
735 .map(|(index, _)| index)
736 .ok_or_else(|| Error::InvalidSection {
737 section: 3,
738 reason: "Gaussian grid has no latitude parallels".into(),
739 })?;
740 if (all_latitudes[first_index] - first).abs() > 0.001 {
741 return Err(Error::InvalidSection {
742 section: 3,
743 reason: format!(
744 "first latitude {first} is not a Gaussian parallel for N={}",
745 self.number_of_parallels
746 ),
747 });
748 }
749
750 let ascending = j_scans_positive(self.scanning_mode);
751 let last_index = if ascending {
752 first_index.checked_sub(requested - 1)
753 } else {
754 first_index.checked_add(requested - 1)
755 }
756 .filter(|index| *index < all_latitudes.len())
757 .ok_or_else(|| Error::InvalidSection {
758 section: 3,
759 reason: "Gaussian latitude scan extends beyond the defined parallels".into(),
760 })?;
761
762 let mut result = Vec::new();
763 result.try_reserve_exact(requested).map_err(|error| {
764 Error::allocation("Gaussian latitude coordinates", requested, error)
765 })?;
766 if ascending {
767 result.extend(
768 (last_index..=first_index)
769 .rev()
770 .map(|index| all_latitudes[index]),
771 );
772 } else {
773 result.extend(all_latitudes[first_index..=last_index].iter().copied());
774 }
775
776 let declared_last = f64::from(self.lat_last) / 1_000_000.0;
777 if (result[requested - 1] - declared_last).abs() > 0.001 {
778 return Err(Error::InvalidSection {
779 section: 3,
780 reason: format!(
781 "last latitude {declared_last} does not match the Gaussian scan endpoint {}",
782 result[requested - 1]
783 ),
784 });
785 }
786 Ok(result)
787 }
788
789 pub fn validate_supported_scan_order(&self) -> Result<()> {
790 validate_supported_scan_order(self.scanning_mode)
791 }
792
793 fn longitude_increment_degrees(&self) -> Result<f64> {
794 if self.di != u32::MAX {
795 let increment = f64::from(self.di) / 1_000_000.0;
796 self.validate_longitude_endpoint(increment)?;
797 return Ok(increment);
798 }
799 if self.ni == 0 {
800 return Err(Error::InvalidSection {
801 section: 3,
802 reason: "Gaussian grid has missing Di and zero Ni".into(),
803 });
804 }
805 let inferred = 360.0 / f64::from(self.ni);
806 self.validate_longitude_endpoint(inferred)?;
807 Ok(inferred)
808 }
809
810 fn validate_longitude_endpoint(&self, increment: f64) -> Result<()> {
811 if self.ni == 0 {
812 return Err(Error::InvalidSection {
813 section: 3,
814 reason: "Gaussian grid has zero Ni".into(),
815 });
816 }
817 let expected_last = f64::from(self.lon_first) / 1_000_000.0
818 + if i_scans_positive(self.scanning_mode) {
819 increment * f64::from(self.ni - 1)
820 } else {
821 -increment * f64::from(self.ni - 1)
822 };
823 let declared_last = f64::from(self.lon_last) / 1_000_000.0;
824 let wrapped_difference = (expected_last - declared_last + 180.0).rem_euclid(360.0) - 180.0;
825 if wrapped_difference.abs() > 0.001 {
826 return Err(Error::InvalidSection {
827 section: 3,
828 reason: "Gaussian longitude endpoint does not match Ni, Di, and scanning mode"
829 .into(),
830 });
831 }
832 Ok(())
833 }
834}
835
836fn gaussian_latitudes(
837 number_of_parallels: u32,
838 max_axis_points: Option<usize>,
839) -> Result<Vec<f64>> {
840 if number_of_parallels == 0 {
841 return Err(Error::InvalidSection {
842 section: 3,
843 reason: "Gaussian grid N must be nonzero".into(),
844 });
845 }
846 let order = usize::try_from(u64::from(number_of_parallels) * 2)
847 .map_err(|_| Error::ValueOutOfRange("Gaussian parallel count does not fit usize".into()))?;
848 ensure_limit("Gaussian parallel calculation", order, max_axis_points)?;
849 let mut latitudes = filled_vec(order, 0.0, "Gaussian latitude coordinates")?;
850 let order_f64 = order as f64;
851
852 for index in 0..order / 2 {
853 let mut root = (std::f64::consts::PI * (index as f64 + 0.75) / (order_f64 + 0.5)).cos();
854 let mut converged = false;
855 for _ in 0..32 {
856 let (polynomial, previous) = legendre_pair(order, root);
857 let derivative = order_f64 * (root * polynomial - previous) / (root * root - 1.0);
858 let next = root - polynomial / derivative;
859 if (next - root).abs() <= 4.0 * f64::EPSILON * next.abs().max(1.0) {
860 root = next;
861 converged = true;
862 break;
863 }
864 root = next;
865 }
866 if !converged || !root.is_finite() {
867 return Err(Error::InvalidSection {
868 section: 3,
869 reason: format!(
870 "failed to compute Gaussian latitude root {index} for N={number_of_parallels}"
871 ),
872 });
873 }
874 let latitude = root.asin().to_degrees();
875 latitudes[index] = latitude;
876 latitudes[order - index - 1] = -latitude;
877 }
878 Ok(latitudes)
879}
880
881fn legendre_pair(order: usize, value: f64) -> (f64, f64) {
882 let mut previous = 0.0;
883 let mut current = 1.0;
884 for degree in 1..=order {
885 let older = previous;
886 previous = current;
887 current = ((2 * degree - 1) as f64 * value * previous - (degree - 1) as f64 * older)
888 / degree as f64;
889 }
890 (current, previous)
891}
892
893impl ProjectedGridCore {
894 pub fn x_coordinates(&self) -> Result<Vec<f64>> {
895 self.x_coordinates_with_limit(None)
896 }
897
898 pub fn x_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
899 projected_x_coordinates(self.nx, self.dx, self.scanning_mode, max_axis_points)
900 }
901
902 pub fn y_coordinates(&self) -> Result<Vec<f64>> {
903 self.y_coordinates_with_limit(None)
904 }
905
906 pub fn y_coordinates_with_limit(&self, max_axis_points: Option<usize>) -> Result<Vec<f64>> {
907 projected_y_coordinates(self.ny, self.dy, self.scanning_mode, max_axis_points)
908 }
909
910 pub fn reorder_for_ndarray_in_place<T>(&self, values: &mut [T]) -> Result<()> {
911 transform_supported_scan_order_in_place(
912 values,
913 self.nx as usize,
914 self.ny as usize,
915 self.scanning_mode,
916 )
917 }
918
919 pub fn validate_supported_scan_order(&self) -> Result<()> {
920 validate_supported_scan_order(self.scanning_mode)
921 }
922}
923
924fn transform_supported_scan_order_in_place<T>(
925 values: &mut [T],
926 ni: usize,
927 nj: usize,
928 scanning_mode: u8,
929) -> Result<()> {
930 validate_supported_scan_order(scanning_mode)?;
931 let expected = ni.checked_mul(nj).ok_or(Error::ArithmeticOverflow {
932 operation: "computing grid point count",
933 })?;
934 if values.len() != expected {
935 return Err(Error::DataLengthMismatch {
936 expected,
937 actual: values.len(),
938 });
939 }
940
941 if adjacent_rows_alternate_direction(scanning_mode) {
942 reverse_alternating_rows(values, ni, nj);
943 }
944
945 Ok(())
946}
947
948fn normalize_north_up_in_place<T>(
949 values: &mut [T],
950 ni: usize,
951 nj: usize,
952 scanning_mode: u8,
953) -> Result<()> {
954 transform_supported_scan_order_in_place(values, ni, nj, scanning_mode)?;
955 if ni == 0 {
956 return Ok(());
957 }
958
959 if !i_scans_positive(scanning_mode) {
960 for row in values.chunks_exact_mut(ni) {
961 row.reverse();
962 }
963 }
964 if j_scans_positive(scanning_mode) {
965 values.reverse();
968 for row in values.chunks_exact_mut(ni) {
969 row.reverse();
970 }
971 }
972
973 Ok(())
974}
975
976fn validate_supported_scan_order(scanning_mode: u8) -> Result<()> {
977 if i_points_are_consecutive(scanning_mode) {
978 Ok(())
979 } else {
980 Err(Error::UnsupportedScanningMode(scanning_mode))
981 }
982}
983
984fn i_scans_positive(scanning_mode: u8) -> bool {
985 scanning_mode & 0b1000_0000 == 0
986}
987
988fn j_scans_positive(scanning_mode: u8) -> bool {
989 scanning_mode & 0b0100_0000 != 0
990}
991
992fn i_points_are_consecutive(scanning_mode: u8) -> bool {
993 scanning_mode & 0b0010_0000 == 0
994}
995
996fn adjacent_rows_alternate_direction(scanning_mode: u8) -> bool {
997 scanning_mode & 0b0001_0000 != 0
998}
999
1000fn projected_x_coordinates(
1001 count: u32,
1002 spacing_millimetres: u32,
1003 scanning_mode: u8,
1004 max_axis_points: Option<usize>,
1005) -> Result<Vec<f64>> {
1006 projected_axis(
1007 count,
1008 spacing_millimetres,
1009 i_scans_positive(scanning_mode),
1010 max_axis_points,
1011 "projected x axis",
1012 )
1013}
1014
1015fn projected_y_coordinates(
1016 count: u32,
1017 spacing_millimetres: u32,
1018 scanning_mode: u8,
1019 max_axis_points: Option<usize>,
1020) -> Result<Vec<f64>> {
1021 projected_axis(
1022 count,
1023 spacing_millimetres,
1024 j_scans_positive(scanning_mode),
1025 max_axis_points,
1026 "projected y axis",
1027 )
1028}
1029
1030fn projected_axis(
1031 count: u32,
1032 spacing_millimetres: u32,
1033 scans_positive: bool,
1034 max_axis_points: Option<usize>,
1035 what: &'static str,
1036) -> Result<Vec<f64>> {
1037 let step = f64::from(spacing_millimetres) / 1_000.0;
1038 let signed_step = if scans_positive { step } else { -step };
1039 linear_axis(count, 0.0, signed_step, max_axis_points, what)
1040}
1041
1042fn linear_axis(
1043 count: u32,
1044 start: f64,
1045 signed_step: f64,
1046 max_axis_points: Option<usize>,
1047 what: &'static str,
1048) -> Result<Vec<f64>> {
1049 let count = count as usize;
1050 ensure_limit(what, count, max_axis_points)?;
1051 let mut axis = Vec::new();
1052 axis.try_reserve(count)
1053 .map_err(|error| Error::allocation(format!("{what} coordinates"), count, error))?;
1054 axis.extend((0..count).map(|index| start + signed_step * index as f64));
1055 Ok(axis)
1056}
1057
1058fn checked_grid_point_count(nx: u32, ny: u32) -> Result<usize> {
1059 let count = u64::from(nx) * u64::from(ny);
1060 usize::try_from(count).map_err(|_| {
1061 Error::ValueOutOfRange(format!("grid point count {count} does not fit in usize"))
1062 })
1063}
1064
1065fn reverse_alternating_rows<T>(values: &mut [T], ni: usize, nj: usize) {
1069 for row in (1..nj).step_by(2) {
1070 values[row * ni..(row + 1) * ni].reverse();
1071 }
1072}
1073
1074fn parse_latlon(data: &[u8]) -> Result<GridDefinition> {
1075 if data.len() < 72 {
1076 return Err(Error::InvalidSection {
1077 section: 3,
1078 reason: format!("template 3.0 requires 72 bytes, got {}", data.len()),
1079 });
1080 }
1081
1082 reject_quasi_regular_grid(data, 0)?;
1083 require_microdegree_angular_unit(data, 0)?;
1084 let grid = parse_latlon_fields(data);
1085 validate_geographic_coordinates(
1086 grid.lat_first,
1087 grid.lon_first,
1088 grid.lat_last,
1089 grid.lon_last,
1090 0,
1091 )?;
1092 Ok(GridDefinition::LatLon(grid))
1093}
1094
1095fn parse_rotated_latlon(data: &[u8]) -> Result<GridDefinition> {
1096 if data.len() < 84 {
1097 return Err(Error::InvalidSection {
1098 section: 3,
1099 reason: format!("template 3.1 requires 84 bytes, got {}", data.len()),
1100 });
1101 }
1102
1103 reject_quasi_regular_grid(data, 1)?;
1104 require_microdegree_angular_unit(data, 1)?;
1105 let grid = parse_latlon_fields(data);
1106 validate_geographic_coordinates(
1107 grid.lat_first,
1108 grid.lon_first,
1109 grid.lat_last,
1110 grid.lon_last,
1111 1,
1112 )?;
1113
1114 let lat_southern_pole = decode_wmo_i32(&data[72..76]).unwrap();
1115 let lon_southern_pole = u32::from_be_bytes(data[76..80].try_into().unwrap());
1116 if lat_southern_pole.unsigned_abs() > 90_000_000 || lon_southern_pole > 360_000_000 {
1117 return Err(Error::InvalidSection {
1118 section: 3,
1119 reason: "rotated-grid southern pole is outside geographic bounds".into(),
1120 });
1121 }
1122 let angle_of_rotation = f32::from_be_bytes(data[80..84].try_into().unwrap());
1123 if !angle_of_rotation.is_finite() {
1124 return Err(Error::InvalidSection {
1125 section: 3,
1126 reason: "rotated-grid angle of rotation must be finite".into(),
1127 });
1128 }
1129
1130 Ok(GridDefinition::RotatedLatLon(RotatedLatLonGrid {
1131 grid,
1132 lat_southern_pole,
1133 lon_southern_pole,
1134 angle_of_rotation,
1135 }))
1136}
1137
1138fn parse_regular_gaussian(data: &[u8]) -> Result<GridDefinition> {
1139 if data.len() < 72 {
1140 return Err(Error::InvalidSection {
1141 section: 3,
1142 reason: format!("template 3.40 requires 72 bytes, got {}", data.len()),
1143 });
1144 }
1145
1146 reject_quasi_regular_grid(data, 40)?;
1147 require_microdegree_angular_unit(data, 40)?;
1148 let grid = RegularGaussianGrid {
1149 ni: u32::from_be_bytes(data[30..34].try_into().unwrap()),
1150 nj: u32::from_be_bytes(data[34..38].try_into().unwrap()),
1151 lat_first: decode_wmo_i32(&data[46..50]).unwrap(),
1152 lon_first: decode_wmo_i32(&data[50..54]).unwrap(),
1153 lat_last: decode_wmo_i32(&data[55..59]).unwrap(),
1154 lon_last: decode_wmo_i32(&data[59..63]).unwrap(),
1155 di: u32::from_be_bytes(data[63..67].try_into().unwrap()),
1156 number_of_parallels: u32::from_be_bytes(data[67..71].try_into().unwrap()),
1157 scanning_mode: data[71],
1158 };
1159 validate_geographic_coordinates(
1160 grid.lat_first,
1161 grid.lon_first,
1162 grid.lat_last,
1163 grid.lon_last,
1164 40,
1165 )?;
1166 if grid.number_of_parallels == 0 {
1167 return Err(Error::InvalidSection {
1168 section: 3,
1169 reason: "template 3.40 number of parallels N must be nonzero".into(),
1170 });
1171 }
1172 let available_rows = u64::from(grid.number_of_parallels) * 2;
1173 if u64::from(grid.nj) > available_rows {
1174 return Err(Error::InvalidSection {
1175 section: 3,
1176 reason: format!(
1177 "template 3.40 Nj={} exceeds the {available_rows} parallels defined by N={}",
1178 grid.nj, grid.number_of_parallels
1179 ),
1180 });
1181 }
1182 Ok(GridDefinition::RegularGaussian(grid))
1183}
1184
1185fn parse_latlon_fields(data: &[u8]) -> LatLonGrid {
1186 LatLonGrid {
1187 ni: u32::from_be_bytes(data[30..34].try_into().unwrap()),
1188 nj: u32::from_be_bytes(data[34..38].try_into().unwrap()),
1189 lat_first: decode_wmo_i32(&data[46..50]).unwrap(),
1190 lon_first: decode_wmo_i32(&data[50..54]).unwrap(),
1191 lat_last: decode_wmo_i32(&data[55..59]).unwrap(),
1192 lon_last: decode_wmo_i32(&data[59..63]).unwrap(),
1193 di: u32::from_be_bytes(data[63..67].try_into().unwrap()),
1194 dj: u32::from_be_bytes(data[67..71].try_into().unwrap()),
1195 scanning_mode: data[71],
1196 }
1197}
1198
1199fn reject_quasi_regular_grid(data: &[u8], template: u16) -> Result<()> {
1200 let ni = u32::from_be_bytes(data[30..34].try_into().unwrap());
1201 let nj = u32::from_be_bytes(data[34..38].try_into().unwrap());
1202 if ni == u32::MAX || nj == u32::MAX || data[10] != 0 {
1203 return Err(Error::UnsupportedQuasiRegularGrid { template });
1204 }
1205 Ok(())
1206}
1207
1208fn require_microdegree_angular_unit(data: &[u8], template: u16) -> Result<()> {
1209 let encoded_basic_angle = u32::from_be_bytes(data[38..42].try_into().unwrap());
1210 let encoded_subdivisions = u32::from_be_bytes(data[42..46].try_into().unwrap());
1211 let basic_angle = match encoded_basic_angle {
1212 0 | u32::MAX => 1,
1213 value => value,
1214 };
1215 let subdivisions = match encoded_subdivisions {
1216 0 | u32::MAX => 1_000_000,
1217 value => value,
1218 };
1219 if u64::from(basic_angle) * 1_000_000 != u64::from(subdivisions) {
1220 return Err(Error::UnsupportedGridAngularUnit {
1221 template,
1222 basic_angle,
1223 subdivisions,
1224 });
1225 }
1226 Ok(())
1227}
1228
1229fn validate_geographic_coordinates(
1230 lat_first: i32,
1231 lon_first: i32,
1232 lat_last: i32,
1233 lon_last: i32,
1234 template: u16,
1235) -> Result<()> {
1236 if lat_first.unsigned_abs() > 90_000_000
1237 || lat_last.unsigned_abs() > 90_000_000
1238 || lon_first.unsigned_abs() > 360_000_000
1239 || lon_last.unsigned_abs() > 360_000_000
1240 {
1241 return Err(Error::InvalidSection {
1242 section: 3,
1243 reason: format!("template 3.{template} coordinates are outside geographic bounds"),
1244 });
1245 }
1246 Ok(())
1247}
1248
1249fn parse_mercator(data: &[u8]) -> Result<GridDefinition> {
1250 if data.len() < 72 {
1251 return Err(Error::InvalidSection {
1252 section: 3,
1253 reason: format!("template 3.10 requires 72 bytes, got {}", data.len()),
1254 });
1255 }
1256
1257 Ok(GridDefinition::Mercator(MercatorGrid {
1258 core: parse_projected_core(data, 64, 59),
1259 lat_d: decode_wmo_i32(&data[47..51]).unwrap(),
1260 lat_last: decode_wmo_i32(&data[51..55]).unwrap(),
1261 lon_last: decode_wmo_i32(&data[55..59]).unwrap(),
1262 orientation_of_grid: decode_wmo_i32(&data[60..64]).unwrap(),
1263 }))
1264}
1265
1266fn parse_polar_stereographic(data: &[u8]) -> Result<GridDefinition> {
1267 if data.len() < 65 {
1268 return Err(Error::InvalidSection {
1269 section: 3,
1270 reason: format!("template 3.20 requires 65 bytes, got {}", data.len()),
1271 });
1272 }
1273
1274 Ok(GridDefinition::PolarStereographic(PolarStereographicGrid {
1275 core: parse_projected_core(data, 55, 64),
1276 lat_d: decode_wmo_i32(&data[47..51]).unwrap(),
1277 lon_v: decode_wmo_i32(&data[51..55]).unwrap(),
1278 projection_center_flag: data[63],
1279 }))
1280}
1281
1282fn parse_albers_equal_area(data: &[u8]) -> Result<GridDefinition> {
1283 if data.len() < 81 {
1284 return Err(Error::InvalidSection {
1285 section: 3,
1286 reason: format!("template 3.31 requires 81 bytes, got {}", data.len()),
1287 });
1288 }
1289
1290 Ok(GridDefinition::AlbersEqualArea(AlbersEqualAreaGrid {
1291 core: parse_projected_core(data, 55, 64),
1292 lat_d: decode_wmo_i32(&data[47..51]).unwrap(),
1293 lon_v: decode_wmo_i32(&data[51..55]).unwrap(),
1294 projection_center_flag: data[63],
1295 latin1: decode_wmo_i32(&data[65..69]).unwrap(),
1296 latin2: decode_wmo_i32(&data[69..73]).unwrap(),
1297 lat_southern_pole: decode_wmo_i32(&data[73..77]).unwrap(),
1298 lon_southern_pole: decode_wmo_i32(&data[77..81]).unwrap(),
1299 }))
1300}
1301
1302fn parse_lambert_conformal(data: &[u8]) -> Result<GridDefinition> {
1303 if data.len() < 81 {
1304 return Err(Error::InvalidSection {
1305 section: 3,
1306 reason: format!("template 3.30 requires 81 bytes, got {}", data.len()),
1307 });
1308 }
1309
1310 Ok(GridDefinition::LambertConformal(LambertConformalGrid {
1311 core: parse_projected_core(data, 55, 64),
1312 lat_d: decode_wmo_i32(&data[47..51]).unwrap(),
1313 lon_v: decode_wmo_i32(&data[51..55]).unwrap(),
1314 projection_center_flag: data[63],
1315 latin1: decode_wmo_i32(&data[65..69]).unwrap(),
1316 latin2: decode_wmo_i32(&data[69..73]).unwrap(),
1317 lat_southern_pole: decode_wmo_i32(&data[73..77]).unwrap(),
1318 lon_southern_pole: decode_wmo_i32(&data[77..81]).unwrap(),
1319 }))
1320}
1321
1322fn parse_projected_core(
1323 data: &[u8],
1324 spacing_offset: usize,
1325 scanning_mode_offset: usize,
1326) -> ProjectedGridCore {
1327 ProjectedGridCore {
1328 number_of_points: u32::from_be_bytes(data[6..10].try_into().unwrap()),
1329 shape_of_earth: data[14],
1330 scale_factor_radius: data[15],
1331 scaled_value_radius: u32::from_be_bytes(data[16..20].try_into().unwrap()),
1332 scale_factor_major_axis: data[20],
1333 scaled_value_major_axis: u32::from_be_bytes(data[21..25].try_into().unwrap()),
1334 scale_factor_minor_axis: data[25],
1335 scaled_value_minor_axis: u32::from_be_bytes(data[26..30].try_into().unwrap()),
1336 nx: u32::from_be_bytes(data[30..34].try_into().unwrap()),
1337 ny: u32::from_be_bytes(data[34..38].try_into().unwrap()),
1338 lat_first: decode_wmo_i32(&data[38..42]).unwrap(),
1339 lon_first: decode_wmo_i32(&data[42..46]).unwrap(),
1340 resolution_and_component_flags: data[46],
1341 dx: u32::from_be_bytes(data[spacing_offset..spacing_offset + 4].try_into().unwrap()),
1342 dy: u32::from_be_bytes(
1343 data[spacing_offset + 4..spacing_offset + 8]
1344 .try_into()
1345 .unwrap(),
1346 ),
1347 scanning_mode: data[scanning_mode_offset],
1348 }
1349}
1350
1351#[cfg(test)]
1352mod tests {
1353 use super::{
1354 AlbersEqualAreaGrid, GridDefinition, LambertConformalGrid, LatLonGrid, MercatorGrid,
1355 PolarStereographicGrid, ProjectedGridCore, RegularGaussianGrid, MISSING_INCREMENT,
1356 };
1357 use crate::binary::encode_wmo_i32;
1358
1359 #[test]
1360 fn infers_missing_latlon_increments_from_endpoints() {
1361 let mut grid = LatLonGrid {
1362 ni: 5,
1363 nj: 3,
1364 lat_first: 10_000_000,
1365 lon_first: 350_000_000,
1366 lat_last: 0,
1367 lon_last: 10_000_000,
1368 di: MISSING_INCREMENT,
1369 dj: MISSING_INCREMENT,
1370 scanning_mode: 0,
1371 };
1372 assert_eq!(
1373 grid.longitudes().unwrap(),
1374 vec![350.0, 355.0, 360.0, 365.0, 370.0]
1375 );
1376 assert_eq!(grid.latitudes().unwrap(), vec![10.0, 5.0, 0.0]);
1377
1378 grid.scanning_mode = 0b1100_0000;
1379 grid.lon_first = 20_000_000;
1380 grid.lon_last = 0;
1381 grid.lat_first = 0;
1382 grid.lat_last = 10_000_000;
1383 grid.dj = 0;
1384 assert_eq!(grid.longitudes().unwrap(), vec![20.0, 15.0, 10.0, 5.0, 0.0]);
1385 assert_eq!(grid.latitudes().unwrap(), vec![0.0, 5.0, 10.0]);
1386 }
1387
1388 fn projected_core(nx: u32, ny: u32, dx: u32, dy: u32, scanning_mode: u8) -> ProjectedGridCore {
1389 ProjectedGridCore {
1390 number_of_points: nx * ny,
1391 shape_of_earth: 6,
1392 scale_factor_radius: 0,
1393 scaled_value_radius: 0,
1394 scale_factor_major_axis: 0,
1395 scaled_value_major_axis: 0,
1396 scale_factor_minor_axis: 0,
1397 scaled_value_minor_axis: 0,
1398 nx,
1399 ny,
1400 lat_first: 0,
1401 lon_first: 0,
1402 resolution_and_component_flags: 0,
1403 dx,
1404 dy,
1405 scanning_mode,
1406 }
1407 }
1408
1409 #[test]
1410 fn reports_latlon_shape() {
1411 let grid = GridDefinition::LatLon(LatLonGrid {
1412 ni: 3,
1413 nj: 2,
1414 lat_first: 50_000_000,
1415 lon_first: -120_000_000,
1416 lat_last: 49_000_000,
1417 lon_last: -118_000_000,
1418 di: 1_000_000,
1419 dj: 1_000_000,
1420 scanning_mode: 0,
1421 });
1422
1423 assert_eq!(grid.shape(), (3, 2));
1424 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1425 assert_eq!(grid.template_number(), 0);
1426 assert!(grid.as_lat_lon().is_some());
1427 assert!(grid.as_mercator().is_none());
1428 assert!(grid.as_polar_stereographic().is_none());
1429 assert!(grid.as_lambert_conformal().is_none());
1430 assert!(grid.as_albers_equal_area().is_none());
1431 assert_eq!(grid.unsupported_template(), None);
1432 match grid {
1433 GridDefinition::LatLon(ref latlon) => {
1434 assert_eq!(latlon.longitudes().unwrap(), vec![-120.0, -119.0, -118.0]);
1435 assert_eq!(latlon.latitudes().unwrap(), vec![50.0, 49.0]);
1436 }
1437 other => panic!("expected lat/lon grid, got {other:?}"),
1438 }
1439 }
1440
1441 #[test]
1442 fn parses_mercator_template() {
1443 let section = build_mercator_section(0);
1444 let grid = GridDefinition::parse(§ion).unwrap();
1445
1446 assert_eq!(grid.shape(), (3, 2));
1447 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1448 assert_eq!(grid.num_points(), 6);
1449 assert_eq!(grid.template_number(), 10);
1450 assert!(grid.as_lat_lon().is_none());
1451 assert!(grid.as_mercator().is_some());
1452 assert!(grid.as_lambert_conformal().is_none());
1453 assert_eq!(grid.unsupported_template(), None);
1454 match grid {
1455 GridDefinition::Mercator(mercator) => {
1456 assert_eq!(mercator.core.number_of_points, 6);
1457 assert_eq!(mercator.core.shape_of_earth, 6);
1458 assert_eq!(mercator.core.nx, 3);
1459 assert_eq!(mercator.core.ny, 2);
1460 assert_eq!(mercator.core.lat_first, -20_000_000);
1461 assert_eq!(mercator.core.lon_first, -100_000_000);
1462 assert_eq!(mercator.core.resolution_and_component_flags, 0x08);
1463 assert_eq!(mercator.lat_d, 0);
1464 assert_eq!(mercator.lat_last, 20_000_000);
1465 assert_eq!(mercator.lon_last, -98_000_000);
1466 assert_eq!(mercator.core.scanning_mode, 0);
1467 assert_eq!(mercator.orientation_of_grid, 0);
1468 assert_eq!(mercator.core.dx, 1_000_000);
1469 assert_eq!(mercator.core.dy, 2_000_000);
1470 }
1471 other => panic!("expected Mercator grid, got {other:?}"),
1472 }
1473 }
1474
1475 #[test]
1476 fn parses_rotated_latlon_template() {
1477 let section = build_rotated_latlon_section();
1478 let grid = GridDefinition::parse(§ion).unwrap();
1479
1480 assert_eq!(grid.shape(), (3, 2));
1481 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1482 assert_eq!(grid.template_number(), 1);
1483 assert!(grid.as_lat_lon().is_none());
1484 let rotated = grid.as_rotated_lat_lon().unwrap();
1485 assert_eq!(rotated.lat_southern_pole, -30_000_000);
1486 assert_eq!(rotated.lon_southern_pole, 10_000_000);
1487 assert_eq!(rotated.angle_of_rotation, 15.5);
1488 assert_eq!(
1489 rotated.rotated_longitudes().unwrap(),
1490 vec![-20.0, 0.0, 20.0]
1491 );
1492 assert_eq!(rotated.rotated_latitudes().unwrap(), vec![-10.0, 0.0]);
1493 assert_eq!(grid.latitudes().unwrap(), None);
1494 }
1495
1496 #[test]
1497 fn rejects_non_finite_rotated_grid_angle() {
1498 let mut section = build_rotated_latlon_section();
1499 section[80..84].copy_from_slice(&f32::NAN.to_be_bytes());
1500 assert!(matches!(
1501 GridDefinition::parse(§ion),
1502 Err(crate::Error::InvalidSection { section: 3, .. })
1503 ));
1504 }
1505
1506 #[test]
1507 fn parses_regular_gaussian_and_computes_latitudes() {
1508 let section = build_regular_gaussian_section(90_000_000);
1509 let grid = GridDefinition::parse(§ion).unwrap();
1510
1511 assert_eq!(grid.shape(), (4, 4));
1512 assert_eq!(grid.ndarray_shape(), vec![4, 4]);
1513 assert_eq!(grid.template_number(), 40);
1514 let gaussian = grid.as_regular_gaussian().unwrap();
1515 assert_eq!(
1516 gaussian,
1517 &RegularGaussianGrid {
1518 ni: 4,
1519 nj: 4,
1520 lat_first: 59_444_408,
1521 lon_first: 0,
1522 lat_last: -59_444_408,
1523 lon_last: 270_000_000,
1524 di: 90_000_000,
1525 number_of_parallels: 2,
1526 scanning_mode: 0,
1527 }
1528 );
1529 let latitudes = gaussian.latitudes().unwrap();
1530 let expected = [
1531 59.444_408_289_166_77,
1532 19.875_719_147_440_904,
1533 -19.875_719_147_440_904,
1534 -59.444_408_289_166_77,
1535 ];
1536 for (actual, expected) in latitudes.iter().zip(expected) {
1537 assert!((actual - expected).abs() < 1e-12);
1538 }
1539 assert_eq!(
1540 gaussian.longitudes().unwrap(),
1541 vec![0.0, 90.0, 180.0, 270.0]
1542 );
1543 assert_eq!(grid.latitudes().unwrap().unwrap(), latitudes);
1544 }
1545
1546 #[test]
1547 fn infers_missing_gaussian_increment_for_global_grid() {
1548 let section = build_regular_gaussian_section(u32::MAX);
1549 let GridDefinition::RegularGaussian(grid) = GridDefinition::parse(§ion).unwrap() else {
1550 panic!("expected regular Gaussian grid");
1551 };
1552 assert_eq!(grid.longitudes().unwrap(), vec![0.0, 90.0, 180.0, 270.0]);
1553 }
1554
1555 #[test]
1556 fn rejects_inconsistent_gaussian_longitude_endpoint() {
1557 let mut section = build_regular_gaussian_section(90_000_000);
1558 section[59..63].copy_from_slice(&encode_wmo_i32(180_000_000).unwrap());
1559 let GridDefinition::RegularGaussian(grid) = GridDefinition::parse(§ion).unwrap() else {
1560 panic!("expected regular Gaussian grid");
1561 };
1562 assert!(matches!(
1563 grid.longitudes(),
1564 Err(crate::Error::InvalidSection { section: 3, .. })
1565 ));
1566 }
1567
1568 #[test]
1569 fn rejects_quasi_regular_geographic_grids_with_typed_error() {
1570 for (template, mut section) in [
1571 (1, build_rotated_latlon_section()),
1572 (40, build_regular_gaussian_section(90_000_000)),
1573 ] {
1574 section[30..34].copy_from_slice(&u32::MAX.to_be_bytes());
1575 section[10] = 2;
1576 assert!(matches!(
1577 GridDefinition::parse(§ion),
1578 Err(crate::Error::UnsupportedQuasiRegularGrid { template: actual })
1579 if actual == template
1580 ));
1581 }
1582 }
1583
1584 #[test]
1585 fn rejects_non_microdegree_geographic_angle_units_explicitly() {
1586 let mut section = build_regular_gaussian_section(90_000_000);
1587 section[38..42].copy_from_slice(&1u32.to_be_bytes());
1588 section[42..46].copy_from_slice(&1_000u32.to_be_bytes());
1589 assert!(matches!(
1590 GridDefinition::parse(§ion),
1591 Err(crate::Error::UnsupportedGridAngularUnit {
1592 template: 40,
1593 basic_angle: 1,
1594 subdivisions: 1_000,
1595 })
1596 ));
1597 }
1598
1599 #[test]
1600 fn parses_polar_stereographic_template() {
1601 let section = build_polar_stereographic_section(0);
1602 let grid = GridDefinition::parse(§ion).unwrap();
1603
1604 assert_eq!(grid.shape(), (3, 2));
1605 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1606 assert_eq!(grid.num_points(), 6);
1607 assert_eq!(grid.template_number(), 20);
1608 assert!(grid.as_lat_lon().is_none());
1609 assert!(grid.as_mercator().is_none());
1610 assert!(grid.as_polar_stereographic().is_some());
1611 assert!(grid.as_lambert_conformal().is_none());
1612 assert!(grid.as_albers_equal_area().is_none());
1613 assert_eq!(grid.unsupported_template(), None);
1614 match grid {
1615 GridDefinition::PolarStereographic(polar) => {
1616 assert_eq!(polar.core.number_of_points, 6);
1617 assert_eq!(polar.core.shape_of_earth, 6);
1618 assert_eq!(polar.core.nx, 3);
1619 assert_eq!(polar.core.ny, 2);
1620 assert_eq!(polar.core.lat_first, 41_612_949);
1621 assert_eq!(polar.core.lon_first, 185_117_126);
1622 assert_eq!(polar.core.resolution_and_component_flags, 0x08);
1623 assert_eq!(polar.lat_d, 60_000_000);
1624 assert_eq!(polar.lon_v, 225_000_000);
1625 assert_eq!(polar.core.dx, 3_000_000);
1626 assert_eq!(polar.core.dy, 3_000_000);
1627 assert_eq!(polar.projection_center_flag, 0);
1628 assert_eq!(polar.core.scanning_mode, 0);
1629 }
1630 other => panic!("expected polar stereographic grid, got {other:?}"),
1631 }
1632 }
1633
1634 #[test]
1635 fn parses_albers_equal_area_template() {
1636 let section = build_albers_section();
1637 let grid = GridDefinition::parse(§ion).unwrap();
1638
1639 assert_eq!(grid.shape(), (3, 2));
1640 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1641 assert_eq!(grid.num_points(), 6);
1642 assert_eq!(grid.template_number(), 31);
1643 assert!(grid.as_lat_lon().is_none());
1644 assert!(grid.as_albers_equal_area().is_some());
1645 assert_eq!(grid.unsupported_template(), None);
1646 match grid {
1647 GridDefinition::AlbersEqualArea(albers) => {
1648 assert_eq!(albers.core.number_of_points, 6);
1649 assert_eq!(albers.core.shape_of_earth, 6);
1650 assert_eq!(albers.core.nx, 3);
1651 assert_eq!(albers.core.ny, 2);
1652 assert_eq!(albers.core.lat_first, 23_000_000);
1653 assert_eq!(albers.core.lon_first, 240_000_000);
1654 assert_eq!(albers.core.resolution_and_component_flags, 0x08);
1655 assert_eq!(albers.lat_d, 25_000_000);
1656 assert_eq!(albers.lon_v, 265_000_000);
1657 assert_eq!(albers.core.dx, 4_000_000);
1658 assert_eq!(albers.core.dy, 5_000_000);
1659 assert_eq!(albers.projection_center_flag, 0);
1660 assert_eq!(albers.core.scanning_mode, 0);
1661 assert_eq!(albers.latin1, 29_500_000);
1662 assert_eq!(albers.latin2, 45_500_000);
1663 assert_eq!(albers.lat_southern_pole, -90_000_000);
1664 assert_eq!(albers.lon_southern_pole, 0);
1665 }
1666 other => panic!("expected Albers equal-area grid, got {other:?}"),
1667 }
1668 }
1669
1670 #[test]
1671 fn parses_lambert_conformal_template() {
1672 let section = build_lambert_section();
1673 let grid = GridDefinition::parse(§ion).unwrap();
1674
1675 assert_eq!(grid.shape(), (3, 2));
1676 assert_eq!(grid.ndarray_shape(), vec![2, 3]);
1677 assert_eq!(grid.num_points(), 6);
1678 assert_eq!(grid.template_number(), 30);
1679 assert!(grid.as_lat_lon().is_none());
1680 assert!(grid.as_lambert_conformal().is_some());
1681 assert!(grid.as_albers_equal_area().is_none());
1682 assert_eq!(grid.unsupported_template(), None);
1683 match grid {
1684 GridDefinition::LambertConformal(lambert) => {
1685 assert_eq!(lambert.core.number_of_points, 6);
1686 assert_eq!(lambert.core.shape_of_earth, 1);
1687 assert_eq!(lambert.core.scaled_value_radius, 6_371_200);
1688 assert_eq!(lambert.core.nx, 3);
1689 assert_eq!(lambert.core.ny, 2);
1690 assert_eq!(lambert.core.lat_first, 12_190_000);
1691 assert_eq!(lambert.core.lon_first, 226_541_000);
1692 assert_eq!(lambert.core.resolution_and_component_flags, 0x08);
1693 assert_eq!(lambert.lat_d, 25_000_000);
1694 assert_eq!(lambert.lon_v, 265_000_000);
1695 assert_eq!(lambert.core.dx, 2_539_703);
1696 assert_eq!(lambert.core.dy, 2_539_703);
1697 assert_eq!(lambert.projection_center_flag, 0);
1698 assert_eq!(lambert.core.scanning_mode, 0);
1699 assert_eq!(lambert.latin1, 25_000_000);
1700 assert_eq!(lambert.latin2, 25_000_000);
1701 assert_eq!(lambert.lat_southern_pole, -90_000_000);
1702 assert_eq!(lambert.lon_southern_pole, 0);
1703 }
1704 other => panic!("expected Lambert conformal grid, got {other:?}"),
1705 }
1706 }
1707
1708 #[test]
1709 fn reports_unsupported_template_helpers() {
1710 let grid = GridDefinition::Unsupported(3_276);
1711
1712 assert_eq!(grid.template_number(), 3_276);
1713 assert!(grid.as_lat_lon().is_none());
1714 assert!(grid.as_mercator().is_none());
1715 assert!(grid.as_polar_stereographic().is_none());
1716 assert!(grid.as_lambert_conformal().is_none());
1717 assert!(grid.as_albers_equal_area().is_none());
1718 assert_eq!(grid.unsupported_template(), Some(3_276));
1719 }
1720
1721 #[test]
1722 fn normalizes_alternating_row_scan() {
1723 let grid = LatLonGrid {
1724 ni: 3,
1725 nj: 2,
1726 lat_first: 0,
1727 lon_first: 0,
1728 lat_last: 0,
1729 lon_last: 0,
1730 di: 1,
1731 dj: 1,
1732 scanning_mode: 0b0001_0000,
1733 };
1734
1735 let ordered = grid
1736 .reorder_for_ndarray(vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0])
1737 .unwrap();
1738 assert_eq!(ordered, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1739 }
1740
1741 #[test]
1742 fn converts_ndarray_order_to_alternating_scan_order() {
1743 let grid = LatLonGrid {
1744 ni: 3,
1745 nj: 2,
1746 lat_first: 0,
1747 lon_first: 0,
1748 lat_last: 0,
1749 lon_last: 0,
1750 di: 1,
1751 dj: 1,
1752 scanning_mode: 0b0001_0000,
1753 };
1754
1755 let scan_order = grid
1756 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
1757 .unwrap();
1758 assert_eq!(scan_order, vec![1, 2, 3, 6, 5, 4]);
1759 }
1760
1761 #[test]
1762 fn preserves_negative_i_direction_for_alternating_row_scan() {
1763 let grid = GridDefinition::LatLon(LatLonGrid {
1765 ni: 3,
1766 nj: 2,
1767 lat_first: 0,
1768 lon_first: 0,
1769 lat_last: 0,
1770 lon_last: 0,
1771 di: 1,
1772 dj: 1,
1773 scanning_mode: 0b1001_0000,
1774 });
1775
1776 let mut reader_order = vec![1, 2, 3, 6, 5, 4];
1777 grid.reorder_for_ndarray_in_place(&mut reader_order)
1778 .unwrap();
1779 assert_eq!(reader_order, vec![1, 2, 3, 4, 5, 6]);
1780
1781 let mut north_up = vec![1, 2, 3, 6, 5, 4];
1782 grid.normalize_north_up_in_place(&mut north_up).unwrap();
1783 assert_eq!(north_up, vec![3, 2, 1, 6, 5, 4]);
1784 }
1785
1786 #[test]
1787 fn normalizes_polar_stereographic_alternating_row_scan() {
1788 let grid = PolarStereographicGrid {
1789 core: projected_core(3, 2, 1, 1, 0b0001_0000),
1790 lat_d: 0,
1791 lon_v: 0,
1792 projection_center_flag: 0,
1793 };
1794
1795 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1796 grid.core.reorder_for_ndarray_in_place(&mut values).unwrap();
1797 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1798 }
1799
1800 #[test]
1801 fn normalizes_mercator_alternating_row_scan() {
1802 let grid = MercatorGrid {
1803 core: projected_core(3, 2, 1, 1, 0b0001_0000),
1804 lat_d: 0,
1805 lat_last: 0,
1806 lon_last: 0,
1807 orientation_of_grid: 0,
1808 };
1809
1810 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1811 grid.core.reorder_for_ndarray_in_place(&mut values).unwrap();
1812 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1813 }
1814
1815 #[test]
1816 fn normalizes_lambert_alternating_row_scan() {
1817 let grid = LambertConformalGrid {
1818 core: ProjectedGridCore {
1819 shape_of_earth: 1,
1820 scaled_value_radius: 6_371_200,
1821 ..projected_core(3, 2, 1, 1, 0b0001_0000)
1822 },
1823 lat_d: 0,
1824 lon_v: 0,
1825 projection_center_flag: 0,
1826 latin1: 0,
1827 latin2: 0,
1828 lat_southern_pole: 0,
1829 lon_southern_pole: 0,
1830 };
1831
1832 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1833 grid.core.reorder_for_ndarray_in_place(&mut values).unwrap();
1834 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1835 }
1836
1837 #[test]
1838 fn normalizes_albers_alternating_row_scan() {
1839 let grid = AlbersEqualAreaGrid {
1840 core: projected_core(3, 2, 1, 1, 0b0001_0000),
1841 lat_d: 0,
1842 lon_v: 0,
1843 projection_center_flag: 0,
1844 latin1: 0,
1845 latin2: 0,
1846 lat_southern_pole: 0,
1847 lon_southern_pole: 0,
1848 };
1849
1850 let mut values = vec![1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
1851 grid.core.reorder_for_ndarray_in_place(&mut values).unwrap();
1852 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1853 }
1854
1855 #[test]
1856 fn generates_projected_coordinate_offsets() {
1857 let mercator = GridDefinition::Mercator(MercatorGrid {
1858 core: projected_core(3, 2, 1_000_000, 2_000_000, 0),
1859 lat_d: 0,
1860 lat_last: 0,
1861 lon_last: 0,
1862 orientation_of_grid: 0,
1863 });
1864 assert_eq!(
1865 mercator.projected_x_coordinates().unwrap().unwrap(),
1866 vec![0.0, 1_000.0, 2_000.0]
1867 );
1868 assert_eq!(
1869 mercator.projected_y_coordinates().unwrap().unwrap(),
1870 vec![-0.0, -2_000.0]
1871 );
1872
1873 let polar = GridDefinition::PolarStereographic(PolarStereographicGrid {
1874 core: projected_core(3, 2, 3_000_000, 6_000_000, 0),
1875 lat_d: 0,
1876 lon_v: 0,
1877 projection_center_flag: 0,
1878 });
1879 assert_eq!(
1880 polar.projected_x_coordinates().unwrap().unwrap(),
1881 vec![0.0, 3_000.0, 6_000.0]
1882 );
1883 assert_eq!(
1884 polar.projected_y_coordinates().unwrap().unwrap(),
1885 vec![-0.0, -6_000.0]
1886 );
1887
1888 let lambert = GridDefinition::LambertConformal(LambertConformalGrid {
1889 core: ProjectedGridCore {
1890 shape_of_earth: 1,
1891 scaled_value_radius: 6_371_200,
1892 ..projected_core(3, 2, 2_539_703, 2_539_703, 0b1100_0000)
1893 },
1894 lat_d: 0,
1895 lon_v: 0,
1896 projection_center_flag: 0,
1897 latin1: 0,
1898 latin2: 0,
1899 lat_southern_pole: 0,
1900 lon_southern_pole: 0,
1901 });
1902 assert_eq!(
1903 lambert.projected_x_coordinates().unwrap().unwrap(),
1904 vec![-0.0, -2_539.703, -5_079.406]
1905 );
1906 assert_eq!(
1907 lambert.projected_y_coordinates().unwrap().unwrap(),
1908 vec![0.0, 2_539.703]
1909 );
1910
1911 let albers = GridDefinition::AlbersEqualArea(AlbersEqualAreaGrid {
1912 core: projected_core(3, 2, 4_000_000, 5_000_000, 0b1100_0000),
1913 lat_d: 0,
1914 lon_v: 0,
1915 projection_center_flag: 0,
1916 latin1: 0,
1917 latin2: 0,
1918 lat_southern_pole: 0,
1919 lon_southern_pole: 0,
1920 });
1921 assert_eq!(
1922 albers.projected_x_coordinates().unwrap().unwrap(),
1923 vec![-0.0, -4_000.0, -8_000.0]
1924 );
1925 assert_eq!(
1926 albers.projected_y_coordinates().unwrap().unwrap(),
1927 vec![0.0, 5_000.0]
1928 );
1929 }
1930
1931 #[test]
1932 fn preserves_non_alternating_scan_modes_in_current_reader_order() {
1933 for scanning_mode in [0b0000_0000, 0b1000_0000, 0b0100_0000, 0b1100_0000] {
1934 let grid = LatLonGrid {
1935 ni: 3,
1936 nj: 2,
1937 lat_first: 0,
1938 lon_first: 0,
1939 lat_last: 0,
1940 lon_last: 0,
1941 di: 1,
1942 dj: 1,
1943 scanning_mode,
1944 };
1945
1946 let values = vec![1, 2, 3, 4, 5, 6];
1947 assert_eq!(
1948 grid.reorder_grib_scan_to_ndarray(values.clone()).unwrap(),
1949 values
1950 );
1951 assert_eq!(
1952 grid.reorder_ndarray_to_grib_scan(values.clone()).unwrap(),
1953 values
1954 );
1955 }
1956 }
1957
1958 #[test]
1959 fn normalizes_scan_directions_to_north_up_west_first_order() {
1960 let grid = GridDefinition::LatLon(LatLonGrid {
1961 ni: 3,
1962 nj: 2,
1963 lat_first: 0,
1964 lon_first: 0,
1965 lat_last: 0,
1966 lon_last: 0,
1967 di: 1,
1968 dj: 1,
1969 scanning_mode: 0b1100_0000,
1970 });
1971 let mut values = vec![1, 2, 3, 4, 5, 6];
1972
1973 grid.normalize_north_up_in_place(&mut values).unwrap();
1974
1975 assert_eq!(values, vec![6, 5, 4, 3, 2, 1]);
1976 }
1977
1978 #[test]
1979 fn rejects_j_consecutive_scan_order() {
1980 let grid = LatLonGrid {
1981 ni: 3,
1982 nj: 2,
1983 lat_first: 0,
1984 lon_first: 0,
1985 lat_last: 0,
1986 lon_last: 0,
1987 di: 1,
1988 dj: 1,
1989 scanning_mode: 0b0010_0000,
1990 };
1991
1992 let err = grid
1993 .reorder_ndarray_to_grib_scan(vec![1, 2, 3, 4, 5, 6])
1994 .unwrap_err();
1995 assert!(matches!(
1996 err,
1997 crate::Error::UnsupportedScanningMode(0b0010_0000)
1998 ));
1999 }
2000
2001 fn build_polar_stereographic_section(scanning_mode: u8) -> Vec<u8> {
2002 let mut section = vec![0u8; 65];
2003 section[..4].copy_from_slice(&65u32.to_be_bytes());
2004 section[4] = 3;
2005 section[6..10].copy_from_slice(&6u32.to_be_bytes());
2006 section[12..14].copy_from_slice(&20u16.to_be_bytes());
2007 section[14] = 6;
2008 section[30..34].copy_from_slice(&3u32.to_be_bytes());
2009 section[34..38].copy_from_slice(&2u32.to_be_bytes());
2010 section[38..42].copy_from_slice(&encode_wmo_i32(41_612_949).unwrap());
2011 section[42..46].copy_from_slice(&185_117_126u32.to_be_bytes());
2012 section[46] = 0x08;
2013 section[47..51].copy_from_slice(&encode_wmo_i32(60_000_000).unwrap());
2014 section[51..55].copy_from_slice(&225_000_000u32.to_be_bytes());
2015 section[55..59].copy_from_slice(&3_000_000u32.to_be_bytes());
2016 section[59..63].copy_from_slice(&3_000_000u32.to_be_bytes());
2017 section[64] = scanning_mode;
2018 section
2019 }
2020
2021 fn build_rotated_latlon_section() -> Vec<u8> {
2022 let mut section = vec![0u8; 84];
2023 section[..4].copy_from_slice(&84u32.to_be_bytes());
2024 section[4] = 3;
2025 section[6..10].copy_from_slice(&6u32.to_be_bytes());
2026 section[12..14].copy_from_slice(&1u16.to_be_bytes());
2027 section[30..34].copy_from_slice(&3u32.to_be_bytes());
2028 section[34..38].copy_from_slice(&2u32.to_be_bytes());
2029 section[46..50].copy_from_slice(&encode_wmo_i32(-10_000_000).unwrap());
2030 section[50..54].copy_from_slice(&encode_wmo_i32(-20_000_000).unwrap());
2031 section[55..59].copy_from_slice(&encode_wmo_i32(0).unwrap());
2032 section[59..63].copy_from_slice(&encode_wmo_i32(20_000_000).unwrap());
2033 section[63..67].copy_from_slice(&20_000_000u32.to_be_bytes());
2034 section[67..71].copy_from_slice(&10_000_000u32.to_be_bytes());
2035 section[71] = 0b0100_0000;
2036 section[72..76].copy_from_slice(&encode_wmo_i32(-30_000_000).unwrap());
2037 section[76..80].copy_from_slice(&10_000_000u32.to_be_bytes());
2038 section[80..84].copy_from_slice(&15.5f32.to_be_bytes());
2039 section
2040 }
2041
2042 fn build_regular_gaussian_section(di: u32) -> Vec<u8> {
2043 let mut section = vec![0u8; 72];
2044 section[..4].copy_from_slice(&72u32.to_be_bytes());
2045 section[4] = 3;
2046 section[6..10].copy_from_slice(&16u32.to_be_bytes());
2047 section[12..14].copy_from_slice(&40u16.to_be_bytes());
2048 section[30..34].copy_from_slice(&4u32.to_be_bytes());
2049 section[34..38].copy_from_slice(&4u32.to_be_bytes());
2050 section[46..50].copy_from_slice(&encode_wmo_i32(59_444_408).unwrap());
2051 section[50..54].copy_from_slice(&encode_wmo_i32(0).unwrap());
2052 section[55..59].copy_from_slice(&encode_wmo_i32(-59_444_408).unwrap());
2053 section[59..63].copy_from_slice(&encode_wmo_i32(270_000_000).unwrap());
2054 section[63..67].copy_from_slice(&di.to_be_bytes());
2055 section[67..71].copy_from_slice(&2u32.to_be_bytes());
2056 section
2057 }
2058
2059 fn build_mercator_section(scanning_mode: u8) -> Vec<u8> {
2060 let mut section = vec![0u8; 72];
2061 section[..4].copy_from_slice(&72u32.to_be_bytes());
2062 section[4] = 3;
2063 section[6..10].copy_from_slice(&6u32.to_be_bytes());
2064 section[12..14].copy_from_slice(&10u16.to_be_bytes());
2065 section[14] = 6;
2066 section[30..34].copy_from_slice(&3u32.to_be_bytes());
2067 section[34..38].copy_from_slice(&2u32.to_be_bytes());
2068 section[38..42].copy_from_slice(&encode_wmo_i32(-20_000_000).unwrap());
2069 section[42..46].copy_from_slice(&encode_wmo_i32(-100_000_000).unwrap());
2070 section[46] = 0x08;
2071 section[47..51].copy_from_slice(&encode_wmo_i32(0).unwrap());
2072 section[51..55].copy_from_slice(&encode_wmo_i32(20_000_000).unwrap());
2073 section[55..59].copy_from_slice(&encode_wmo_i32(-98_000_000).unwrap());
2074 section[59] = scanning_mode;
2075 section[64..68].copy_from_slice(&1_000_000u32.to_be_bytes());
2076 section[68..72].copy_from_slice(&2_000_000u32.to_be_bytes());
2077 section
2078 }
2079
2080 fn build_lambert_section() -> Vec<u8> {
2081 let mut section = vec![0u8; 81];
2082 section[..4].copy_from_slice(&81u32.to_be_bytes());
2083 section[4] = 3;
2084 section[6..10].copy_from_slice(&6u32.to_be_bytes());
2085 section[12..14].copy_from_slice(&30u16.to_be_bytes());
2086 section[14] = 1;
2087 section[16..20].copy_from_slice(&6_371_200u32.to_be_bytes());
2088 section[30..34].copy_from_slice(&3u32.to_be_bytes());
2089 section[34..38].copy_from_slice(&2u32.to_be_bytes());
2090 section[38..42].copy_from_slice(&encode_wmo_i32(12_190_000).unwrap());
2091 section[42..46].copy_from_slice(&226_541_000u32.to_be_bytes());
2092 section[46] = 0x08;
2093 section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
2094 section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
2095 section[55..59].copy_from_slice(&2_539_703u32.to_be_bytes());
2096 section[59..63].copy_from_slice(&2_539_703u32.to_be_bytes());
2097 section[65..69].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
2098 section[69..73].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
2099 section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
2100 section
2101 }
2102
2103 fn build_albers_section() -> Vec<u8> {
2104 let mut section = vec![0u8; 81];
2105 section[..4].copy_from_slice(&81u32.to_be_bytes());
2106 section[4] = 3;
2107 section[6..10].copy_from_slice(&6u32.to_be_bytes());
2108 section[12..14].copy_from_slice(&31u16.to_be_bytes());
2109 section[14] = 6;
2110 section[30..34].copy_from_slice(&3u32.to_be_bytes());
2111 section[34..38].copy_from_slice(&2u32.to_be_bytes());
2112 section[38..42].copy_from_slice(&encode_wmo_i32(23_000_000).unwrap());
2113 section[42..46].copy_from_slice(&240_000_000u32.to_be_bytes());
2114 section[46] = 0x08;
2115 section[47..51].copy_from_slice(&encode_wmo_i32(25_000_000).unwrap());
2116 section[51..55].copy_from_slice(&265_000_000u32.to_be_bytes());
2117 section[55..59].copy_from_slice(&4_000_000u32.to_be_bytes());
2118 section[59..63].copy_from_slice(&5_000_000u32.to_be_bytes());
2119 section[65..69].copy_from_slice(&encode_wmo_i32(29_500_000).unwrap());
2120 section[69..73].copy_from_slice(&encode_wmo_i32(45_500_000).unwrap());
2121 section[73..77].copy_from_slice(&encode_wmo_i32(-90_000_000).unwrap());
2122 section
2123 }
2124}