1#![forbid(unsafe_code)]
4
5use std::io::Write;
6
7use grib_core::binary::{
8 encode_ibm_f32, encode_wmo_i16, encode_wmo_i24, encode_wmo_i32, encode_wmo_i8, write_u16_be,
9 write_u24_be, write_u32_be, write_u64_be, write_u8_be, U24_MAX,
10};
11use grib_core::bit::BitWriter;
12use grib_core::{
13 AlbersEqualAreaGrid, AnalysisOrForecastTemplate, ComplexPackingParams, DataRepresentation,
14 FixedSurface, GridDefinition, Identification, ImagePackingParams, Jpeg2000PackingParams,
15 LambertConformalGrid, LatLonGrid, MercatorGrid, PngPackingParams, PolarStereographicGrid,
16 ProductDefinition, ProductDefinitionTemplate, ReferenceTime, SimplePackingParams,
17 SpatialDifferencingParams, StatisticalTimeRange,
18};
19
20pub use grib_core::grib1::ProductDefinition as Grib1ProductDefinition;
21pub use grib_core::{Error, Result};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum PackingStrategy {
26 SimpleAuto { decimal_scale: i16 },
28 ComplexAuto {
30 decimal_scale: i16,
31 spatial_differencing: Option<SpatialDifferencingOrder>,
32 },
33 Jpeg2000Auto { decimal_scale: i16 },
35 PngAuto { decimal_scale: i16 },
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum SpatialDifferencingOrder {
42 First,
44 Second,
46}
47
48const COMPLEX_AUTO_GROUP_LEN: usize = 32;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum ValueOrder {
53 #[default]
55 LogicalRowMajor,
56 GribScanOrder,
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct Grib1FieldBuilder {
63 product: Option<Grib1ProductDefinition>,
64 grid: Option<GridDefinition>,
65 packing: Option<PackingStrategy>,
66 values: Option<Vec<f64>>,
67 bitmap: Option<Grib1BitmapDefinition>,
68 value_order: ValueOrder,
69}
70
71impl Grib1FieldBuilder {
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn product(mut self, product: Grib1ProductDefinition) -> Self {
77 self.product = Some(product);
78 self
79 }
80
81 pub fn grid(mut self, grid: GridDefinition) -> Self {
82 self.grid = Some(grid);
83 self
84 }
85
86 pub fn packing(mut self, packing: PackingStrategy) -> Self {
87 self.packing = Some(packing);
88 self
89 }
90
91 pub fn values<T>(mut self, values: &[T]) -> Self
92 where
93 T: Copy + Into<f64>,
94 {
95 self.values = Some(values.iter().copied().map(Into::into).collect());
96 self
97 }
98
99 pub fn bitmap(mut self, bitmap: &[bool]) -> Self {
100 self.bitmap = Some(Grib1BitmapDefinition {
101 present: bitmap.to_vec(),
102 table_reference: None,
103 });
104 self
105 }
106
107 pub fn predefined_bitmap(mut self, table_reference: u16, bitmap: &[bool]) -> Self {
112 self.bitmap = Some(Grib1BitmapDefinition {
113 present: bitmap.to_vec(),
114 table_reference: Some(table_reference),
115 });
116 self
117 }
118
119 pub fn value_order(mut self, value_order: ValueOrder) -> Self {
120 self.value_order = value_order;
121 self
122 }
123
124 pub fn build(self) -> Result<Grib1Field> {
125 let mut product = self
126 .product
127 .ok_or_else(|| Error::Other("missing GRIB1 product definition".into()))?;
128 let grid = self
129 .grid
130 .ok_or_else(|| Error::Other("missing GRIB1 grid definition".into()))?;
131 let packing = self
132 .packing
133 .ok_or_else(|| Error::Other("missing GRIB1 packing strategy".into()))?;
134 let mut values = self
135 .values
136 .ok_or_else(|| Error::Other("missing GRIB1 field values".into()))?;
137 let mut bitmap = self.bitmap;
138
139 validate_supported_grib1_grid(&grid)?;
140
141 let expected = checked_grid_point_count(&grid)?;
142 if values.len() != expected {
143 return Err(Error::DataLengthMismatch {
144 expected,
145 actual: values.len(),
146 });
147 }
148 if let Some(bitmap) = &bitmap {
149 if bitmap.present.len() != expected {
150 return Err(Error::DataLengthMismatch {
151 expected,
152 actual: bitmap.present.len(),
153 });
154 }
155 if bitmap.table_reference == Some(0) {
156 return Err(Error::Other(
157 "GRIB1 predefined bitmap table reference must be nonzero".into(),
158 ));
159 }
160 }
161
162 if self.value_order == ValueOrder::LogicalRowMajor {
163 reorder_field_to_grib_scan_order(
164 &grid,
165 &mut values,
166 bitmap.as_mut().map(|bitmap| bitmap.present.as_mut_slice()),
167 )?;
168 }
169
170 let bitmap_mask = bitmap.as_ref().map(|bitmap| bitmap.present.as_slice());
171 let predefined_bitmap_reference = bitmap.as_ref().and_then(|bitmap| bitmap.table_reference);
172
173 let mut packed = match packing {
174 PackingStrategy::SimpleAuto { decimal_scale } => {
175 product.decimal_scale = decimal_scale;
176 pack_simple_auto(&values, bitmap_mask, decimal_scale)?
177 }
178 PackingStrategy::ComplexAuto { .. } => {
179 return Err(Error::Other(
180 "GRIB1 writer does not support complex packing".into(),
181 ));
182 }
183 PackingStrategy::Jpeg2000Auto { .. } => {
184 return Err(Error::Other(
185 "GRIB1 writer does not support JPEG2000 packing".into(),
186 ));
187 }
188 PackingStrategy::PngAuto { .. } => {
189 return Err(Error::Other(
190 "GRIB1 writer does not support PNG packing".into(),
191 ));
192 }
193 };
194 if predefined_bitmap_reference.is_some() {
195 packed.bitmap_payload = None;
196 }
197 product.has_grid_definition = true;
198 product.has_bitmap =
199 packed.bitmap_payload.is_some() || predefined_bitmap_reference.is_some();
200
201 Ok(Grib1Field {
202 product,
203 grid,
204 packed,
205 predefined_bitmap_reference,
206 })
207 }
208}
209
210#[derive(Debug, Clone)]
211struct Grib1BitmapDefinition {
212 present: Vec<bool>,
213 table_reference: Option<u16>,
214}
215
216#[derive(Debug, Clone)]
218pub struct Grib1Field {
219 product: Grib1ProductDefinition,
220 grid: GridDefinition,
221 packed: PackedField,
222 predefined_bitmap_reference: Option<u16>,
223}
224
225impl Grib1Field {
226 pub fn product(&self) -> &Grib1ProductDefinition {
227 &self.product
228 }
229
230 pub fn grid(&self) -> &GridDefinition {
231 &self.grid
232 }
233
234 pub fn data_representation(&self) -> &DataRepresentation {
235 &self.packed.representation
236 }
237}
238
239#[derive(Debug, Clone, Default)]
241pub struct Grib2FieldBuilder {
242 discipline: u8,
243 identification: Option<Identification>,
244 grid: Option<GridDefinition>,
245 product: Option<ProductDefinition>,
246 packing: Option<PackingStrategy>,
247 values: Option<Vec<f64>>,
248 bitmap: Option<Vec<bool>>,
249 value_order: ValueOrder,
250}
251
252impl Grib2FieldBuilder {
253 pub fn new() -> Self {
254 Self::default()
255 }
256
257 pub fn discipline(mut self, discipline: u8) -> Self {
258 self.discipline = discipline;
259 self
260 }
261
262 pub fn identification(mut self, identification: Identification) -> Self {
263 self.identification = Some(identification);
264 self
265 }
266
267 pub fn grid(mut self, grid: GridDefinition) -> Self {
268 self.grid = Some(grid);
269 self
270 }
271
272 pub fn product(mut self, product: ProductDefinition) -> Self {
273 self.product = Some(product);
274 self
275 }
276
277 pub fn packing(mut self, packing: PackingStrategy) -> Self {
278 self.packing = Some(packing);
279 self
280 }
281
282 pub fn values<T>(mut self, values: &[T]) -> Self
283 where
284 T: Copy + Into<f64>,
285 {
286 self.values = Some(values.iter().copied().map(Into::into).collect());
287 self
288 }
289
290 pub fn bitmap(mut self, bitmap: &[bool]) -> Self {
291 self.bitmap = Some(bitmap.to_vec());
292 self
293 }
294
295 pub fn value_order(mut self, value_order: ValueOrder) -> Self {
296 self.value_order = value_order;
297 self
298 }
299
300 pub fn build(self) -> Result<Grib2Field> {
301 let identification = self
302 .identification
303 .ok_or_else(|| Error::Other("missing GRIB2 identification".into()))?;
304 let grid = self
305 .grid
306 .ok_or_else(|| Error::Other("missing GRIB2 grid definition".into()))?;
307 let product = self
308 .product
309 .ok_or_else(|| Error::Other("missing GRIB2 product definition".into()))?;
310 let packing = self
311 .packing
312 .ok_or_else(|| Error::Other("missing GRIB2 packing strategy".into()))?;
313 let mut values = self
314 .values
315 .ok_or_else(|| Error::Other("missing GRIB2 field values".into()))?;
316 let mut bitmap = self.bitmap;
317
318 validate_supported_grid(&grid)?;
319 validate_supported_product(&product)?;
320
321 let expected = checked_grid_point_count(&grid)?;
322 if values.len() != expected {
323 return Err(Error::DataLengthMismatch {
324 expected,
325 actual: values.len(),
326 });
327 }
328 if let Some(bitmap) = &bitmap {
329 if bitmap.len() != expected {
330 return Err(Error::DataLengthMismatch {
331 expected,
332 actual: bitmap.len(),
333 });
334 }
335 }
336
337 if self.value_order == ValueOrder::LogicalRowMajor {
338 reorder_field_to_grib_scan_order(&grid, &mut values, bitmap.as_deref_mut())?;
339 }
340
341 let packed = match packing {
342 PackingStrategy::SimpleAuto { decimal_scale } => {
343 pack_simple_auto(&values, bitmap.as_deref(), decimal_scale)?
344 }
345 PackingStrategy::ComplexAuto {
346 decimal_scale,
347 spatial_differencing,
348 } => pack_complex_auto(
349 &values,
350 bitmap.as_deref(),
351 decimal_scale,
352 spatial_differencing,
353 )?,
354 PackingStrategy::Jpeg2000Auto { decimal_scale } => {
355 pack_jpeg2000_auto(&values, bitmap.as_deref(), &grid, decimal_scale)?
356 }
357 PackingStrategy::PngAuto { decimal_scale } => {
358 pack_png_auto(&values, bitmap.as_deref(), &grid, decimal_scale)?
359 }
360 };
361
362 Ok(Grib2Field {
363 discipline: self.discipline,
364 identification,
365 grid,
366 product,
367 packed,
368 })
369 }
370}
371
372#[derive(Debug, Clone)]
374pub struct Grib2Field {
375 discipline: u8,
376 identification: Identification,
377 grid: GridDefinition,
378 product: ProductDefinition,
379 packed: PackedField,
380}
381
382impl Grib2Field {
383 pub fn discipline(&self) -> u8 {
384 self.discipline
385 }
386
387 pub fn identification(&self) -> &Identification {
388 &self.identification
389 }
390
391 pub fn grid(&self) -> &GridDefinition {
392 &self.grid
393 }
394
395 pub fn product(&self) -> &ProductDefinition {
396 &self.product
397 }
398
399 pub fn data_representation(&self) -> &DataRepresentation {
400 &self.packed.representation
401 }
402}
403
404pub struct GribWriter<'a, W> {
406 out: &'a mut W,
407}
408
409impl<'a, W: Write> GribWriter<'a, W> {
410 pub fn new(out: &'a mut W) -> Self {
411 Self { out }
412 }
413
414 pub fn write_grib1_message(&mut self, field: Grib1Field) -> Result<()> {
415 let mut body = Vec::new();
416 write_grib1_product_section(&mut body, &field.product)?;
417 write_grib1_grid_section(&mut body, &field.grid)?;
418 if let Some(table_reference) = field.predefined_bitmap_reference {
419 write_grib1_predefined_bitmap_section(&mut body, table_reference)?;
420 } else if let Some(bitmap) = &field.packed.bitmap_payload {
421 write_grib1_bitmap_section(&mut body, bitmap, field.grid.num_points())?;
422 }
423 write_grib1_data_section(&mut body, &field.packed, 0)?;
424
425 let total_len = checked_grib1_u24_length(8usize + body.len() + 4, 0)?;
426 let mut message = Vec::new();
427 message.extend_from_slice(b"GRIB");
428 write_u24_be(&mut message, total_len)?;
429 write_u8_be(&mut message, 1)?;
430 message.extend_from_slice(&body);
431 message.extend_from_slice(b"7777");
432
433 self.out
434 .write_all(&message)
435 .map_err(|err| Error::Io(err, "GRIB writer output".into()))
436 }
437
438 pub fn write_grib2_message<I>(&mut self, fields: I) -> Result<()>
439 where
440 I: IntoIterator<Item = Grib2Field>,
441 {
442 let fields = fields.into_iter().collect::<Vec<_>>();
443 if fields.is_empty() {
444 return Err(Error::InvalidMessage(
445 "cannot write a GRIB2 message without fields".into(),
446 ));
447 }
448
449 let first = &fields[0];
450 for field in &fields[1..] {
451 if field.discipline != first.discipline {
452 return Err(Error::InvalidMessage(
453 "all fields in a GRIB2 message must share a discipline".into(),
454 ));
455 }
456 if field.identification != first.identification {
457 return Err(Error::InvalidMessage(
458 "all fields in a GRIB2 message must share Section 1 identification".into(),
459 ));
460 }
461 }
462
463 let mut message = Vec::new();
464 write_indicator_placeholder(&mut message, first.discipline)?;
465 write_identification_section(&mut message, &first.identification)?;
466 let mut current_grid = None;
467 for field in &fields {
468 if current_grid != Some(&field.grid) {
469 write_grid_section(&mut message, &field.grid)?;
470 current_grid = Some(&field.grid);
471 }
472 write_product_section(&mut message, &field.product)?;
473 write_data_representation_section(&mut message, &field.packed)?;
474 if let Some(bitmap) = &field.packed.bitmap_payload {
475 write_bitmap_section(&mut message, bitmap)?;
476 }
477 write_data_section(&mut message, &field.packed.data_payload)?;
478 }
479 message.extend_from_slice(b"7777");
480
481 let total_len = u64::try_from(message.len())
482 .map_err(|_| Error::Other("GRIB2 message length exceeds u64".into()))?;
483 message[8..16].copy_from_slice(&total_len.to_be_bytes());
484
485 self.out
486 .write_all(&message)
487 .map_err(|err| Error::Io(err, "GRIB writer output".into()))
488 }
489}
490
491#[derive(Debug, Clone)]
492struct PackedField {
493 representation: DataRepresentation,
494 bitmap_payload: Option<Vec<u8>>,
495 data_payload: Vec<u8>,
496}
497
498fn pack_simple_auto(
499 values: &[f64],
500 explicit_bitmap: Option<&[bool]>,
501 decimal_scale: i16,
502) -> Result<PackedField> {
503 let present = present_mask(values, explicit_bitmap)?;
504 let present_count = present.iter().filter(|present| **present).count();
505 let bitmap_payload = if present.iter().any(|present| !*present) {
506 Some(pack_bitmap(&present)?)
507 } else {
508 None
509 };
510
511 let quantized = quantize_present_values(values, &present, decimal_scale, "simple packing")?;
512 let (reference_value, deltas) = simple_packing_deltas(&quantized)?;
513 let max_delta = deltas.iter().copied().max().unwrap_or(0);
514 let bits_per_value = if max_delta == 0 {
515 0
516 } else {
517 (u64::BITS - max_delta.leading_zeros()) as u8
518 };
519
520 let mut writer = BitWriter::with_capacity_bits(deltas.len() * usize::from(bits_per_value));
521 if bits_per_value > 0 {
522 for delta in &deltas {
523 writer.write(*delta, usize::from(bits_per_value))?;
524 }
525 writer.align_to_byte()?;
526 }
527
528 let representation = DataRepresentation::SimplePacking(SimplePackingParams {
529 encoded_values: present_count,
530 reference_value,
531 binary_scale: 0,
532 decimal_scale,
533 bits_per_value,
534 original_field_type: 0,
535 });
536
537 Ok(PackedField {
538 representation,
539 bitmap_payload,
540 data_payload: writer.into_bytes(),
541 })
542}
543
544fn pack_complex_auto(
545 values: &[f64],
546 explicit_bitmap: Option<&[bool]>,
547 decimal_scale: i16,
548 spatial_differencing: Option<SpatialDifferencingOrder>,
549) -> Result<PackedField> {
550 let present = present_mask(values, explicit_bitmap)?;
551 let present_count = present.iter().filter(|present| **present).count();
552 let bitmap_payload = if present.iter().any(|present| !*present) {
553 Some(pack_bitmap(&present)?)
554 } else {
555 None
556 };
557
558 let quantized = quantize_present_values(values, &present, decimal_scale, "complex packing")?;
559 let (reference_value, deltas) = simple_packing_deltas(&quantized)?;
560 let spatial_packing = spatial_differencing
561 .map(|order| spatially_difference_values(&deltas, order))
562 .transpose()?;
563 let packed_values = spatial_packing
564 .as_ref()
565 .map_or(deltas.as_slice(), |spatial| spatial.values.as_slice());
566 let groups = complex_groups(packed_values)?;
567 let max_group_reference = groups
568 .iter()
569 .map(|group| group.reference)
570 .max()
571 .unwrap_or(0);
572 let max_group_width = groups.iter().map(|group| group.width).max().unwrap_or(0);
573 let group_reference_bits = bits_needed(max_group_reference)?;
574 let group_width_bits = bits_needed(u64::from(max_group_width))?;
575 let group_length_reference = complex_group_length_reference(present_count)?;
576 let true_length_last_group = complex_true_length_last_group(present_count)?;
577
578 let mut writer = BitWriter::new();
579 if let Some(spatial) = &spatial_packing {
580 write_spatial_descriptors(&mut writer, spatial)?;
581 }
582 for group in &groups {
583 writer.write(group.reference, usize::from(group_reference_bits))?;
584 }
585 writer.align_to_byte()?;
586 for group in &groups {
587 writer.write(u64::from(group.width), usize::from(group_width_bits))?;
588 }
589 writer.align_to_byte()?;
590 for group in &groups {
591 for value in &group.values {
592 writer.write(
593 value
594 .checked_sub(group.reference)
595 .ok_or_else(|| Error::Other("complex group value underflow".into()))?,
596 usize::from(group.width),
597 )?;
598 }
599 }
600 writer.align_to_byte()?;
601
602 let representation = DataRepresentation::ComplexPacking(ComplexPackingParams {
603 encoded_values: present_count,
604 reference_value,
605 binary_scale: 0,
606 decimal_scale,
607 group_reference_bits,
608 original_field_type: 0,
609 group_splitting_method: 1,
610 missing_value_management: 0,
611 primary_missing_substitute: u32::MAX,
612 secondary_missing_substitute: u32::MAX,
613 num_groups: groups.len(),
614 group_width_reference: 0,
615 group_width_bits,
616 group_length_reference,
617 group_length_increment: 1,
618 true_length_last_group,
619 scaled_group_length_bits: 0,
620 spatial_differencing: spatial_packing.as_ref().map(|spatial| spatial.params),
621 });
622
623 Ok(PackedField {
624 representation,
625 bitmap_payload,
626 data_payload: writer.into_bytes(),
627 })
628}
629
630#[cfg(feature = "jpeg2000")]
631fn pack_jpeg2000_auto(
632 values: &[f64],
633 explicit_bitmap: Option<&[bool]>,
634 grid: &GridDefinition,
635 decimal_scale: i16,
636) -> Result<PackedField> {
637 let prepared = prepare_image_packing(
638 values,
639 explicit_bitmap,
640 grid,
641 decimal_scale,
642 "JPEG2000 packing",
643 jpeg2000_bits_per_value,
644 )?;
645 let data_payload = encode_jpeg2000_payload(
646 &prepared.deltas,
647 prepared.params.bits_per_value,
648 prepared.dimensions,
649 )?;
650
651 Ok(PackedField {
652 representation: DataRepresentation::Jpeg2000Packing(Jpeg2000PackingParams {
653 packing: prepared.params,
654 compression_type: 0,
655 target_compression_ratio: 0,
656 }),
657 bitmap_payload: prepared.bitmap_payload,
658 data_payload,
659 })
660}
661
662#[cfg(not(feature = "jpeg2000"))]
663fn pack_jpeg2000_auto(
664 _values: &[f64],
665 _explicit_bitmap: Option<&[bool]>,
666 _grid: &GridDefinition,
667 _decimal_scale: i16,
668) -> Result<PackedField> {
669 Err(Error::UnsupportedDataTemplate(40))
670}
671
672#[cfg(feature = "png")]
673fn pack_png_auto(
674 values: &[f64],
675 explicit_bitmap: Option<&[bool]>,
676 grid: &GridDefinition,
677 decimal_scale: i16,
678) -> Result<PackedField> {
679 let prepared = prepare_image_packing(
680 values,
681 explicit_bitmap,
682 grid,
683 decimal_scale,
684 "PNG packing",
685 png_bits_per_value,
686 )?;
687 let data_payload = encode_png_payload(
688 &prepared.deltas,
689 prepared.params.bits_per_value,
690 prepared.dimensions,
691 )?;
692
693 Ok(PackedField {
694 representation: DataRepresentation::PngPacking(PngPackingParams {
695 packing: prepared.params,
696 }),
697 bitmap_payload: prepared.bitmap_payload,
698 data_payload,
699 })
700}
701
702#[cfg(not(feature = "png"))]
703fn pack_png_auto(
704 _values: &[f64],
705 _explicit_bitmap: Option<&[bool]>,
706 _grid: &GridDefinition,
707 _decimal_scale: i16,
708) -> Result<PackedField> {
709 Err(Error::UnsupportedDataTemplate(41))
710}
711
712#[cfg(any(feature = "jpeg2000", feature = "png"))]
713#[derive(Debug, Clone)]
714struct PreparedImagePacking {
715 params: ImagePackingParams,
716 bitmap_payload: Option<Vec<u8>>,
717 deltas: Vec<u64>,
718 dimensions: ImageDimensions,
719}
720
721#[cfg(any(feature = "jpeg2000", feature = "png"))]
722#[derive(Debug, Clone, Copy, PartialEq, Eq)]
723struct ImageDimensions {
724 width: u32,
725 height: u32,
726}
727
728#[cfg(any(feature = "jpeg2000", feature = "png"))]
729fn prepare_image_packing(
730 values: &[f64],
731 explicit_bitmap: Option<&[bool]>,
732 grid: &GridDefinition,
733 decimal_scale: i16,
734 packing_name: &str,
735 select_bits_per_value: fn(u8) -> Result<u8>,
736) -> Result<PreparedImagePacking> {
737 let present = present_mask(values, explicit_bitmap)?;
738 let present_count = present.iter().filter(|present| **present).count();
739 if present_count == 0 {
740 return Err(Error::Other(format!(
741 "{packing_name} requires at least one present value"
742 )));
743 }
744
745 let bitmap_payload = if present.iter().any(|present| !*present) {
746 Some(pack_bitmap(&present)?)
747 } else {
748 None
749 };
750
751 let quantized = quantize_present_values(values, &present, decimal_scale, packing_name)?;
752 let (reference_value, deltas) = simple_packing_deltas(&quantized)?;
753 let max_delta = deltas.iter().copied().max().unwrap_or(0);
754 let minimum_bits = bits_needed(max_delta)?.max(1);
755 let bits_per_value = select_bits_per_value(minimum_bits)?;
756 validate_image_deltas_fit(&deltas, bits_per_value)?;
757
758 Ok(PreparedImagePacking {
759 params: ImagePackingParams {
760 encoded_values: present_count,
761 reference_value,
762 binary_scale: 0,
763 decimal_scale,
764 bits_per_value,
765 original_field_type: 0,
766 },
767 bitmap_payload,
768 deltas,
769 dimensions: image_dimensions(grid, values.len(), present_count)?,
770 })
771}
772
773#[cfg(any(feature = "jpeg2000", feature = "png"))]
774fn image_dimensions(
775 grid: &GridDefinition,
776 total_values: usize,
777 present_count: usize,
778) -> Result<ImageDimensions> {
779 if present_count == total_values {
780 let (width, height) = grid.shape();
781 if width == 0 || height == 0 {
782 return Err(Error::UnsupportedGridTemplate(grid.template_number()));
783 }
784 return Ok(ImageDimensions {
785 width: u32::try_from(width)
786 .map_err(|_| Error::Other("image width exceeds u32".into()))?,
787 height: u32::try_from(height)
788 .map_err(|_| Error::Other("image height exceeds u32".into()))?,
789 });
790 }
791
792 Ok(ImageDimensions {
793 width: u32::try_from(present_count)
794 .map_err(|_| Error::Other("image width exceeds u32".into()))?,
795 height: 1,
796 })
797}
798
799#[cfg(any(feature = "jpeg2000", feature = "png"))]
800fn validate_image_deltas_fit(deltas: &[u64], bits_per_value: u8) -> Result<()> {
801 let max_value = if bits_per_value == u64::BITS as u8 {
802 u64::MAX
803 } else {
804 (1u64 << bits_per_value) - 1
805 };
806 if deltas.iter().any(|delta| *delta > max_value) {
807 return Err(Error::UnsupportedPackingWidth(bits_per_value));
808 }
809 Ok(())
810}
811
812#[cfg(feature = "jpeg2000")]
813fn jpeg2000_bits_per_value(minimum_bits: u8) -> Result<u8> {
814 if (1..=31).contains(&minimum_bits) {
815 Ok(minimum_bits)
816 } else {
817 Err(Error::UnsupportedPackingWidth(minimum_bits))
818 }
819}
820
821#[cfg(feature = "png")]
822fn png_bits_per_value(minimum_bits: u8) -> Result<u8> {
823 match minimum_bits {
824 0 | 1 => Ok(1),
825 2 => Ok(2),
826 3 | 4 => Ok(4),
827 5..=8 => Ok(8),
828 9..=16 => Ok(16),
829 17..=24 => Ok(24),
830 25..=32 => Ok(32),
831 bits => Err(Error::UnsupportedPackingWidth(bits)),
832 }
833}
834
835#[cfg(feature = "png")]
836fn encode_png_payload(
837 deltas: &[u64],
838 bits_per_value: u8,
839 dimensions: ImageDimensions,
840) -> Result<Vec<u8>> {
841 validate_image_sample_count(deltas.len(), dimensions)?;
842 let (color_type, bit_depth, image_data) = png_image_data(deltas, bits_per_value, dimensions)?;
843
844 let mut payload = Vec::new();
845 {
846 let mut encoder = png::Encoder::new(&mut payload, dimensions.width, dimensions.height);
847 encoder.set_color(color_type);
848 encoder.set_depth(bit_depth);
849 let mut writer = encoder
850 .write_header()
851 .map_err(|err| Error::Other(format!("PNG encode failed: {err}")))?;
852 writer
853 .write_image_data(&image_data)
854 .map_err(|err| Error::Other(format!("PNG encode failed: {err}")))?;
855 }
856 Ok(payload)
857}
858
859#[cfg(feature = "png")]
860fn png_image_data(
861 deltas: &[u64],
862 bits_per_value: u8,
863 dimensions: ImageDimensions,
864) -> Result<(png::ColorType, png::BitDepth, Vec<u8>)> {
865 match bits_per_value {
866 1 => Ok((
867 png::ColorType::Grayscale,
868 png::BitDepth::One,
869 pack_png_subbyte_rows(deltas, dimensions, 1)?,
870 )),
871 2 => Ok((
872 png::ColorType::Grayscale,
873 png::BitDepth::Two,
874 pack_png_subbyte_rows(deltas, dimensions, 2)?,
875 )),
876 4 => Ok((
877 png::ColorType::Grayscale,
878 png::BitDepth::Four,
879 pack_png_subbyte_rows(deltas, dimensions, 4)?,
880 )),
881 8 => Ok((
882 png::ColorType::Grayscale,
883 png::BitDepth::Eight,
884 deltas
885 .iter()
886 .map(|delta| u8::try_from(*delta))
887 .collect::<std::result::Result<Vec<_>, _>>()
888 .map_err(|_| Error::UnsupportedPackingWidth(bits_per_value))?,
889 )),
890 16 => {
891 let mut data = Vec::with_capacity(deltas.len() * 2);
892 for delta in deltas {
893 data.extend_from_slice(
894 &u16::try_from(*delta)
895 .map_err(|_| Error::UnsupportedPackingWidth(bits_per_value))?
896 .to_be_bytes(),
897 );
898 }
899 Ok((png::ColorType::Grayscale, png::BitDepth::Sixteen, data))
900 }
901 24 => Ok((
902 png::ColorType::Rgb,
903 png::BitDepth::Eight,
904 pack_png_multibyte_samples(deltas, 3)?,
905 )),
906 32 => Ok((
907 png::ColorType::Rgba,
908 png::BitDepth::Eight,
909 pack_png_multibyte_samples(deltas, 4)?,
910 )),
911 bits => Err(Error::UnsupportedPackingWidth(bits)),
912 }
913}
914
915#[cfg(feature = "png")]
916fn pack_png_subbyte_rows(
917 deltas: &[u64],
918 dimensions: ImageDimensions,
919 bits_per_value: u8,
920) -> Result<Vec<u8>> {
921 let width =
922 usize::try_from(dimensions.width).map_err(|_| Error::Other("PNG width overflow".into()))?;
923 let height = usize::try_from(dimensions.height)
924 .map_err(|_| Error::Other("PNG height overflow".into()))?;
925 let bits = usize::from(bits_per_value);
926 let row_bits = width
927 .checked_mul(bits)
928 .ok_or_else(|| Error::Other("PNG row width overflow".into()))?;
929 let row_bytes = row_bits.div_ceil(8);
930 let mut data = vec![
931 0;
932 row_bytes
933 .checked_mul(height)
934 .ok_or_else(|| Error::Other("PNG data length overflow".into()))?
935 ];
936
937 for (index, delta) in deltas.iter().copied().enumerate() {
938 let row = index / width;
939 let column = index % width;
940 let bit_offset = column
941 .checked_mul(bits)
942 .ok_or_else(|| Error::Other("PNG bit offset overflow".into()))?;
943 let byte_index = row
944 .checked_mul(row_bytes)
945 .and_then(|row_offset| row_offset.checked_add(bit_offset / 8))
946 .ok_or_else(|| Error::Other("PNG byte offset overflow".into()))?;
947 let shift = 8 - bits - (bit_offset % 8);
948 data[byte_index] |= (delta as u8) << shift;
949 }
950
951 Ok(data)
952}
953
954#[cfg(feature = "png")]
955fn pack_png_multibyte_samples(deltas: &[u64], bytes_per_sample: usize) -> Result<Vec<u8>> {
956 let mut data = Vec::with_capacity(
957 deltas
958 .len()
959 .checked_mul(bytes_per_sample)
960 .ok_or_else(|| Error::Other("PNG data length overflow".into()))?,
961 );
962 for delta in deltas {
963 let bytes = u32::try_from(*delta)
964 .map_err(|_| Error::UnsupportedPackingWidth((bytes_per_sample * 8) as u8))?
965 .to_be_bytes();
966 data.extend_from_slice(&bytes[4 - bytes_per_sample..]);
967 }
968 Ok(data)
969}
970
971#[cfg(feature = "jpeg2000")]
972fn encode_jpeg2000_payload(
973 deltas: &[u64],
974 bits_per_value: u8,
975 dimensions: ImageDimensions,
976) -> Result<Vec<u8>> {
977 validate_image_sample_count(deltas.len(), dimensions)?;
978
979 let component = openjp2::opj_image_comptparm {
980 dx: 1,
981 dy: 1,
982 w: dimensions.width,
983 h: dimensions.height,
984 prec: u32::from(bits_per_value),
985 bpp: u32::from(bits_per_value),
986 sgnd: 0,
987 ..Default::default()
988 };
989 let mut image = openjp2::opj_image::create(&[component], openjp2::OPJ_CLRSPC_GRAY)
990 .ok_or_else(|| Error::Other("failed to create JPEG2000 image".into()))?;
991 image.x1 = dimensions.width;
992 image.y1 = dimensions.height;
993
994 let components = image
995 .comps_mut()
996 .ok_or_else(|| Error::Other("JPEG2000 image has no components".into()))?;
997 let component = components
998 .get_mut(0)
999 .ok_or_else(|| Error::Other("JPEG2000 image has no components".into()))?;
1000 component.bpp = u32::from(bits_per_value);
1001 component.prec = u32::from(bits_per_value);
1002 let data = components
1003 .get_mut(0)
1004 .and_then(|component| component.data_mut())
1005 .ok_or_else(|| Error::Other("JPEG2000 image component has no data".into()))?;
1006 if data.len() != deltas.len() {
1007 return Err(Error::DataLengthMismatch {
1008 expected: deltas.len(),
1009 actual: data.len(),
1010 });
1011 }
1012 for (target, delta) in data.iter_mut().zip(deltas) {
1013 *target =
1014 i32::try_from(*delta).map_err(|_| Error::UnsupportedPackingWidth(bits_per_value))?;
1015 }
1016
1017 let path = tempfile::Builder::new()
1018 .prefix("grib-writer-")
1019 .suffix(".j2k")
1020 .tempfile()
1021 .map_err(|err| Error::Io(err, "JPEG2000 temporary codestream".into()))?
1022 .into_temp_path();
1023
1024 {
1025 let mut stream = openjp2::Stream::new_file(&path, 64 * 1024, false)
1026 .map_err(|err| Error::Io(err, "JPEG2000 temporary codestream".into()))?;
1027 let mut codec = openjp2::Codec::new_encoder(openjp2::OPJ_CODEC_J2K)
1028 .ok_or_else(|| Error::Other("failed to create JPEG2000 encoder".into()))?;
1029 let mut params = openjp2::opj_cparameters_t {
1030 tcp_numlayers: 1,
1031 cp_disto_alloc: 1,
1032 numresolution: jpeg2000_num_resolutions(dimensions),
1033 ..Default::default()
1034 };
1035
1036 if codec.setup_encoder(&mut params, &mut image) == 0 {
1037 return Err(Error::Other("JPEG2000 encoder setup failed".into()));
1038 }
1039 if codec.start_compress(&mut image, &mut stream) == 0 {
1040 return Err(Error::Other("JPEG2000 start-compress failed".into()));
1041 }
1042 if codec.encode(&mut stream) == 0 {
1043 return Err(Error::Other("JPEG2000 codestream encode failed".into()));
1044 }
1045 if codec.end_compress(&mut stream) == 0 {
1046 return Err(Error::Other("JPEG2000 end-compress failed".into()));
1047 }
1048 stream
1049 .flush()
1050 .map_err(|err| Error::Io(err, "JPEG2000 temporary codestream".into()))?;
1051 }
1052
1053 std::fs::read(&path).map_err(|err| Error::Io(err, "JPEG2000 temporary codestream".into()))
1054}
1055
1056#[cfg(feature = "jpeg2000")]
1057fn jpeg2000_num_resolutions(dimensions: ImageDimensions) -> i32 {
1058 let min_dimension = dimensions.width.min(dimensions.height);
1059 let mut resolutions = 1;
1060 while resolutions < 32 && min_dimension >= (1u32 << resolutions) {
1061 resolutions += 1;
1062 }
1063 resolutions
1064}
1065
1066#[cfg(any(feature = "jpeg2000", feature = "png"))]
1067fn validate_image_sample_count(sample_count: usize, dimensions: ImageDimensions) -> Result<()> {
1068 let width = usize::try_from(dimensions.width)
1069 .map_err(|_| Error::Other("image width overflow".into()))?;
1070 let height = usize::try_from(dimensions.height)
1071 .map_err(|_| Error::Other("image height overflow".into()))?;
1072 let expected = width
1073 .checked_mul(height)
1074 .ok_or_else(|| Error::Other("image sample count overflow".into()))?;
1075 if sample_count != expected {
1076 return Err(Error::DataLengthMismatch {
1077 expected,
1078 actual: sample_count,
1079 });
1080 }
1081 Ok(())
1082}
1083
1084fn quantize_present_values(
1085 values: &[f64],
1086 present: &[bool],
1087 decimal_scale: i16,
1088 packing_name: &str,
1089) -> Result<Vec<f64>> {
1090 let decimal_factor = 10.0_f64.powi(i32::from(decimal_scale));
1091 if !decimal_factor.is_finite() || decimal_factor <= 0.0 {
1092 return Err(Error::Other(format!(
1093 "invalid decimal scale for {packing_name}: {decimal_scale}"
1094 )));
1095 }
1096
1097 values
1098 .iter()
1099 .zip(present)
1100 .filter_map(|(value, present)| present.then_some(*value))
1101 .map(|value| {
1102 if !value.is_finite() {
1103 return Err(Error::Other(format!(
1104 "present values must be finite for {packing_name}"
1105 )));
1106 }
1107 let scaled = value * decimal_factor;
1108 if !scaled.is_finite() {
1109 return Err(Error::Other(format!(
1110 "scaled value overflow during {packing_name}"
1111 )));
1112 }
1113 Ok(scaled.round())
1114 })
1115 .collect()
1116}
1117
1118impl SpatialDifferencingOrder {
1119 const fn grib_order(self) -> u8 {
1120 match self {
1121 Self::First => 1,
1122 Self::Second => 2,
1123 }
1124 }
1125
1126 const fn min_values(self) -> usize {
1127 match self {
1128 Self::First => 1,
1129 Self::Second => 2,
1130 }
1131 }
1132}
1133
1134#[derive(Debug, Clone)]
1135struct SpatialPacking {
1136 params: SpatialDifferencingParams,
1137 descriptors: SpatialDescriptors,
1138 values: Vec<u64>,
1139}
1140
1141#[derive(Debug, Clone, Copy)]
1142struct SpatialDescriptors {
1143 first_value: i64,
1144 second_value: Option<i64>,
1145 overall_minimum: i64,
1146}
1147
1148fn spatially_difference_values(
1149 values: &[u64],
1150 order: SpatialDifferencingOrder,
1151) -> Result<SpatialPacking> {
1152 if values.len() < order.min_values() {
1153 return Err(Error::DataLengthMismatch {
1154 expected: order.min_values(),
1155 actual: values.len(),
1156 });
1157 }
1158
1159 let values = values
1160 .iter()
1161 .copied()
1162 .map(|value| {
1163 i64::try_from(value)
1164 .map_err(|_| Error::Other("spatial differencing value exceeds i64".into()))
1165 })
1166 .collect::<Result<Vec<_>>>()?;
1167
1168 let (descriptors, differenced) = match order {
1169 SpatialDifferencingOrder::First => first_order_spatial_difference(&values)?,
1170 SpatialDifferencingOrder::Second => second_order_spatial_difference(&values)?,
1171 };
1172 let descriptor_octets = spatial_descriptor_octets(&descriptors)?;
1173
1174 Ok(SpatialPacking {
1175 params: SpatialDifferencingParams {
1176 order: order.grib_order(),
1177 descriptor_octets,
1178 },
1179 descriptors,
1180 values: differenced,
1181 })
1182}
1183
1184fn first_order_spatial_difference(values: &[i64]) -> Result<(SpatialDescriptors, Vec<u64>)> {
1185 let mut differences = Vec::with_capacity(values.len().saturating_sub(1));
1186 for pair in values.windows(2) {
1187 differences.push(
1188 pair[1]
1189 .checked_sub(pair[0])
1190 .ok_or_else(|| Error::Other("spatial differencing overflow".into()))?,
1191 );
1192 }
1193 let overall_minimum = differences.iter().copied().min().unwrap_or(0);
1194
1195 let mut differenced = Vec::with_capacity(values.len());
1196 differenced.push(0);
1197 for difference in differences {
1198 differenced.push(spatial_difference_delta(difference, overall_minimum)?);
1199 }
1200
1201 Ok((
1202 SpatialDescriptors {
1203 first_value: values[0],
1204 second_value: None,
1205 overall_minimum,
1206 },
1207 differenced,
1208 ))
1209}
1210
1211fn second_order_spatial_difference(values: &[i64]) -> Result<(SpatialDescriptors, Vec<u64>)> {
1212 let first_value = values[0];
1213 let second_value = values[1];
1214 let mut previous_difference = second_value
1215 .checked_sub(first_value)
1216 .ok_or_else(|| Error::Other("spatial differencing overflow".into()))?;
1217 let mut second_differences = Vec::with_capacity(values.len().saturating_sub(2));
1218
1219 for index in 2..values.len() {
1220 let difference = values[index]
1221 .checked_sub(values[index - 1])
1222 .ok_or_else(|| Error::Other("spatial differencing overflow".into()))?;
1223 second_differences.push(
1224 difference
1225 .checked_sub(previous_difference)
1226 .ok_or_else(|| Error::Other("spatial differencing overflow".into()))?,
1227 );
1228 previous_difference = difference;
1229 }
1230
1231 let overall_minimum = second_differences.iter().copied().min().unwrap_or(0);
1232 let mut differenced = Vec::with_capacity(values.len());
1233 differenced.push(0);
1234 differenced.push(0);
1235 for second_difference in second_differences {
1236 differenced.push(spatial_difference_delta(
1237 second_difference,
1238 overall_minimum,
1239 )?);
1240 }
1241
1242 Ok((
1243 SpatialDescriptors {
1244 first_value,
1245 second_value: Some(second_value),
1246 overall_minimum,
1247 },
1248 differenced,
1249 ))
1250}
1251
1252fn spatial_difference_delta(value: i64, overall_minimum: i64) -> Result<u64> {
1253 let delta = value
1254 .checked_sub(overall_minimum)
1255 .ok_or_else(|| Error::Other("spatial differencing overflow".into()))?;
1256 u64::try_from(delta)
1257 .map_err(|_| Error::Other("spatial differencing produced negative delta".into()))
1258}
1259
1260fn spatial_descriptor_octets(descriptors: &SpatialDescriptors) -> Result<u8> {
1261 let values = [
1262 Some(descriptors.first_value),
1263 descriptors.second_value,
1264 Some(descriptors.overall_minimum),
1265 ];
1266 for octets in 1..=8 {
1267 if values
1268 .iter()
1269 .flatten()
1270 .all(|value| signed_magnitude_fits(*value, octets))
1271 {
1272 return Ok(octets);
1273 }
1274 }
1275
1276 Err(Error::Other(
1277 "spatial differencing descriptor exceeds signed-magnitude range".into(),
1278 ))
1279}
1280
1281fn signed_magnitude_fits(value: i64, octets: u8) -> bool {
1282 signed_magnitude_bits(value, octets).is_ok()
1283}
1284
1285fn write_spatial_descriptors(writer: &mut BitWriter, spatial: &SpatialPacking) -> Result<()> {
1286 let bit_count = usize::from(spatial.params.descriptor_octets) * 8;
1287 writer.write(
1288 signed_magnitude_bits(
1289 spatial.descriptors.first_value,
1290 spatial.params.descriptor_octets,
1291 )?,
1292 bit_count,
1293 )?;
1294 if let Some(second_value) = spatial.descriptors.second_value {
1295 writer.write(
1296 signed_magnitude_bits(second_value, spatial.params.descriptor_octets)?,
1297 bit_count,
1298 )?;
1299 }
1300 writer.write(
1301 signed_magnitude_bits(
1302 spatial.descriptors.overall_minimum,
1303 spatial.params.descriptor_octets,
1304 )?,
1305 bit_count,
1306 )
1307}
1308
1309fn signed_magnitude_bits(value: i64, octets: u8) -> Result<u64> {
1310 let bit_count = u32::from(octets) * 8;
1311 if bit_count == 0 || bit_count > u64::BITS {
1312 return Err(Error::Other(
1313 "spatial differencing descriptor width must be 1..=8 octets".into(),
1314 ));
1315 }
1316 let magnitude = value
1317 .checked_abs()
1318 .ok_or_else(|| Error::Other("spatial differencing descriptor magnitude overflow".into()))?
1319 as u64;
1320 let magnitude_bits = bit_count - 1;
1321 let max_magnitude = if magnitude_bits == u64::BITS {
1322 u64::MAX
1323 } else {
1324 (1u64 << magnitude_bits) - 1
1325 };
1326 if magnitude > max_magnitude {
1327 return Err(Error::Other(
1328 "spatial differencing descriptor exceeds signed-magnitude range".into(),
1329 ));
1330 }
1331
1332 let sign_bit = if value < 0 {
1333 1u64 << (bit_count - 1)
1334 } else {
1335 0
1336 };
1337 Ok(sign_bit | magnitude)
1338}
1339
1340fn reorder_field_to_grib_scan_order(
1341 grid: &GridDefinition,
1342 values: &mut [f64],
1343 bitmap: Option<&mut [bool]>,
1344) -> Result<()> {
1345 grid.reorder_for_ndarray_in_place(values)?;
1346 if let Some(bitmap) = bitmap {
1347 grid.reorder_for_ndarray_in_place(bitmap)?;
1348 }
1349 Ok(())
1350}
1351
1352fn present_mask(values: &[f64], explicit_bitmap: Option<&[bool]>) -> Result<Vec<bool>> {
1353 match explicit_bitmap {
1354 Some(bitmap) => values
1355 .iter()
1356 .zip(bitmap)
1357 .map(|(value, present)| {
1358 if *present && !value.is_finite() {
1359 return Err(Error::Other(
1360 "explicit bitmap marks a non-finite value as present".into(),
1361 ));
1362 }
1363 Ok(*present)
1364 })
1365 .collect(),
1366 None => values
1367 .iter()
1368 .map(|value| {
1369 if value.is_nan() {
1370 Ok(false)
1371 } else if value.is_finite() {
1372 Ok(true)
1373 } else {
1374 Err(Error::Other(
1375 "infinite values cannot be written as packed data".into(),
1376 ))
1377 }
1378 })
1379 .collect(),
1380 }
1381}
1382
1383fn simple_packing_deltas(quantized: &[f64]) -> Result<(f32, Vec<u64>)> {
1384 if quantized.is_empty() {
1385 return Ok((0.0, Vec::new()));
1386 }
1387
1388 let min_value = quantized.iter().copied().fold(f64::INFINITY, f64::min);
1389 let reference_value = f32_not_greater_than(min_value)
1390 .ok_or_else(|| Error::Other("failed to choose simple-packing reference value".into()))?;
1391 let reference = f64::from(reference_value);
1392
1393 let mut deltas = Vec::with_capacity(quantized.len());
1394 for value in quantized {
1395 let delta = (value - reference).round();
1396 if !delta.is_finite() || delta < 0.0 || delta > u64::MAX as f64 {
1397 return Err(Error::Other(
1398 "packed simple-packing delta does not fit in u64".into(),
1399 ));
1400 }
1401 deltas.push(delta as u64);
1402 }
1403
1404 Ok((reference_value, deltas))
1405}
1406
1407#[derive(Debug, Clone)]
1408struct ComplexGroup {
1409 reference: u64,
1410 width: u8,
1411 values: Vec<u64>,
1412}
1413
1414fn complex_groups(deltas: &[u64]) -> Result<Vec<ComplexGroup>> {
1415 if deltas.is_empty() {
1416 return Ok(vec![ComplexGroup {
1417 reference: 0,
1418 width: 0,
1419 values: Vec::new(),
1420 }]);
1421 }
1422
1423 let group_len = complex_group_len(deltas.len());
1424 let mut groups = Vec::with_capacity(deltas.len().div_ceil(group_len));
1425 for chunk in deltas.chunks(group_len) {
1426 let reference = chunk.iter().copied().min().unwrap_or(0);
1427 let max_value = chunk.iter().copied().max().unwrap_or(reference);
1428 if max_value > i64::MAX as u64 {
1429 return Err(Error::Other(
1430 "complex packing value exceeds i64 decoder range".into(),
1431 ));
1432 }
1433 let width = bits_needed(max_value - reference)?;
1434 groups.push(ComplexGroup {
1435 reference,
1436 width,
1437 values: chunk.to_vec(),
1438 });
1439 }
1440 Ok(groups)
1441}
1442
1443fn complex_group_length_reference(value_count: usize) -> Result<u32> {
1444 u32::try_from(complex_group_len(value_count))
1445 .map_err(|_| Error::Other("complex group length exceeds u32".into()))
1446}
1447
1448fn complex_true_length_last_group(value_count: usize) -> Result<u32> {
1449 if value_count == 0 {
1450 return Ok(0);
1451 }
1452
1453 let group_len = complex_group_len(value_count);
1454 let remainder = value_count % group_len;
1455 let length = if remainder == 0 { group_len } else { remainder };
1456 u32::try_from(length).map_err(|_| Error::Other("complex group length exceeds u32".into()))
1457}
1458
1459fn complex_group_len(value_count: usize) -> usize {
1460 COMPLEX_AUTO_GROUP_LEN.min(value_count)
1461}
1462
1463fn bits_needed(value: u64) -> Result<u8> {
1464 let bits = if value == 0 {
1465 0
1466 } else {
1467 u64::BITS - value.leading_zeros()
1468 };
1469 u8::try_from(bits).map_err(|_| Error::Other("bit width exceeds u8".into()))
1470}
1471
1472fn f32_not_greater_than(value: f64) -> Option<f32> {
1473 if !value.is_finite() || value < f64::from(f32::MIN) || value > f64::from(f32::MAX) {
1474 return None;
1475 }
1476
1477 let mut candidate = value as f32;
1478 while f64::from(candidate) > value {
1479 candidate = next_down_f32(candidate)?;
1480 }
1481 Some(candidate)
1482}
1483
1484fn next_down_f32(value: f32) -> Option<f32> {
1485 if value.is_nan() || value == f32::NEG_INFINITY {
1486 return None;
1487 }
1488 if value == 0.0 {
1489 return Some(-f32::from_bits(1));
1490 }
1491 let bits = value.to_bits();
1492 Some(if value.is_sign_positive() {
1493 f32::from_bits(bits - 1)
1494 } else {
1495 f32::from_bits(bits + 1)
1496 })
1497}
1498
1499fn pack_bitmap(present: &[bool]) -> Result<Vec<u8>> {
1500 let mut writer = BitWriter::with_capacity_bits(present.len());
1501 for present in present {
1502 writer.write(u64::from(*present), 1)?;
1503 }
1504 writer.align_to_byte()?;
1505 Ok(writer.into_bytes())
1506}
1507
1508fn write_grib1_product_section(out: &mut Vec<u8>, product: &Grib1ProductDefinition) -> Result<()> {
1509 let (year_of_century, century) = grib1_reference_year_fields(product.reference_time.year)?;
1510
1511 write_u24_be(out, 28)?;
1512 write_u8_be(out, product.table_version)?;
1513 write_u8_be(out, product.center_id)?;
1514 write_u8_be(out, product.generating_process_id)?;
1515 write_u8_be(out, product.grid_id)?;
1516 let mut flags = 0b1000_0000;
1517 if product.has_bitmap {
1518 flags |= 0b0100_0000;
1519 }
1520 write_u8_be(out, flags)?;
1521 write_u8_be(out, product.parameter_number)?;
1522 write_u8_be(out, product.level_type)?;
1523 write_u16_be(out, product.level_value)?;
1524 write_u8_be(out, year_of_century)?;
1525 write_u8_be(out, product.reference_time.month)?;
1526 write_u8_be(out, product.reference_time.day)?;
1527 write_u8_be(out, product.reference_time.hour)?;
1528 write_u8_be(out, product.reference_time.minute)?;
1529 write_u8_be(out, product.forecast_time_unit)?;
1530 write_u8_be(out, product.p1)?;
1531 write_u8_be(out, product.p2)?;
1532 write_u8_be(out, product.time_range_indicator)?;
1533 write_u16_be(out, product.average_count)?;
1534 write_u8_be(out, product.missing_count)?;
1535 write_u8_be(out, century)?;
1536 write_u8_be(out, product.subcenter_id)?;
1537 out.extend_from_slice(
1538 &encode_wmo_i16(product.decimal_scale)
1539 .ok_or_else(|| Error::Other("decimal scale does not fit GRIB signed i16".into()))?,
1540 );
1541 Ok(())
1542}
1543
1544fn write_grib1_grid_section(out: &mut Vec<u8>, grid: &GridDefinition) -> Result<()> {
1545 let Some(grid) = grid.as_lat_lon() else {
1546 return Err(Error::UnsupportedGridTemplate(grid.template_number()));
1547 };
1548
1549 write_u24_be(out, 32)?;
1550 write_u8_be(out, 0)?;
1551 write_u8_be(out, 255)?;
1552 write_u8_be(out, 0)?;
1553 write_u16_be(out, checked_grib1_grid_dimension(grid.ni, "Ni")?)?;
1554 write_u16_be(out, checked_grib1_grid_dimension(grid.nj, "Nj")?)?;
1555 out.extend_from_slice(&encode_grib1_coordinate(
1556 grid.lat_first,
1557 "latitude of first grid point",
1558 )?);
1559 out.extend_from_slice(&encode_grib1_coordinate(
1560 grid.lon_first,
1561 "longitude of first grid point",
1562 )?);
1563 write_u8_be(out, 0x80)?;
1564 out.extend_from_slice(&encode_grib1_coordinate(
1565 grid.lat_last,
1566 "latitude of last grid point",
1567 )?);
1568 out.extend_from_slice(&encode_grib1_coordinate(
1569 grid.lon_last,
1570 "longitude of last grid point",
1571 )?);
1572 write_u16_be(
1573 out,
1574 checked_grib1_increment(grid.di, "i direction increment")?,
1575 )?;
1576 write_u16_be(
1577 out,
1578 checked_grib1_increment(grid.dj, "j direction increment")?,
1579 )?;
1580 write_u8_be(out, grid.scanning_mode)?;
1581 out.extend_from_slice(&[0; 4]);
1582 Ok(())
1583}
1584
1585fn write_grib1_bitmap_section(
1586 out: &mut Vec<u8>,
1587 bitmap_payload: &[u8],
1588 num_points: usize,
1589) -> Result<()> {
1590 let length = checked_grib1_u24_length(6usize + bitmap_payload.len(), 3)?;
1591 write_u24_be(out, length)?;
1592 write_u8_be(out, unused_bits_for_width(num_points, 1)?)?;
1593 write_u16_be(out, 0)?;
1594 out.extend_from_slice(bitmap_payload);
1595 Ok(())
1596}
1597
1598fn write_grib1_predefined_bitmap_section(out: &mut Vec<u8>, table_reference: u16) -> Result<()> {
1599 if table_reference == 0 {
1600 return Err(Error::Other(
1601 "GRIB1 predefined bitmap table reference must be nonzero".into(),
1602 ));
1603 }
1604 write_u24_be(out, 6)?;
1605 write_u8_be(out, 0)?;
1606 write_u16_be(out, table_reference)?;
1607 Ok(())
1608}
1609
1610fn write_grib1_data_section(out: &mut Vec<u8>, packed: &PackedField, flags: u8) -> Result<()> {
1611 validate_grib1_binary_data_flags(flags)?;
1612 let DataRepresentation::SimplePacking(params) = &packed.representation else {
1613 return Err(Error::UnsupportedDataTemplate(1004));
1614 };
1615
1616 let length = checked_grib1_u24_length(11usize + packed.data_payload.len(), 4)?;
1617 write_u24_be(out, length)?;
1618 let unused_bits = unused_bits_for_width(params.encoded_values, params.bits_per_value)?;
1619 write_u8_be(out, (flags << 4) | unused_bits)?;
1620 out.extend_from_slice(
1621 &encode_wmo_i16(params.binary_scale)
1622 .ok_or_else(|| Error::Other("binary scale does not fit GRIB signed i16".into()))?,
1623 );
1624 out.extend_from_slice(
1625 &encode_ibm_f32(params.reference_value)
1626 .ok_or_else(|| Error::Other("reference value does not fit GRIB1 IBM float".into()))?,
1627 );
1628 write_u8_be(out, params.bits_per_value)?;
1629 out.extend_from_slice(&packed.data_payload);
1630 Ok(())
1631}
1632
1633fn validate_grib1_binary_data_flags(flags: u8) -> Result<()> {
1634 if flags == 0 {
1635 return Ok(());
1636 }
1637 if flags > 0x0f {
1638 return Err(Error::Other(
1639 "GRIB1 binary data flags must fit in four bits".into(),
1640 ));
1641 }
1642 let template = if flags & 0b1000 != 0 {
1643 1004
1644 } else if flags & 0b0100 != 0 {
1645 1005
1646 } else if flags & 0b0010 != 0 {
1647 1006
1648 } else {
1649 1007
1650 };
1651 Err(Error::UnsupportedDataTemplate(template))
1652}
1653
1654fn unused_bits_for_width(values: usize, bits_per_value: u8) -> Result<u8> {
1655 let bits = values
1656 .checked_mul(usize::from(bits_per_value))
1657 .ok_or_else(|| Error::Other("packed bit count overflow".into()))?;
1658 Ok(((8 - (bits % 8)) % 8) as u8)
1659}
1660
1661fn grib1_reference_year_fields(year: u16) -> Result<(u8, u8)> {
1662 if year == 0 {
1663 return Err(Error::Other(
1664 "GRIB1 reference year 0 cannot be encoded".into(),
1665 ));
1666 }
1667
1668 let century = ((year - 1) / 100) + 1;
1669 let year_of_century = year - ((century - 1) * 100);
1670 Ok((
1671 u8::try_from(year_of_century)
1672 .map_err(|_| Error::Other("GRIB1 year of century exceeds u8".into()))?,
1673 u8::try_from(century).map_err(|_| Error::Other("GRIB1 century exceeds u8".into()))?,
1674 ))
1675}
1676
1677fn encode_grib1_coordinate(value: i32, name: &str) -> Result<[u8; 3]> {
1678 if value % 1_000 != 0 {
1679 return Err(Error::Other(format!(
1680 "{name} must be representable in GRIB1 millidegrees"
1681 )));
1682 }
1683 encode_wmo_i24(value / 1_000)
1684 .ok_or_else(|| Error::Other(format!("{name} does not fit GRIB signed i24")))
1685}
1686
1687fn checked_grib1_grid_dimension(value: u32, name: &str) -> Result<u16> {
1688 u16::try_from(value).map_err(|_| Error::Other(format!("{name} exceeds GRIB1 u16 limit")))
1689}
1690
1691fn checked_grib1_increment(value: u32, name: &str) -> Result<u16> {
1692 if value % 1_000 != 0 {
1693 return Err(Error::Other(format!(
1694 "{name} must be representable in GRIB1 millidegrees"
1695 )));
1696 }
1697 u16::try_from(value / 1_000)
1698 .map_err(|_| Error::Other(format!("{name} exceeds GRIB1 u16 millidegree limit")))
1699}
1700
1701fn checked_grib1_u24_length(length: usize, section: u8) -> Result<u32> {
1702 let length = u32::try_from(length).map_err(|_| Error::InvalidSection {
1703 section,
1704 reason: "GRIB1 length exceeds unsigned 24-bit limit".into(),
1705 })?;
1706 if length > U24_MAX {
1707 return Err(Error::InvalidSection {
1708 section,
1709 reason: format!("GRIB1 length {length} exceeds unsigned 24-bit limit"),
1710 });
1711 }
1712 Ok(length)
1713}
1714
1715fn write_indicator_placeholder(out: &mut Vec<u8>, discipline: u8) -> Result<()> {
1716 out.extend_from_slice(b"GRIB");
1717 write_u16_be(out, 0)?;
1718 write_u8_be(out, discipline)?;
1719 write_u8_be(out, 2)?;
1720 write_u64_be(out, 0)
1721}
1722
1723fn write_identification_section(out: &mut Vec<u8>, identification: &Identification) -> Result<()> {
1724 write_u32_be(out, 21)?;
1725 write_u8_be(out, 1)?;
1726 write_u16_be(out, identification.center_id)?;
1727 write_u16_be(out, identification.subcenter_id)?;
1728 write_u8_be(out, identification.master_table_version)?;
1729 write_u8_be(out, identification.local_table_version)?;
1730 write_u8_be(out, identification.significance_of_reference_time)?;
1731 write_u16_be(out, identification.reference_year)?;
1732 write_u8_be(out, identification.reference_month)?;
1733 write_u8_be(out, identification.reference_day)?;
1734 write_u8_be(out, identification.reference_hour)?;
1735 write_u8_be(out, identification.reference_minute)?;
1736 write_u8_be(out, identification.reference_second)?;
1737 write_u8_be(out, identification.production_status)?;
1738 write_u8_be(out, identification.processed_data_type)
1739}
1740
1741fn write_grid_section(out: &mut Vec<u8>, grid: &GridDefinition) -> Result<()> {
1742 match grid {
1743 GridDefinition::LatLon(grid) => write_latlon_grid_section(out, grid),
1744 GridDefinition::Mercator(grid) => write_mercator_grid_section(out, grid),
1745 GridDefinition::PolarStereographic(grid) => {
1746 write_polar_stereographic_grid_section(out, grid)
1747 }
1748 GridDefinition::LambertConformal(grid) => write_lambert_conformal_grid_section(out, grid),
1749 GridDefinition::AlbersEqualArea(grid) => write_albers_equal_area_grid_section(out, grid),
1750 _ => Err(Error::UnsupportedGridTemplate(grid.template_number())),
1751 }
1752}
1753
1754fn write_latlon_grid_section(out: &mut Vec<u8>, grid: &LatLonGrid) -> Result<()> {
1755 let mut section = vec![0u8; 72];
1756 section[..4].copy_from_slice(&72u32.to_be_bytes());
1757 section[4] = 3;
1758 section[6..10].copy_from_slice(&checked_latlon_point_count(grid)?.to_be_bytes());
1759 section[12..14].copy_from_slice(&0u16.to_be_bytes());
1760 section[30..34].copy_from_slice(&grid.ni.to_be_bytes());
1761 section[34..38].copy_from_slice(&grid.nj.to_be_bytes());
1762 section[46..50].copy_from_slice(&encode_wmo_i32(grid.lat_first).ok_or_else(|| {
1763 Error::Other("latitude of first grid point does not fit GRIB signed i32".into())
1764 })?);
1765 section[50..54].copy_from_slice(&encode_wmo_i32(grid.lon_first).ok_or_else(|| {
1766 Error::Other("longitude of first grid point does not fit GRIB signed i32".into())
1767 })?);
1768 section[55..59].copy_from_slice(&encode_wmo_i32(grid.lat_last).ok_or_else(|| {
1769 Error::Other("latitude of last grid point does not fit GRIB signed i32".into())
1770 })?);
1771 section[59..63].copy_from_slice(&encode_wmo_i32(grid.lon_last).ok_or_else(|| {
1772 Error::Other("longitude of last grid point does not fit GRIB signed i32".into())
1773 })?);
1774 section[63..67].copy_from_slice(&grid.di.to_be_bytes());
1775 section[67..71].copy_from_slice(&grid.dj.to_be_bytes());
1776 section[71] = grid.scanning_mode;
1777 out.extend_from_slice(§ion);
1778 Ok(())
1779}
1780
1781fn write_mercator_grid_section(out: &mut Vec<u8>, grid: &MercatorGrid) -> Result<()> {
1782 checked_projected_point_count(grid.ni, grid.nj, grid.number_of_points, "Mercator grid")?;
1783
1784 let mut section = vec![0u8; 72];
1785 section[..4].copy_from_slice(&72u32.to_be_bytes());
1786 section[4] = 3;
1787 section[6..10].copy_from_slice(&grid.number_of_points.to_be_bytes());
1788 section[12..14].copy_from_slice(&10u16.to_be_bytes());
1789 write_projected_grid_shape_of_earth(
1790 &mut section,
1791 ProjectedGridShapeOfEarth {
1792 shape_of_earth: grid.shape_of_earth,
1793 scale_factor_radius: grid.scale_factor_radius,
1794 scaled_value_radius: grid.scaled_value_radius,
1795 scale_factor_major_axis: grid.scale_factor_major_axis,
1796 scaled_value_major_axis: grid.scaled_value_major_axis,
1797 scale_factor_minor_axis: grid.scale_factor_minor_axis,
1798 scaled_value_minor_axis: grid.scaled_value_minor_axis,
1799 },
1800 );
1801 section[30..34].copy_from_slice(&grid.ni.to_be_bytes());
1802 section[34..38].copy_from_slice(&grid.nj.to_be_bytes());
1803 section[38..42].copy_from_slice(&encode_wmo_i32(grid.lat_first).ok_or_else(|| {
1804 Error::Other("latitude of first grid point does not fit GRIB signed i32".into())
1805 })?);
1806 section[42..46].copy_from_slice(&encode_wmo_i32(grid.lon_first).ok_or_else(|| {
1807 Error::Other("longitude of first grid point does not fit GRIB signed i32".into())
1808 })?);
1809 section[46] = grid.resolution_and_component_flags;
1810 section[47..51].copy_from_slice(&encode_wmo_i32(grid.lat_d).ok_or_else(|| {
1811 Error::Other(
1812 "latitude where grid lengths are specified does not fit GRIB signed i32".into(),
1813 )
1814 })?);
1815 section[51..55].copy_from_slice(&encode_wmo_i32(grid.lat_last).ok_or_else(|| {
1816 Error::Other("latitude of last grid point does not fit GRIB signed i32".into())
1817 })?);
1818 section[55..59].copy_from_slice(&encode_wmo_i32(grid.lon_last).ok_or_else(|| {
1819 Error::Other("longitude of last grid point does not fit GRIB signed i32".into())
1820 })?);
1821 section[59] = grid.scanning_mode;
1822 section[60..64].copy_from_slice(&grid.orientation_of_grid.to_be_bytes());
1823 section[64..68].copy_from_slice(&grid.di.to_be_bytes());
1824 section[68..72].copy_from_slice(&grid.dj.to_be_bytes());
1825 out.extend_from_slice(§ion);
1826 Ok(())
1827}
1828
1829fn write_polar_stereographic_grid_section(
1830 out: &mut Vec<u8>,
1831 grid: &PolarStereographicGrid,
1832) -> Result<()> {
1833 checked_projected_point_count(
1834 grid.nx,
1835 grid.ny,
1836 grid.number_of_points,
1837 "polar stereographic grid",
1838 )?;
1839
1840 let mut section = vec![0u8; 65];
1841 section[..4].copy_from_slice(&65u32.to_be_bytes());
1842 section[4] = 3;
1843 section[6..10].copy_from_slice(&grid.number_of_points.to_be_bytes());
1844 section[12..14].copy_from_slice(&20u16.to_be_bytes());
1845 write_projected_grid_shape_of_earth(
1846 &mut section,
1847 ProjectedGridShapeOfEarth {
1848 shape_of_earth: grid.shape_of_earth,
1849 scale_factor_radius: grid.scale_factor_radius,
1850 scaled_value_radius: grid.scaled_value_radius,
1851 scale_factor_major_axis: grid.scale_factor_major_axis,
1852 scaled_value_major_axis: grid.scaled_value_major_axis,
1853 scale_factor_minor_axis: grid.scale_factor_minor_axis,
1854 scaled_value_minor_axis: grid.scaled_value_minor_axis,
1855 },
1856 );
1857 section[30..34].copy_from_slice(&grid.nx.to_be_bytes());
1858 section[34..38].copy_from_slice(&grid.ny.to_be_bytes());
1859 section[38..42].copy_from_slice(&encode_wmo_i32(grid.lat_first).ok_or_else(|| {
1860 Error::Other("latitude of first grid point does not fit GRIB signed i32".into())
1861 })?);
1862 section[42..46].copy_from_slice(&grid.lon_first.to_be_bytes());
1863 section[46] = grid.resolution_and_component_flags;
1864 section[47..51].copy_from_slice(&encode_wmo_i32(grid.lat_d).ok_or_else(|| {
1865 Error::Other(
1866 "latitude where grid lengths are specified does not fit GRIB signed i32".into(),
1867 )
1868 })?);
1869 section[51..55].copy_from_slice(&grid.lon_v.to_be_bytes());
1870 section[55..59].copy_from_slice(&grid.dx.to_be_bytes());
1871 section[59..63].copy_from_slice(&grid.dy.to_be_bytes());
1872 section[63] = grid.projection_center_flag;
1873 section[64] = grid.scanning_mode;
1874 out.extend_from_slice(§ion);
1875 Ok(())
1876}
1877
1878fn write_albers_equal_area_grid_section(
1879 out: &mut Vec<u8>,
1880 grid: &AlbersEqualAreaGrid,
1881) -> Result<()> {
1882 checked_projected_point_count(
1883 grid.nx,
1884 grid.ny,
1885 grid.number_of_points,
1886 "Albers equal-area grid",
1887 )?;
1888
1889 let mut section = vec![0u8; 81];
1890 section[..4].copy_from_slice(&81u32.to_be_bytes());
1891 section[4] = 3;
1892 section[6..10].copy_from_slice(&grid.number_of_points.to_be_bytes());
1893 section[12..14].copy_from_slice(&31u16.to_be_bytes());
1894 write_projected_grid_shape_of_earth(
1895 &mut section,
1896 ProjectedGridShapeOfEarth {
1897 shape_of_earth: grid.shape_of_earth,
1898 scale_factor_radius: grid.scale_factor_radius,
1899 scaled_value_radius: grid.scaled_value_radius,
1900 scale_factor_major_axis: grid.scale_factor_major_axis,
1901 scaled_value_major_axis: grid.scaled_value_major_axis,
1902 scale_factor_minor_axis: grid.scale_factor_minor_axis,
1903 scaled_value_minor_axis: grid.scaled_value_minor_axis,
1904 },
1905 );
1906 section[30..34].copy_from_slice(&grid.nx.to_be_bytes());
1907 section[34..38].copy_from_slice(&grid.ny.to_be_bytes());
1908 section[38..42].copy_from_slice(&encode_wmo_i32(grid.lat_first).ok_or_else(|| {
1909 Error::Other("latitude of first grid point does not fit GRIB signed i32".into())
1910 })?);
1911 section[42..46].copy_from_slice(&grid.lon_first.to_be_bytes());
1912 section[46] = grid.resolution_and_component_flags;
1913 section[47..51].copy_from_slice(&encode_wmo_i32(grid.lat_d).ok_or_else(|| {
1914 Error::Other(
1915 "latitude where grid lengths are specified does not fit GRIB signed i32".into(),
1916 )
1917 })?);
1918 section[51..55].copy_from_slice(&grid.lon_v.to_be_bytes());
1919 section[55..59].copy_from_slice(&grid.dx.to_be_bytes());
1920 section[59..63].copy_from_slice(&grid.dy.to_be_bytes());
1921 section[63] = grid.projection_center_flag;
1922 section[64] = grid.scanning_mode;
1923 section[65..69].copy_from_slice(
1924 &encode_wmo_i32(grid.latin1).ok_or_else(|| {
1925 Error::Other("first Latin latitude does not fit GRIB signed i32".into())
1926 })?,
1927 );
1928 section[69..73].copy_from_slice(&encode_wmo_i32(grid.latin2).ok_or_else(|| {
1929 Error::Other("second Latin latitude does not fit GRIB signed i32".into())
1930 })?);
1931 section[73..77].copy_from_slice(&encode_wmo_i32(grid.lat_southern_pole).ok_or_else(|| {
1932 Error::Other("latitude of southern pole does not fit GRIB signed i32".into())
1933 })?);
1934 section[77..81].copy_from_slice(&grid.lon_southern_pole.to_be_bytes());
1935 out.extend_from_slice(§ion);
1936 Ok(())
1937}
1938
1939fn write_lambert_conformal_grid_section(
1940 out: &mut Vec<u8>,
1941 grid: &LambertConformalGrid,
1942) -> Result<()> {
1943 checked_projected_point_count(
1944 grid.nx,
1945 grid.ny,
1946 grid.number_of_points,
1947 "Lambert conformal grid",
1948 )?;
1949
1950 let mut section = vec![0u8; 81];
1951 section[..4].copy_from_slice(&81u32.to_be_bytes());
1952 section[4] = 3;
1953 section[6..10].copy_from_slice(&grid.number_of_points.to_be_bytes());
1954 section[12..14].copy_from_slice(&30u16.to_be_bytes());
1955 write_projected_grid_shape_of_earth(
1956 &mut section,
1957 ProjectedGridShapeOfEarth {
1958 shape_of_earth: grid.shape_of_earth,
1959 scale_factor_radius: grid.scale_factor_radius,
1960 scaled_value_radius: grid.scaled_value_radius,
1961 scale_factor_major_axis: grid.scale_factor_major_axis,
1962 scaled_value_major_axis: grid.scaled_value_major_axis,
1963 scale_factor_minor_axis: grid.scale_factor_minor_axis,
1964 scaled_value_minor_axis: grid.scaled_value_minor_axis,
1965 },
1966 );
1967 section[30..34].copy_from_slice(&grid.nx.to_be_bytes());
1968 section[34..38].copy_from_slice(&grid.ny.to_be_bytes());
1969 section[38..42].copy_from_slice(&encode_wmo_i32(grid.lat_first).ok_or_else(|| {
1970 Error::Other("latitude of first grid point does not fit GRIB signed i32".into())
1971 })?);
1972 section[42..46].copy_from_slice(&grid.lon_first.to_be_bytes());
1973 section[46] = grid.resolution_and_component_flags;
1974 section[47..51].copy_from_slice(&encode_wmo_i32(grid.lat_d).ok_or_else(|| {
1975 Error::Other(
1976 "latitude where grid lengths are specified does not fit GRIB signed i32".into(),
1977 )
1978 })?);
1979 section[51..55].copy_from_slice(&grid.lon_v.to_be_bytes());
1980 section[55..59].copy_from_slice(&grid.dx.to_be_bytes());
1981 section[59..63].copy_from_slice(&grid.dy.to_be_bytes());
1982 section[63] = grid.projection_center_flag;
1983 section[64] = grid.scanning_mode;
1984 section[65..69].copy_from_slice(
1985 &encode_wmo_i32(grid.latin1).ok_or_else(|| {
1986 Error::Other("first Latin latitude does not fit GRIB signed i32".into())
1987 })?,
1988 );
1989 section[69..73].copy_from_slice(&encode_wmo_i32(grid.latin2).ok_or_else(|| {
1990 Error::Other("second Latin latitude does not fit GRIB signed i32".into())
1991 })?);
1992 section[73..77].copy_from_slice(&encode_wmo_i32(grid.lat_southern_pole).ok_or_else(|| {
1993 Error::Other("latitude of southern pole does not fit GRIB signed i32".into())
1994 })?);
1995 section[77..81].copy_from_slice(&grid.lon_southern_pole.to_be_bytes());
1996 out.extend_from_slice(§ion);
1997 Ok(())
1998}
1999
2000struct ProjectedGridShapeOfEarth {
2001 shape_of_earth: u8,
2002 scale_factor_radius: u8,
2003 scaled_value_radius: u32,
2004 scale_factor_major_axis: u8,
2005 scaled_value_major_axis: u32,
2006 scale_factor_minor_axis: u8,
2007 scaled_value_minor_axis: u32,
2008}
2009
2010fn write_projected_grid_shape_of_earth(section: &mut [u8], shape: ProjectedGridShapeOfEarth) {
2011 section[14] = shape.shape_of_earth;
2012 section[15] = shape.scale_factor_radius;
2013 section[16..20].copy_from_slice(&shape.scaled_value_radius.to_be_bytes());
2014 section[20] = shape.scale_factor_major_axis;
2015 section[21..25].copy_from_slice(&shape.scaled_value_major_axis.to_be_bytes());
2016 section[25] = shape.scale_factor_minor_axis;
2017 section[26..30].copy_from_slice(&shape.scaled_value_minor_axis.to_be_bytes());
2018}
2019
2020fn write_product_section(out: &mut Vec<u8>, product: &ProductDefinition) -> Result<()> {
2021 match &product.template {
2022 ProductDefinitionTemplate::AnalysisOrForecast(template) => {
2023 write_product_template_prefix(out, product, 0, 34, template)
2024 }
2025 ProductDefinitionTemplate::IndividualEnsembleForecast(template) => {
2026 write_product_template_prefix(out, product, 1, 37, &template.base)?;
2027 write_ensemble_product_extra(out, template)
2028 }
2029 ProductDefinitionTemplate::StatisticalProcess(template) => {
2030 let range_count = checked_time_range_count(template.time_ranges.len())?;
2031 let section_length = statistical_product_section_len(46, range_count)?;
2032 write_product_template_prefix(out, product, 8, section_length, &template.base)?;
2033 write_reference_time(out, template.end_of_overall_time_interval)?;
2034 write_u8_be(out, range_count)?;
2035 write_u32_be(out, template.number_of_missing_in_statistical_process)?;
2036 write_statistical_time_ranges(out, &template.time_ranges)
2037 }
2038 ProductDefinitionTemplate::EnsembleStatisticalProcess(template) => {
2039 let range_count = checked_time_range_count(template.time_ranges.len())?;
2040 let section_length = statistical_product_section_len(49, range_count)?;
2041 write_product_template_prefix(
2042 out,
2043 product,
2044 11,
2045 section_length,
2046 &template.ensemble.base,
2047 )?;
2048 write_ensemble_product_extra(out, &template.ensemble)?;
2049 write_reference_time(out, template.end_of_overall_time_interval)?;
2050 write_u8_be(out, range_count)?;
2051 write_u32_be(out, template.number_of_missing_in_statistical_process)?;
2052 write_statistical_time_ranges(out, &template.time_ranges)
2053 }
2054 }
2055}
2056
2057fn write_product_template_prefix(
2058 out: &mut Vec<u8>,
2059 product: &ProductDefinition,
2060 template_number: u16,
2061 section_length: u32,
2062 template: &AnalysisOrForecastTemplate,
2063) -> Result<()> {
2064 write_u32_be(out, section_length)?;
2065 write_u8_be(out, 4)?;
2066 write_u16_be(out, 0)?;
2067 write_u16_be(out, template_number)?;
2068 write_u8_be(out, product.parameter_category)?;
2069 write_u8_be(out, product.parameter_number)?;
2070 write_u8_be(out, template.generating_process)?;
2071 write_u8_be(out, 0)?;
2072 write_u8_be(out, 0)?;
2073 write_u16_be(out, 0)?;
2074 write_u8_be(out, 0)?;
2075 write_u8_be(out, template.forecast_time_unit)?;
2076 write_u32_be(out, template.forecast_time)?;
2077 write_surface(out, template.first_surface.as_ref())?;
2078 write_surface(out, template.second_surface.as_ref())
2079}
2080
2081fn write_ensemble_product_extra(
2082 out: &mut Vec<u8>,
2083 template: &grib_core::IndividualEnsembleForecastTemplate,
2084) -> Result<()> {
2085 write_u8_be(out, template.type_of_ensemble_forecast)?;
2086 write_u8_be(out, template.perturbation_number)?;
2087 write_u8_be(out, template.number_of_forecasts_in_ensemble)
2088}
2089
2090fn write_reference_time(out: &mut Vec<u8>, reference_time: ReferenceTime) -> Result<()> {
2091 validate_reference_time(reference_time)?;
2092
2093 write_u16_be(out, reference_time.year)?;
2094 write_u8_be(out, reference_time.month)?;
2095 write_u8_be(out, reference_time.day)?;
2096 write_u8_be(out, reference_time.hour)?;
2097 write_u8_be(out, reference_time.minute)?;
2098 write_u8_be(out, reference_time.second)
2099}
2100
2101fn validate_reference_time(reference_time: ReferenceTime) -> Result<()> {
2102 if reference_time.is_valid() {
2103 return Ok(());
2104 }
2105
2106 Err(Error::InvalidSection {
2107 section: 4,
2108 reason: format!(
2109 "invalid reference timestamp {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
2110 reference_time.year,
2111 reference_time.month,
2112 reference_time.day,
2113 reference_time.hour,
2114 reference_time.minute,
2115 reference_time.second
2116 ),
2117 })
2118}
2119
2120fn checked_time_range_count(range_count: usize) -> Result<u8> {
2121 u8::try_from(range_count).map_err(|_| {
2122 Error::Other(format!(
2123 "statistical product time-range count ({range_count}) exceeds GRIB2 u8 limit"
2124 ))
2125 })
2126}
2127
2128fn statistical_product_section_len(base_len: u32, range_count: u8) -> Result<u32> {
2129 u32::from(range_count)
2130 .checked_mul(12)
2131 .and_then(|ranges_len| base_len.checked_add(ranges_len))
2132 .ok_or_else(|| Error::Other("statistical product section length overflow".into()))
2133}
2134
2135fn write_statistical_time_ranges(out: &mut Vec<u8>, ranges: &[StatisticalTimeRange]) -> Result<()> {
2136 for range in ranges {
2137 write_u8_be(out, range.type_of_statistical_processing)?;
2138 write_u8_be(out, range.type_of_time_increment)?;
2139 write_u8_be(out, range.time_range_unit)?;
2140 write_u32_be(out, range.time_range_length)?;
2141 write_u8_be(out, range.time_increment_unit)?;
2142 write_u32_be(out, range.time_increment)?;
2143 }
2144 Ok(())
2145}
2146
2147fn write_surface(out: &mut Vec<u8>, surface: Option<&FixedSurface>) -> Result<()> {
2148 match surface {
2149 Some(surface) => {
2150 write_u8_be(out, surface.surface_type)?;
2151 write_u8_be(
2152 out,
2153 encode_wmo_i8(surface.scale_factor).ok_or_else(|| {
2154 Error::Other("fixed-surface scale factor does not fit GRIB signed i8".into())
2155 })?,
2156 )?;
2157 out.extend_from_slice(&encode_wmo_i32(surface.scaled_value).ok_or_else(|| {
2158 Error::Other("fixed-surface scaled value does not fit GRIB signed i32".into())
2159 })?);
2160 Ok(())
2161 }
2162 None => {
2163 write_u8_be(out, 255)?;
2164 out.extend_from_slice(&[0xff; 5]);
2165 Ok(())
2166 }
2167 }
2168}
2169
2170fn write_data_representation_section(out: &mut Vec<u8>, packed: &PackedField) -> Result<()> {
2171 match &packed.representation {
2172 DataRepresentation::SimplePacking(params) => {
2173 write_simple_data_representation_section(out, params)
2174 }
2175 DataRepresentation::ComplexPacking(params) => {
2176 write_complex_data_representation_section(out, params)
2177 }
2178 DataRepresentation::Jpeg2000Packing(params) => {
2179 write_jpeg2000_data_representation_section(out, params)
2180 }
2181 DataRepresentation::PngPacking(params) => {
2182 write_png_data_representation_section(out, params)
2183 }
2184 DataRepresentation::Unsupported(template) => Err(Error::UnsupportedDataTemplate(*template)),
2185 }
2186}
2187
2188fn write_simple_data_representation_section(
2189 out: &mut Vec<u8>,
2190 params: &SimplePackingParams,
2191) -> Result<()> {
2192 let encoded_values = u32::try_from(params.encoded_values)
2193 .map_err(|_| Error::Other("encoded value count exceeds u32".into()))?;
2194 write_u32_be(out, 21)?;
2195 write_u8_be(out, 5)?;
2196 write_u32_be(out, encoded_values)?;
2197 write_u16_be(out, 0)?;
2198 out.extend_from_slice(¶ms.reference_value.to_be_bytes());
2199 out.extend_from_slice(
2200 &encode_wmo_i16(params.binary_scale)
2201 .ok_or_else(|| Error::Other("binary scale does not fit GRIB signed i16".into()))?,
2202 );
2203 out.extend_from_slice(
2204 &encode_wmo_i16(params.decimal_scale)
2205 .ok_or_else(|| Error::Other("decimal scale does not fit GRIB signed i16".into()))?,
2206 );
2207 write_u8_be(out, params.bits_per_value)?;
2208 write_u8_be(out, params.original_field_type)
2209}
2210
2211fn write_complex_data_representation_section(
2212 out: &mut Vec<u8>,
2213 params: &ComplexPackingParams,
2214) -> Result<()> {
2215 let encoded_values = u32::try_from(params.encoded_values)
2216 .map_err(|_| Error::Other("encoded value count exceeds u32".into()))?;
2217 let num_groups = u32::try_from(params.num_groups)
2218 .map_err(|_| Error::Other("complex group count exceeds u32".into()))?;
2219 let template = if params.spatial_differencing.is_some() {
2220 3
2221 } else {
2222 2
2223 };
2224 let section_length = if params.spatial_differencing.is_some() {
2225 49
2226 } else {
2227 47
2228 };
2229
2230 write_u32_be(out, section_length)?;
2231 write_u8_be(out, 5)?;
2232 write_u32_be(out, encoded_values)?;
2233 write_u16_be(out, template)?;
2234 out.extend_from_slice(¶ms.reference_value.to_be_bytes());
2235 out.extend_from_slice(
2236 &encode_wmo_i16(params.binary_scale)
2237 .ok_or_else(|| Error::Other("binary scale does not fit GRIB signed i16".into()))?,
2238 );
2239 out.extend_from_slice(
2240 &encode_wmo_i16(params.decimal_scale)
2241 .ok_or_else(|| Error::Other("decimal scale does not fit GRIB signed i16".into()))?,
2242 );
2243 write_u8_be(out, params.group_reference_bits)?;
2244 write_u8_be(out, params.original_field_type)?;
2245 write_u8_be(out, params.group_splitting_method)?;
2246 write_u8_be(out, params.missing_value_management)?;
2247 write_u32_be(out, params.primary_missing_substitute)?;
2248 write_u32_be(out, params.secondary_missing_substitute)?;
2249 write_u32_be(out, num_groups)?;
2250 write_u8_be(out, params.group_width_reference)?;
2251 write_u8_be(out, params.group_width_bits)?;
2252 write_u32_be(out, params.group_length_reference)?;
2253 write_u8_be(out, params.group_length_increment)?;
2254 write_u32_be(out, params.true_length_last_group)?;
2255 write_u8_be(out, params.scaled_group_length_bits)?;
2256 if let Some(spatial) = params.spatial_differencing {
2257 write_u8_be(out, spatial.order)?;
2258 write_u8_be(out, spatial.descriptor_octets)?;
2259 }
2260 Ok(())
2261}
2262
2263fn write_jpeg2000_data_representation_section(
2264 out: &mut Vec<u8>,
2265 params: &Jpeg2000PackingParams,
2266) -> Result<()> {
2267 write_image_data_representation_base(out, 23, 40, ¶ms.packing)?;
2268 write_u8_be(out, params.compression_type)?;
2269 write_u8_be(out, params.target_compression_ratio)
2270}
2271
2272fn write_png_data_representation_section(
2273 out: &mut Vec<u8>,
2274 params: &PngPackingParams,
2275) -> Result<()> {
2276 write_image_data_representation_base(out, 21, 41, ¶ms.packing)
2277}
2278
2279fn write_image_data_representation_base(
2280 out: &mut Vec<u8>,
2281 section_length: u32,
2282 template: u16,
2283 params: &ImagePackingParams,
2284) -> Result<()> {
2285 let encoded_values = u32::try_from(params.encoded_values)
2286 .map_err(|_| Error::Other("encoded value count exceeds u32".into()))?;
2287 write_u32_be(out, section_length)?;
2288 write_u8_be(out, 5)?;
2289 write_u32_be(out, encoded_values)?;
2290 write_u16_be(out, template)?;
2291 out.extend_from_slice(¶ms.reference_value.to_be_bytes());
2292 out.extend_from_slice(
2293 &encode_wmo_i16(params.binary_scale)
2294 .ok_or_else(|| Error::Other("binary scale does not fit GRIB signed i16".into()))?,
2295 );
2296 out.extend_from_slice(
2297 &encode_wmo_i16(params.decimal_scale)
2298 .ok_or_else(|| Error::Other("decimal scale does not fit GRIB signed i16".into()))?,
2299 );
2300 write_u8_be(out, params.bits_per_value)?;
2301 write_u8_be(out, params.original_field_type)
2302}
2303
2304fn write_bitmap_section(out: &mut Vec<u8>, bitmap_payload: &[u8]) -> Result<()> {
2305 let length = checked_section_length(6usize + bitmap_payload.len(), 6)?;
2306 write_u32_be(out, length)?;
2307 write_u8_be(out, 6)?;
2308 write_u8_be(out, 0)?;
2309 out.extend_from_slice(bitmap_payload);
2310 Ok(())
2311}
2312
2313fn write_data_section(out: &mut Vec<u8>, data_payload: &[u8]) -> Result<()> {
2314 let length = checked_section_length(5usize + data_payload.len(), 7)?;
2315 write_u32_be(out, length)?;
2316 write_u8_be(out, 7)?;
2317 out.extend_from_slice(data_payload);
2318 Ok(())
2319}
2320
2321fn checked_section_length(length: usize, section: u8) -> Result<u32> {
2322 u32::try_from(length).map_err(|_| Error::InvalidSection {
2323 section,
2324 reason: format!("section length {length} exceeds u32"),
2325 })
2326}
2327
2328fn checked_grid_point_count(grid: &GridDefinition) -> Result<usize> {
2329 match grid {
2330 GridDefinition::LatLon(grid) => Ok(checked_latlon_point_count(grid)? as usize),
2331 GridDefinition::Mercator(grid) => {
2332 checked_projected_point_count(grid.ni, grid.nj, grid.number_of_points, "Mercator grid")
2333 }
2334 GridDefinition::PolarStereographic(grid) => checked_projected_point_count(
2335 grid.nx,
2336 grid.ny,
2337 grid.number_of_points,
2338 "polar stereographic grid",
2339 ),
2340 GridDefinition::LambertConformal(grid) => checked_projected_point_count(
2341 grid.nx,
2342 grid.ny,
2343 grid.number_of_points,
2344 "Lambert conformal grid",
2345 ),
2346 GridDefinition::AlbersEqualArea(grid) => checked_projected_point_count(
2347 grid.nx,
2348 grid.ny,
2349 grid.number_of_points,
2350 "Albers equal-area grid",
2351 ),
2352 _ => Err(Error::UnsupportedGridTemplate(grid.template_number())),
2353 }
2354}
2355
2356fn checked_latlon_point_count(grid: &LatLonGrid) -> Result<u32> {
2357 let count = u64::from(grid.ni)
2358 .checked_mul(u64::from(grid.nj))
2359 .ok_or_else(|| Error::Other("grid point count overflow".into()))?;
2360 u32::try_from(count).map_err(|_| Error::Other("grid point count exceeds u32".into()))
2361}
2362
2363fn checked_projected_point_count(
2364 nx: u32,
2365 ny: u32,
2366 number_of_points: u32,
2367 grid_name: &str,
2368) -> Result<usize> {
2369 let expected = u64::from(nx)
2370 .checked_mul(u64::from(ny))
2371 .ok_or_else(|| Error::Other(format!("{grid_name} point count overflow")))?;
2372 let expected = u32::try_from(expected)
2373 .map_err(|_| Error::Other(format!("{grid_name} point count exceeds u32")))?;
2374 if number_of_points != expected {
2375 return Err(Error::Other(format!(
2376 "{grid_name} number_of_points ({number_of_points}) does not match Nx*Ny ({expected})"
2377 )));
2378 }
2379 usize::try_from(number_of_points)
2380 .map_err(|_| Error::Other(format!("{grid_name} point count exceeds usize")))
2381}
2382
2383fn validate_supported_grid(grid: &GridDefinition) -> Result<()> {
2384 grid.validate_supported_scan_order()
2385}
2386
2387fn validate_supported_scan_order(grid: &LatLonGrid) -> Result<()> {
2388 if grid.scanning_mode & 0b0010_0000 == 0 {
2389 Ok(())
2390 } else {
2391 Err(Error::UnsupportedScanningMode(grid.scanning_mode))
2392 }
2393}
2394
2395fn validate_supported_grib1_grid(grid: &GridDefinition) -> Result<()> {
2396 let Some(grid) = grid.as_lat_lon() else {
2397 return Err(Error::UnsupportedGridTemplate(grid.template_number()));
2398 };
2399 validate_supported_scan_order(grid)?;
2400 checked_grib1_grid_dimension(grid.ni, "Ni")?;
2401 checked_grib1_grid_dimension(grid.nj, "Nj")?;
2402 checked_grib1_increment(grid.di, "i direction increment")?;
2403 checked_grib1_increment(grid.dj, "j direction increment")?;
2404 encode_grib1_coordinate(grid.lat_first, "latitude of first grid point")?;
2405 encode_grib1_coordinate(grid.lon_first, "longitude of first grid point")?;
2406 encode_grib1_coordinate(grid.lat_last, "latitude of last grid point")?;
2407 encode_grib1_coordinate(grid.lon_last, "longitude of last grid point")?;
2408 Ok(())
2409}
2410
2411fn validate_supported_product(product: &ProductDefinition) -> Result<()> {
2412 match &product.template {
2413 ProductDefinitionTemplate::AnalysisOrForecast(_) => Ok(()),
2414 ProductDefinitionTemplate::IndividualEnsembleForecast(_) => Ok(()),
2415 ProductDefinitionTemplate::StatisticalProcess(template) => {
2416 checked_time_range_count(template.time_ranges.len())?;
2417 validate_reference_time(template.end_of_overall_time_interval)
2418 }
2419 ProductDefinitionTemplate::EnsembleStatisticalProcess(template) => {
2420 checked_time_range_count(template.time_ranges.len())?;
2421 validate_reference_time(template.end_of_overall_time_interval)
2422 }
2423 }
2424}
2425
2426#[cfg(test)]
2427mod tests {
2428 use super::{
2429 Grib1FieldBuilder, Grib1ProductDefinition, Grib2FieldBuilder, GribWriter, PackingStrategy,
2430 SpatialDifferencingOrder, ValueOrder,
2431 };
2432 use std::process::Command;
2433
2434 use grib_core::binary::decode_ibm_f32;
2435 use grib_core::metadata::ReferenceTime;
2436 use grib_core::{
2437 AlbersEqualAreaGrid, AnalysisOrForecastTemplate, DataRepresentation,
2438 EnsembleStatisticalProcessTemplate, FixedSurface, GridDefinition, Identification,
2439 IndividualEnsembleForecastTemplate, LambertConformalGrid, LatLonGrid, MercatorGrid,
2440 PolarStereographicGrid, ProductDefinition, ProductDefinitionTemplate,
2441 StatisticalProcessTemplate, StatisticalTimeRange,
2442 };
2443 use grib_reader::sections::scan_sections;
2444 use grib_reader::{GribFile, OpenOptions, PredefinedBitmap};
2445 use serde::Deserialize;
2446
2447 fn identification() -> Identification {
2448 Identification {
2449 center_id: 7,
2450 subcenter_id: 0,
2451 master_table_version: 35,
2452 local_table_version: 1,
2453 significance_of_reference_time: 1,
2454 reference_year: 2026,
2455 reference_month: 3,
2456 reference_day: 20,
2457 reference_hour: 12,
2458 reference_minute: 0,
2459 reference_second: 0,
2460 production_status: 0,
2461 processed_data_type: 1,
2462 }
2463 }
2464
2465 fn grib1_product() -> Grib1ProductDefinition {
2466 Grib1ProductDefinition {
2467 table_version: 2,
2468 center_id: 7,
2469 generating_process_id: 255,
2470 grid_id: 0,
2471 has_grid_definition: true,
2472 has_bitmap: false,
2473 parameter_number: 11,
2474 level_type: 100,
2475 level_value: 850,
2476 reference_time: ReferenceTime {
2477 year: 2026,
2478 month: 3,
2479 day: 20,
2480 hour: 12,
2481 minute: 0,
2482 second: 0,
2483 },
2484 forecast_time_unit: 1,
2485 p1: 6,
2486 p2: 0,
2487 time_range_indicator: 0,
2488 average_count: 0,
2489 missing_count: 0,
2490 century: 21,
2491 subcenter_id: 0,
2492 decimal_scale: 0,
2493 }
2494 }
2495
2496 fn grid() -> GridDefinition {
2497 grid_with_shape_and_scanning_mode(2, 2, 0)
2498 }
2499
2500 fn grid_with_scanning_mode(scanning_mode: u8) -> GridDefinition {
2501 grid_with_shape_and_scanning_mode(3, 2, scanning_mode)
2502 }
2503
2504 fn grid_with_shape_and_scanning_mode(ni: u32, nj: u32, scanning_mode: u8) -> GridDefinition {
2505 let lon_first = -120_000_000;
2506 let lat_first = 50_000_000;
2507 let di = 1_000_000;
2508 let dj = 1_000_000;
2509 let i_step = if scanning_mode & 0b1000_0000 == 0 {
2510 di as i32
2511 } else {
2512 -(di as i32)
2513 };
2514 let j_step = if scanning_mode & 0b0100_0000 != 0 {
2515 dj as i32
2516 } else {
2517 -(dj as i32)
2518 };
2519
2520 GridDefinition::LatLon(LatLonGrid {
2521 ni,
2522 nj,
2523 lat_first,
2524 lon_first,
2525 lat_last: lat_first + (nj.saturating_sub(1) as i32) * j_step,
2526 lon_last: lon_first + (ni.saturating_sub(1) as i32) * i_step,
2527 di,
2528 dj,
2529 scanning_mode,
2530 })
2531 }
2532
2533 fn polar_grid(scanning_mode: u8) -> GridDefinition {
2534 GridDefinition::PolarStereographic(PolarStereographicGrid {
2535 number_of_points: 6,
2536 shape_of_earth: 6,
2537 scale_factor_radius: 0,
2538 scaled_value_radius: 0,
2539 scale_factor_major_axis: 0,
2540 scaled_value_major_axis: 0,
2541 scale_factor_minor_axis: 0,
2542 scaled_value_minor_axis: 0,
2543 nx: 3,
2544 ny: 2,
2545 lat_first: 41_612_949,
2546 lon_first: 185_117_126,
2547 resolution_and_component_flags: 0x08,
2548 lat_d: 60_000_000,
2549 lon_v: 225_000_000,
2550 dx: 3_000_000,
2551 dy: 3_000_000,
2552 projection_center_flag: 0,
2553 scanning_mode,
2554 })
2555 }
2556
2557 fn mercator_grid(scanning_mode: u8) -> GridDefinition {
2558 GridDefinition::Mercator(MercatorGrid {
2559 number_of_points: 6,
2560 shape_of_earth: 6,
2561 scale_factor_radius: 0,
2562 scaled_value_radius: 0,
2563 scale_factor_major_axis: 0,
2564 scaled_value_major_axis: 0,
2565 scale_factor_minor_axis: 0,
2566 scaled_value_minor_axis: 0,
2567 ni: 3,
2568 nj: 2,
2569 lat_first: -20_000_000,
2570 lon_first: -100_000_000,
2571 resolution_and_component_flags: 0x08,
2572 lat_d: 0,
2573 lat_last: 20_000_000,
2574 lon_last: -98_000_000,
2575 scanning_mode,
2576 orientation_of_grid: 0,
2577 di: 1_000_000,
2578 dj: 2_000_000,
2579 })
2580 }
2581
2582 fn lambert_grid(scanning_mode: u8) -> GridDefinition {
2583 GridDefinition::LambertConformal(LambertConformalGrid {
2584 number_of_points: 6,
2585 shape_of_earth: 1,
2586 scale_factor_radius: 0,
2587 scaled_value_radius: 6_371_200,
2588 scale_factor_major_axis: 0,
2589 scaled_value_major_axis: 0,
2590 scale_factor_minor_axis: 0,
2591 scaled_value_minor_axis: 0,
2592 nx: 3,
2593 ny: 2,
2594 lat_first: 12_190_000,
2595 lon_first: 226_541_000,
2596 resolution_and_component_flags: 0x08,
2597 lat_d: 25_000_000,
2598 lon_v: 265_000_000,
2599 dx: 2_539_703,
2600 dy: 2_539_703,
2601 projection_center_flag: 0,
2602 scanning_mode,
2603 latin1: 25_000_000,
2604 latin2: 25_000_000,
2605 lat_southern_pole: -90_000_000,
2606 lon_southern_pole: 0,
2607 })
2608 }
2609
2610 fn albers_grid(scanning_mode: u8) -> GridDefinition {
2611 GridDefinition::AlbersEqualArea(AlbersEqualAreaGrid {
2612 number_of_points: 6,
2613 shape_of_earth: 6,
2614 scale_factor_radius: 0,
2615 scaled_value_radius: 0,
2616 scale_factor_major_axis: 0,
2617 scaled_value_major_axis: 0,
2618 scale_factor_minor_axis: 0,
2619 scaled_value_minor_axis: 0,
2620 nx: 3,
2621 ny: 2,
2622 lat_first: 23_000_000,
2623 lon_first: 240_000_000,
2624 resolution_and_component_flags: 0x08,
2625 lat_d: 25_000_000,
2626 lon_v: 265_000_000,
2627 dx: 4_000_000,
2628 dy: 5_000_000,
2629 projection_center_flag: 0,
2630 scanning_mode,
2631 latin1: 29_500_000,
2632 latin2: 45_500_000,
2633 lat_southern_pole: -90_000_000,
2634 lon_southern_pole: 0,
2635 })
2636 }
2637
2638 fn product(parameter_category: u8, parameter_number: u8) -> ProductDefinition {
2639 ProductDefinition {
2640 parameter_category,
2641 parameter_number,
2642 template: ProductDefinitionTemplate::AnalysisOrForecast(analysis_or_forecast_template()),
2643 }
2644 }
2645
2646 fn analysis_or_forecast_template() -> AnalysisOrForecastTemplate {
2647 AnalysisOrForecastTemplate {
2648 generating_process: 2,
2649 forecast_time_unit: 1,
2650 forecast_time: 6,
2651 first_surface: Some(FixedSurface {
2652 surface_type: 103,
2653 scale_factor: 0,
2654 scaled_value: 850,
2655 }),
2656 second_surface: None,
2657 }
2658 }
2659
2660 fn statistical_time_range() -> StatisticalTimeRange {
2661 StatisticalTimeRange {
2662 type_of_statistical_processing: 1,
2663 type_of_time_increment: 2,
2664 time_range_unit: 1,
2665 time_range_length: 6,
2666 time_increment_unit: 255,
2667 time_increment: 0,
2668 }
2669 }
2670
2671 fn interval_end_time() -> ReferenceTime {
2672 ReferenceTime {
2673 year: 2026,
2674 month: 3,
2675 day: 20,
2676 hour: 18,
2677 minute: 0,
2678 second: 0,
2679 }
2680 }
2681
2682 fn write_message(fields: impl IntoIterator<Item = super::Grib2Field>) -> Vec<u8> {
2683 let mut bytes = Vec::new();
2684 GribWriter::new(&mut bytes)
2685 .write_grib2_message(fields)
2686 .unwrap();
2687 bytes
2688 }
2689
2690 fn write_grib1_message(field: super::Grib1Field) -> Vec<u8> {
2691 let mut bytes = Vec::new();
2692 GribWriter::new(&mut bytes)
2693 .write_grib1_message(field)
2694 .unwrap();
2695 bytes
2696 }
2697
2698 fn section_numbers(bytes: &[u8]) -> Vec<u8> {
2699 scan_sections(bytes)
2700 .unwrap()
2701 .iter()
2702 .map(|section| section.number)
2703 .collect()
2704 }
2705
2706 #[cfg(any(feature = "jpeg2000", feature = "png"))]
2707 fn section_payload(bytes: &[u8], section_number: u8) -> &[u8] {
2708 let section = scan_sections(bytes)
2709 .unwrap()
2710 .into_iter()
2711 .find(|section| section.number == section_number)
2712 .unwrap();
2713 &bytes[section.offset + 5..section.offset + section.length]
2714 }
2715
2716 fn simple_field(
2717 values: &[f64],
2718 parameter_category: u8,
2719 parameter_number: u8,
2720 ) -> super::Grib2Field {
2721 Grib2FieldBuilder::new()
2722 .identification(identification())
2723 .grid(grid())
2724 .product(product(parameter_category, parameter_number))
2725 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2726 .values(values)
2727 .build()
2728 .unwrap()
2729 }
2730
2731 fn grib1_simple_field(values: &[f64]) -> super::Grib1Field {
2732 Grib1FieldBuilder::new()
2733 .product(grib1_product())
2734 .grid(grid())
2735 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2736 .values(values)
2737 .build()
2738 .unwrap()
2739 }
2740
2741 #[test]
2742 fn writes_simple_grib1_field_readable_by_reader() {
2743 let values = [1.0, 2.0, 3.0, 4.0];
2744 let bytes = write_grib1_message(grib1_simple_field(&values));
2745
2746 let file = GribFile::from_bytes(bytes).unwrap();
2747 let message = file.message(0).unwrap();
2748 assert_eq!(file.edition(), 1);
2749 assert_eq!(file.message_count(), 1);
2750 assert_eq!(message.parameter_name(), "TMP");
2751 assert_eq!(message.grid_shape(), (2, 2));
2752 assert_eq!(message.forecast_time(), Some(6));
2753 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
2754 }
2755
2756 #[test]
2757 fn writes_grib1_bitmap_from_nan_values() {
2758 let values = [5.0, f64::NAN, 7.0, 8.0];
2759 let bytes = write_grib1_message(grib1_simple_field(&values));
2760 let bitmap_offset = 8 + 28 + 32;
2761 assert_eq!(&bytes[bitmap_offset + 4..bitmap_offset + 6], &[0, 0]);
2762
2763 let file = GribFile::from_bytes(bytes).unwrap();
2764 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
2765 assert_eq!(decoded[0], 5.0);
2766 assert!(decoded[1].is_nan());
2767 assert_eq!(decoded[2], 7.0);
2768 assert_eq!(decoded[3], 8.0);
2769 }
2770
2771 #[test]
2772 fn writes_grib1_bitmap_from_explicit_mask() {
2773 let field = Grib1FieldBuilder::new()
2774 .product(grib1_product())
2775 .grid(grid())
2776 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2777 .values(&[5.0, 999.0, 7.0, 8.0])
2778 .bitmap(&[true, false, true, true])
2779 .build()
2780 .unwrap();
2781
2782 let file = GribFile::from_bytes(write_grib1_message(field)).unwrap();
2783 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
2784 assert_eq!(decoded[0], 5.0);
2785 assert!(decoded[1].is_nan());
2786 assert_eq!(decoded[2], 7.0);
2787 assert_eq!(decoded[3], 8.0);
2788 }
2789
2790 #[test]
2791 fn writes_grib1_predefined_bitmap_reference() {
2792 let bitmap = [true, false, true, false];
2793 let field = Grib1FieldBuilder::new()
2794 .product(grib1_product())
2795 .grid(grid())
2796 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2797 .values(&[9.0, 999.0, 7.0, 999.0])
2798 .predefined_bitmap(300, &bitmap)
2799 .build()
2800 .unwrap();
2801 let bytes = write_grib1_message(field);
2802
2803 let bitmap_offset = 8 + 28 + 32;
2804 assert_eq!(
2805 &bytes[bitmap_offset..bitmap_offset + 6],
2806 &[0, 0, 6, 0, 1, 44]
2807 );
2808
2809 let err = match GribFile::from_bytes(bytes.clone()) {
2810 Ok(_) => panic!("expected unsupported predefined bitmap"),
2811 Err(err) => err,
2812 };
2813 assert!(matches!(
2814 err,
2815 grib_core::Error::UnsupportedBitmapIndicator(300)
2816 ));
2817
2818 let bitmap_payload = [0b1010_0000];
2819 let predefined = [PredefinedBitmap {
2820 center_id: 7,
2821 subcenter_id: Some(0),
2822 table_reference: 300,
2823 bitmap: &bitmap_payload,
2824 }];
2825 let file = GribFile::from_bytes_with_grib1_predefined_bitmaps(
2826 bytes,
2827 OpenOptions::default(),
2828 &predefined,
2829 )
2830 .unwrap();
2831 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
2832 assert_eq!(decoded[0], 9.0);
2833 assert!(decoded[1].is_nan());
2834 assert_eq!(decoded[2], 7.0);
2835 assert!(decoded[3].is_nan());
2836 }
2837
2838 #[test]
2839 fn rejects_zero_grib1_predefined_bitmap_reference() {
2840 let err = Grib1FieldBuilder::new()
2841 .product(grib1_product())
2842 .grid(grid())
2843 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2844 .values(&[1.0, 999.0, 3.0, 999.0])
2845 .predefined_bitmap(0, &[true, false, true, false])
2846 .build()
2847 .unwrap_err();
2848
2849 assert!(
2850 matches!(err, grib_core::Error::Other(message) if message.contains("must be nonzero"))
2851 );
2852 }
2853
2854 #[test]
2855 fn writes_grib1_ibm_float_reference_value() {
2856 let bytes = write_grib1_message(grib1_simple_field(&[10.0, 11.0, 12.0, 13.0]));
2857 let bds_offset = 8 + 28 + 32;
2858 let reference = decode_ibm_f32(bytes[bds_offset + 6..bds_offset + 10].try_into().unwrap());
2859 assert_eq!(reference, 10.0);
2860
2861 let file = GribFile::from_bytes(bytes).unwrap();
2862 assert_eq!(
2863 file.message(0).unwrap().read_flat_data_as_f64().unwrap(),
2864 vec![10.0, 11.0, 12.0, 13.0]
2865 );
2866 }
2867
2868 #[test]
2869 fn rejects_grib1_u24_length_overflow() {
2870 let err = super::checked_grib1_u24_length(grib_core::binary::U24_MAX as usize + 1, 0)
2871 .unwrap_err();
2872 assert!(matches!(
2873 err,
2874 grib_core::Error::InvalidSection { section: 0, .. }
2875 ));
2876 }
2877
2878 #[test]
2879 fn rejects_unsupported_grib1_binary_data_flags() {
2880 let err = super::validate_grib1_binary_data_flags(0b0001).unwrap_err();
2881 assert!(matches!(
2882 err,
2883 grib_core::Error::UnsupportedDataTemplate(1007)
2884 ));
2885 }
2886
2887 #[test]
2888 fn rejects_grib1_grid_dimensions_beyond_u16() {
2889 let err = Grib1FieldBuilder::new()
2890 .product(grib1_product())
2891 .grid(GridDefinition::LatLon(LatLonGrid {
2892 ni: 65_536,
2893 nj: 1,
2894 lat_first: 0,
2895 lon_first: 0,
2896 lat_last: 0,
2897 lon_last: 0,
2898 di: 1_000,
2899 dj: 1_000,
2900 scanning_mode: 0,
2901 }))
2902 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2903 .values(&[1.0])
2904 .build()
2905 .unwrap_err();
2906 assert!(matches!(err, grib_core::Error::Other(message) if message.contains("Ni exceeds")));
2907 }
2908
2909 #[test]
2910 fn writes_simple_grib2_field_readable_by_reader() {
2911 let values = [1.0, 2.0, 3.0, 4.0];
2912 let field = simple_field(&values, 0, 0);
2913
2914 let file = GribFile::from_bytes(write_message([field])).unwrap();
2915 let message = file.message(0).unwrap();
2916 assert_eq!(message.parameter_name(), "TMP");
2917 assert_eq!(message.grid_shape(), (2, 2));
2918 assert_eq!(message.forecast_time(), Some(6));
2919 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
2920 }
2921
2922 #[test]
2923 fn writes_individual_ensemble_product_template_readable_by_reader() {
2924 let values = [1.0, 2.0, 3.0, 4.0];
2925 let field = Grib2FieldBuilder::new()
2926 .identification(identification())
2927 .grid(grid())
2928 .product(ProductDefinition {
2929 parameter_category: 0,
2930 parameter_number: 0,
2931 template: ProductDefinitionTemplate::IndividualEnsembleForecast(
2932 IndividualEnsembleForecastTemplate {
2933 base: analysis_or_forecast_template(),
2934 type_of_ensemble_forecast: 1,
2935 perturbation_number: 2,
2936 number_of_forecasts_in_ensemble: 20,
2937 },
2938 ),
2939 })
2940 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2941 .values(&values)
2942 .build()
2943 .unwrap();
2944
2945 let bytes = write_message([field]);
2946 let product_section = scan_sections(&bytes)
2947 .unwrap()
2948 .into_iter()
2949 .find(|section| section.number == 4)
2950 .unwrap();
2951 assert_eq!(product_section.length, 37);
2952
2953 let file = GribFile::from_bytes(bytes).unwrap();
2954 let message = file.message(0).unwrap();
2955 let product = message.product_definition().unwrap();
2956 assert_eq!(product.template_number(), 1);
2957 match &product.template {
2958 ProductDefinitionTemplate::IndividualEnsembleForecast(template) => {
2959 assert_eq!(template.type_of_ensemble_forecast, 1);
2960 assert_eq!(template.perturbation_number, 2);
2961 assert_eq!(template.number_of_forecasts_in_ensemble, 20);
2962 assert_eq!(template.base.forecast_time, 6);
2963 }
2964 other => panic!("expected template 4.1, got {other:?}"),
2965 }
2966 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
2967 }
2968
2969 #[test]
2970 fn writes_statistical_product_template_readable_by_reader() {
2971 let values = [1.0, 2.0, 3.0, 4.0];
2972 let mut base = analysis_or_forecast_template();
2973 base.forecast_time = 1;
2974 let field = Grib2FieldBuilder::new()
2975 .identification(identification())
2976 .grid(grid())
2977 .product(ProductDefinition {
2978 parameter_category: 0,
2979 parameter_number: 1,
2980 template: ProductDefinitionTemplate::StatisticalProcess(
2981 StatisticalProcessTemplate {
2982 base,
2983 end_of_overall_time_interval: interval_end_time(),
2984 number_of_missing_in_statistical_process: 0,
2985 time_ranges: vec![statistical_time_range()],
2986 },
2987 ),
2988 })
2989 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
2990 .values(&values)
2991 .build()
2992 .unwrap();
2993
2994 let bytes = write_message([field]);
2995 let product_section = scan_sections(&bytes)
2996 .unwrap()
2997 .into_iter()
2998 .find(|section| section.number == 4)
2999 .unwrap();
3000 assert_eq!(product_section.length, 58);
3001
3002 let file = GribFile::from_bytes(bytes).unwrap();
3003 let message = file.message(0).unwrap();
3004 assert_eq!(message.valid_time(), Some(interval_end_time()));
3005 let product = message.product_definition().unwrap();
3006 assert_eq!(product.template_number(), 8);
3007 match &product.template {
3008 ProductDefinitionTemplate::StatisticalProcess(template) => {
3009 assert_eq!(template.base.forecast_time, 1);
3010 assert_eq!(template.end_of_overall_time_interval, interval_end_time());
3011 assert_eq!(template.time_ranges, vec![statistical_time_range()]);
3012 }
3013 other => panic!("expected template 4.8, got {other:?}"),
3014 }
3015 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3016 }
3017
3018 #[test]
3019 fn writes_ensemble_statistical_product_template_readable_by_reader() {
3020 let values = [1.0, 2.0, 3.0, 4.0];
3021 let field = Grib2FieldBuilder::new()
3022 .identification(identification())
3023 .grid(grid())
3024 .product(ProductDefinition {
3025 parameter_category: 0,
3026 parameter_number: 2,
3027 template: ProductDefinitionTemplate::EnsembleStatisticalProcess(
3028 EnsembleStatisticalProcessTemplate {
3029 ensemble: IndividualEnsembleForecastTemplate {
3030 base: analysis_or_forecast_template(),
3031 type_of_ensemble_forecast: 1,
3032 perturbation_number: 3,
3033 number_of_forecasts_in_ensemble: 30,
3034 },
3035 end_of_overall_time_interval: interval_end_time(),
3036 number_of_missing_in_statistical_process: 0,
3037 time_ranges: vec![statistical_time_range()],
3038 },
3039 ),
3040 })
3041 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3042 .values(&values)
3043 .build()
3044 .unwrap();
3045
3046 let bytes = write_message([field]);
3047 let product_section = scan_sections(&bytes)
3048 .unwrap()
3049 .into_iter()
3050 .find(|section| section.number == 4)
3051 .unwrap();
3052 assert_eq!(product_section.length, 61);
3053
3054 let file = GribFile::from_bytes(bytes).unwrap();
3055 let message = file.message(0).unwrap();
3056 assert_eq!(message.valid_time(), Some(interval_end_time()));
3057 let product = message.product_definition().unwrap();
3058 assert_eq!(product.template_number(), 11);
3059 match &product.template {
3060 ProductDefinitionTemplate::EnsembleStatisticalProcess(template) => {
3061 assert_eq!(template.ensemble.type_of_ensemble_forecast, 1);
3062 assert_eq!(template.ensemble.perturbation_number, 3);
3063 assert_eq!(template.ensemble.number_of_forecasts_in_ensemble, 30);
3064 assert_eq!(template.end_of_overall_time_interval, interval_end_time());
3065 assert_eq!(template.time_ranges, vec![statistical_time_range()]);
3066 }
3067 other => panic!("expected template 4.11, got {other:?}"),
3068 }
3069 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3070 }
3071
3072 #[test]
3073 fn rejects_too_many_statistical_time_ranges() {
3074 let err = Grib2FieldBuilder::new()
3075 .identification(identification())
3076 .grid(grid())
3077 .product(ProductDefinition {
3078 parameter_category: 0,
3079 parameter_number: 1,
3080 template: ProductDefinitionTemplate::StatisticalProcess(
3081 StatisticalProcessTemplate {
3082 base: analysis_or_forecast_template(),
3083 end_of_overall_time_interval: interval_end_time(),
3084 number_of_missing_in_statistical_process: 0,
3085 time_ranges: vec![statistical_time_range(); 256],
3086 },
3087 ),
3088 })
3089 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3090 .values(&[1.0, 2.0, 3.0, 4.0])
3091 .build()
3092 .unwrap_err();
3093
3094 assert!(
3095 matches!(err, grib_core::Error::Other(message) if message.contains("time-range count"))
3096 );
3097 }
3098
3099 #[test]
3100 fn rejects_invalid_statistical_product_end_time() {
3101 let err = Grib2FieldBuilder::new()
3102 .identification(identification())
3103 .grid(grid())
3104 .product(ProductDefinition {
3105 parameter_category: 0,
3106 parameter_number: 1,
3107 template: ProductDefinitionTemplate::StatisticalProcess(
3108 StatisticalProcessTemplate {
3109 base: analysis_or_forecast_template(),
3110 end_of_overall_time_interval: ReferenceTime {
3111 year: 2026,
3112 month: 13,
3113 day: 20,
3114 hour: 18,
3115 minute: 0,
3116 second: 0,
3117 },
3118 number_of_missing_in_statistical_process: 0,
3119 time_ranges: vec![statistical_time_range()],
3120 },
3121 ),
3122 })
3123 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3124 .values(&[1.0, 2.0, 3.0, 4.0])
3125 .build()
3126 .unwrap_err();
3127
3128 assert!(
3129 matches!(err, grib_core::Error::InvalidSection { section: 4, reason } if reason.contains("invalid reference timestamp"))
3130 );
3131 }
3132
3133 #[test]
3134 fn writes_polar_stereographic_grib2_field_readable_by_reader() {
3135 let values = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3136 let field = Grib2FieldBuilder::new()
3137 .identification(identification())
3138 .grid(polar_grid(0))
3139 .product(product(0, 0))
3140 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3141 .values(&values)
3142 .build()
3143 .unwrap();
3144
3145 let file = GribFile::from_bytes(write_message([field])).unwrap();
3146 let message = file.message(0).unwrap();
3147 assert_eq!(message.grid_shape(), (3, 2));
3148 assert_eq!(
3149 message.projected_x_coordinates().unwrap().unwrap(),
3150 vec![0.0, 3_000.0, 6_000.0]
3151 );
3152 assert_eq!(
3153 message.projected_y_coordinates().unwrap().unwrap(),
3154 vec![-0.0, -3_000.0]
3155 );
3156 match message.grid_definition() {
3157 GridDefinition::PolarStereographic(grid) => {
3158 assert_eq!(grid.number_of_points, 6);
3159 assert_eq!(grid.shape_of_earth, 6);
3160 assert_eq!(grid.nx, 3);
3161 assert_eq!(grid.ny, 2);
3162 assert_eq!(grid.lat_first, 41_612_949);
3163 assert_eq!(grid.lon_first, 185_117_126);
3164 assert_eq!(grid.lat_d, 60_000_000);
3165 assert_eq!(grid.lon_v, 225_000_000);
3166 assert_eq!(grid.dx, 3_000_000);
3167 assert_eq!(grid.dy, 3_000_000);
3168 }
3169 other => panic!("expected polar stereographic grid, got {other:?}"),
3170 }
3171 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3172 }
3173
3174 #[test]
3175 fn writes_mercator_grib2_field_readable_by_reader() {
3176 let values = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3177 let field = Grib2FieldBuilder::new()
3178 .identification(identification())
3179 .grid(mercator_grid(0))
3180 .product(product(0, 0))
3181 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3182 .values(&values)
3183 .build()
3184 .unwrap();
3185
3186 let file = GribFile::from_bytes(write_message([field])).unwrap();
3187 let message = file.message(0).unwrap();
3188 assert_eq!(message.grid_shape(), (3, 2));
3189 assert_eq!(
3190 message.projected_x_coordinates().unwrap().unwrap(),
3191 vec![0.0, 1_000.0, 2_000.0]
3192 );
3193 assert_eq!(
3194 message.projected_y_coordinates().unwrap().unwrap(),
3195 vec![-0.0, -2_000.0]
3196 );
3197 match message.grid_definition() {
3198 GridDefinition::Mercator(grid) => {
3199 assert_eq!(grid.number_of_points, 6);
3200 assert_eq!(grid.shape_of_earth, 6);
3201 assert_eq!(grid.ni, 3);
3202 assert_eq!(grid.nj, 2);
3203 assert_eq!(grid.lat_first, -20_000_000);
3204 assert_eq!(grid.lon_first, -100_000_000);
3205 assert_eq!(grid.lat_d, 0);
3206 assert_eq!(grid.lat_last, 20_000_000);
3207 assert_eq!(grid.lon_last, -98_000_000);
3208 assert_eq!(grid.di, 1_000_000);
3209 assert_eq!(grid.dj, 2_000_000);
3210 }
3211 other => panic!("expected Mercator grid, got {other:?}"),
3212 }
3213 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3214 }
3215
3216 #[test]
3217 fn writes_lambert_conformal_grib2_field_readable_by_reader() {
3218 let values = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3219 let field = Grib2FieldBuilder::new()
3220 .identification(identification())
3221 .grid(lambert_grid(0))
3222 .product(product(0, 0))
3223 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3224 .values(&values)
3225 .build()
3226 .unwrap();
3227
3228 let file = GribFile::from_bytes(write_message([field])).unwrap();
3229 let message = file.message(0).unwrap();
3230 assert_eq!(message.grid_shape(), (3, 2));
3231 assert_eq!(
3232 message.projected_x_coordinates().unwrap().unwrap(),
3233 vec![0.0, 2_539.703, 5_079.406]
3234 );
3235 assert_eq!(
3236 message.projected_y_coordinates().unwrap().unwrap(),
3237 vec![-0.0, -2_539.703]
3238 );
3239 match message.grid_definition() {
3240 GridDefinition::LambertConformal(grid) => {
3241 assert_eq!(grid.number_of_points, 6);
3242 assert_eq!(grid.shape_of_earth, 1);
3243 assert_eq!(grid.scaled_value_radius, 6_371_200);
3244 assert_eq!(grid.nx, 3);
3245 assert_eq!(grid.ny, 2);
3246 assert_eq!(grid.lat_first, 12_190_000);
3247 assert_eq!(grid.lon_first, 226_541_000);
3248 assert_eq!(grid.lat_d, 25_000_000);
3249 assert_eq!(grid.lon_v, 265_000_000);
3250 assert_eq!(grid.dx, 2_539_703);
3251 assert_eq!(grid.dy, 2_539_703);
3252 assert_eq!(grid.latin1, 25_000_000);
3253 assert_eq!(grid.latin2, 25_000_000);
3254 }
3255 other => panic!("expected Lambert conformal grid, got {other:?}"),
3256 }
3257 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3258 }
3259
3260 #[test]
3261 fn writes_albers_equal_area_grib2_field_readable_by_reader() {
3262 let values = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3263 let field = Grib2FieldBuilder::new()
3264 .identification(identification())
3265 .grid(albers_grid(0))
3266 .product(product(0, 0))
3267 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3268 .values(&values)
3269 .build()
3270 .unwrap();
3271
3272 let file = GribFile::from_bytes(write_message([field])).unwrap();
3273 let message = file.message(0).unwrap();
3274 assert_eq!(message.grid_shape(), (3, 2));
3275 assert_eq!(
3276 message.projected_x_coordinates().unwrap().unwrap(),
3277 vec![0.0, 4_000.0, 8_000.0]
3278 );
3279 assert_eq!(
3280 message.projected_y_coordinates().unwrap().unwrap(),
3281 vec![-0.0, -5_000.0]
3282 );
3283 match message.grid_definition() {
3284 GridDefinition::AlbersEqualArea(grid) => {
3285 assert_eq!(grid.number_of_points, 6);
3286 assert_eq!(grid.shape_of_earth, 6);
3287 assert_eq!(grid.nx, 3);
3288 assert_eq!(grid.ny, 2);
3289 assert_eq!(grid.lat_first, 23_000_000);
3290 assert_eq!(grid.lon_first, 240_000_000);
3291 assert_eq!(grid.lat_d, 25_000_000);
3292 assert_eq!(grid.lon_v, 265_000_000);
3293 assert_eq!(grid.dx, 4_000_000);
3294 assert_eq!(grid.dy, 5_000_000);
3295 assert_eq!(grid.latin1, 29_500_000);
3296 assert_eq!(grid.latin2, 45_500_000);
3297 }
3298 other => panic!("expected Albers equal-area grid, got {other:?}"),
3299 }
3300 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3301 }
3302
3303 #[test]
3304 fn roundtrips_projected_grid_logical_order_for_alternating_scan_rows() {
3305 let values = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3306 let field = Grib2FieldBuilder::new()
3307 .identification(identification())
3308 .grid(polar_grid(0b0001_0000))
3309 .product(product(0, 0))
3310 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3311 .values(&values)
3312 .build()
3313 .unwrap();
3314
3315 let file = GribFile::from_bytes(write_message([field])).unwrap();
3316 assert_eq!(
3317 file.message(0).unwrap().read_flat_data_as_f64().unwrap(),
3318 values
3319 );
3320 }
3321
3322 #[test]
3323 fn rejects_projected_grid_point_count_mismatch_before_writing() {
3324 let mut grid = match polar_grid(0) {
3325 GridDefinition::PolarStereographic(grid) => grid,
3326 other => panic!("expected polar stereographic grid, got {other:?}"),
3327 };
3328 grid.number_of_points = 5;
3329 let err = Grib2FieldBuilder::new()
3330 .identification(identification())
3331 .grid(GridDefinition::PolarStereographic(grid))
3332 .product(product(0, 0))
3333 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3334 .values(&[1.0, 2.0, 3.0, 4.0, 5.0])
3335 .build()
3336 .unwrap_err();
3337
3338 assert!(
3339 matches!(err, grib_core::Error::Other(message) if message.contains("number_of_points"))
3340 );
3341 }
3342
3343 #[cfg(not(feature = "png"))]
3344 #[test]
3345 fn png_packing_requires_png_feature() {
3346 let err = Grib2FieldBuilder::new()
3347 .identification(identification())
3348 .grid(grid())
3349 .product(product(0, 0))
3350 .packing(PackingStrategy::PngAuto { decimal_scale: 0 })
3351 .values(&[1.0, 2.0, 3.0, 4.0])
3352 .build()
3353 .unwrap_err();
3354
3355 assert!(matches!(err, grib_core::Error::UnsupportedDataTemplate(41)));
3356 }
3357
3358 #[cfg(not(feature = "jpeg2000"))]
3359 #[test]
3360 fn jpeg2000_packing_requires_jpeg2000_feature() {
3361 let err = Grib2FieldBuilder::new()
3362 .identification(identification())
3363 .grid(grid())
3364 .product(product(0, 0))
3365 .packing(PackingStrategy::Jpeg2000Auto { decimal_scale: 0 })
3366 .values(&[1.0, 2.0, 3.0, 4.0])
3367 .build()
3368 .unwrap_err();
3369
3370 assert!(matches!(err, grib_core::Error::UnsupportedDataTemplate(40)));
3371 }
3372
3373 #[cfg(feature = "png")]
3374 #[test]
3375 fn writes_png_grib2_field() {
3376 let field = Grib2FieldBuilder::new()
3377 .identification(identification())
3378 .grid(grid())
3379 .product(product(0, 0))
3380 .packing(PackingStrategy::PngAuto { decimal_scale: 0 })
3381 .values(&[12.0, 14.0, 16.0, 18.0])
3382 .build()
3383 .unwrap();
3384
3385 match field.data_representation() {
3386 DataRepresentation::PngPacking(params) => {
3387 assert_eq!(params.packing.encoded_values, 4);
3388 assert_eq!(params.packing.reference_value, 12.0);
3389 assert_eq!(params.packing.bits_per_value, 4);
3390 }
3391 other => panic!("expected PNG packing, got {other:?}"),
3392 }
3393
3394 let bytes = write_message([field]);
3395 let file = GribFile::from_bytes(bytes.clone()).unwrap();
3396 let message = file.message(0).unwrap();
3397 assert!(matches!(
3398 &message.metadata().data_representation,
3399 DataRepresentation::PngPacking(_)
3400 ));
3401 assert_eq!(
3402 message.read_flat_data_as_f64().unwrap(),
3403 vec![12.0, 14.0, 16.0, 18.0]
3404 );
3405
3406 let payload = section_payload(&bytes, 7);
3407 let decoder = png::Decoder::new(std::io::Cursor::new(payload));
3408 let mut reader = decoder.read_info().unwrap();
3409 let mut decoded = vec![0; reader.output_buffer_size().unwrap()];
3410 let info = reader.next_frame(&mut decoded).unwrap();
3411 assert_eq!(info.width, 2);
3412 assert_eq!(info.height, 2);
3413 assert_eq!(info.color_type, png::ColorType::Grayscale);
3414 assert_eq!(info.bit_depth, png::BitDepth::Four);
3415 assert_eq!(&decoded[..info.buffer_size()], &[0x02, 0x46]);
3416 }
3417
3418 #[cfg(feature = "png")]
3419 #[test]
3420 fn writes_png_projected_grib2_field() {
3421 let field = Grib2FieldBuilder::new()
3422 .identification(identification())
3423 .grid(polar_grid(0))
3424 .product(product(0, 0))
3425 .packing(PackingStrategy::PngAuto { decimal_scale: 0 })
3426 .values(&[12.0, 14.0, 16.0, 18.0, 20.0, 22.0])
3427 .build()
3428 .unwrap();
3429
3430 let bytes = write_message([field]);
3431 let file = GribFile::from_bytes(bytes.clone()).unwrap();
3432 let message = file.message(0).unwrap();
3433 assert!(matches!(
3434 &message.metadata().data_representation,
3435 DataRepresentation::PngPacking(_)
3436 ));
3437 assert!(matches!(
3438 message.grid_definition(),
3439 GridDefinition::PolarStereographic(_)
3440 ));
3441 assert_eq!(
3442 message.read_flat_data_as_f64().unwrap(),
3443 vec![12.0, 14.0, 16.0, 18.0, 20.0, 22.0]
3444 );
3445
3446 let payload = section_payload(&bytes, 7);
3447 let decoder = png::Decoder::new(std::io::Cursor::new(payload));
3448 let reader = decoder.read_info().unwrap();
3449 let info = reader.info();
3450 assert_eq!(info.width, 3);
3451 assert_eq!(info.height, 2);
3452 }
3453
3454 #[cfg(feature = "jpeg2000")]
3455 #[test]
3456 fn writes_jpeg2000_grib2_field() {
3457 let field = Grib2FieldBuilder::new()
3458 .identification(identification())
3459 .grid(grid())
3460 .product(product(0, 0))
3461 .packing(PackingStrategy::Jpeg2000Auto { decimal_scale: 0 })
3462 .values(&[12.0, 13.0, 14.0, 15.0])
3463 .build()
3464 .unwrap();
3465
3466 match field.data_representation() {
3467 DataRepresentation::Jpeg2000Packing(params) => {
3468 assert_eq!(params.packing.encoded_values, 4);
3469 assert_eq!(params.packing.reference_value, 12.0);
3470 assert_eq!(params.packing.bits_per_value, 2);
3471 assert_eq!(params.compression_type, 0);
3472 assert_eq!(params.target_compression_ratio, 0);
3473 }
3474 other => panic!("expected JPEG2000 packing, got {other:?}"),
3475 }
3476
3477 let bytes = write_message([field]);
3478 let file = GribFile::from_bytes(bytes.clone()).unwrap();
3479 let message = file.message(0).unwrap();
3480 assert!(matches!(
3481 &message.metadata().data_representation,
3482 DataRepresentation::Jpeg2000Packing(_)
3483 ));
3484 assert_eq!(
3485 message.read_flat_data_as_f64().unwrap(),
3486 vec![12.0, 13.0, 14.0, 15.0]
3487 );
3488
3489 let payload = section_payload(&bytes, 7);
3490 assert!(payload.starts_with(&[0xff, 0x4f, 0xff, 0x51]));
3491 }
3492
3493 #[test]
3494 fn writes_complex_grib2_field_readable_by_reader() {
3495 let values = (0..70)
3496 .map(|index| f64::from((index * 37) % 113) - 50.0)
3497 .collect::<Vec<_>>();
3498 let field = Grib2FieldBuilder::new()
3499 .identification(identification())
3500 .grid(grid_with_shape_and_scanning_mode(35, 2, 0))
3501 .product(product(0, 0))
3502 .packing(PackingStrategy::ComplexAuto {
3503 decimal_scale: 0,
3504 spatial_differencing: None,
3505 })
3506 .values(&values)
3507 .build()
3508 .unwrap();
3509
3510 let file = GribFile::from_bytes(write_message([field])).unwrap();
3511 let message = file.message(0).unwrap();
3512 match &message.metadata().data_representation {
3513 DataRepresentation::ComplexPacking(params) => {
3514 assert_eq!(params.num_groups, 3);
3515 assert_eq!(params.group_splitting_method, 1);
3516 assert_eq!(params.missing_value_management, 0);
3517 assert_eq!(params.group_length_reference, 32);
3518 assert_eq!(params.true_length_last_group, 6);
3519 assert_eq!(params.spatial_differencing, None);
3520 }
3521 other => panic!("expected complex packing, got {other:?}"),
3522 }
3523 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3524 }
3525
3526 #[test]
3527 fn writes_complex_grib2_decimal_scaled_values() {
3528 let values = [1.24, 2.34, -3.46, 4.56];
3529 let field = Grib2FieldBuilder::new()
3530 .identification(identification())
3531 .grid(grid())
3532 .product(product(0, 0))
3533 .packing(PackingStrategy::ComplexAuto {
3534 decimal_scale: 1,
3535 spatial_differencing: None,
3536 })
3537 .values(&values)
3538 .build()
3539 .unwrap();
3540
3541 let file = GribFile::from_bytes(write_message([field])).unwrap();
3542 let message = file.message(0).unwrap();
3543 assert!(matches!(
3544 &message.metadata().data_representation,
3545 DataRepresentation::ComplexPacking(_)
3546 ));
3547 let decoded = message.read_flat_data_as_f64().unwrap();
3548 for (actual, expected) in decoded.iter().zip(values) {
3549 assert!((actual - expected).abs() <= 0.05);
3550 }
3551 }
3552
3553 #[test]
3554 fn writes_complex_grib2_bitmap_from_nan_values() {
3555 let values = [1.0, f64::NAN, 3.0, 4.0];
3556 let field = Grib2FieldBuilder::new()
3557 .identification(identification())
3558 .grid(grid())
3559 .product(product(0, 0))
3560 .packing(PackingStrategy::ComplexAuto {
3561 decimal_scale: 0,
3562 spatial_differencing: None,
3563 })
3564 .values(&values)
3565 .build()
3566 .unwrap();
3567
3568 let bytes = write_message([field]);
3569 assert_eq!(section_numbers(&bytes), vec![1, 3, 4, 5, 6, 7, 8]);
3570
3571 let file = GribFile::from_bytes(bytes).unwrap();
3572 let message = file.message(0).unwrap();
3573 match &message.metadata().data_representation {
3574 DataRepresentation::ComplexPacking(params) => assert_eq!(params.encoded_values, 3),
3575 other => panic!("expected complex packing, got {other:?}"),
3576 }
3577 let decoded = message.read_flat_data_as_f64().unwrap();
3578 assert_eq!(decoded[0], 1.0);
3579 assert!(decoded[1].is_nan());
3580 assert_eq!(decoded[2], 3.0);
3581 assert_eq!(decoded[3], 4.0);
3582 }
3583
3584 #[test]
3585 fn writes_all_missing_complex_grib2_bitmap_field() {
3586 let field = Grib2FieldBuilder::new()
3587 .identification(identification())
3588 .grid(grid())
3589 .product(product(0, 0))
3590 .packing(PackingStrategy::ComplexAuto {
3591 decimal_scale: 0,
3592 spatial_differencing: None,
3593 })
3594 .values(&[f64::NAN; 4])
3595 .build()
3596 .unwrap();
3597
3598 let file = GribFile::from_bytes(write_message([field])).unwrap();
3599 let message = file.message(0).unwrap();
3600 match &message.metadata().data_representation {
3601 DataRepresentation::ComplexPacking(params) => {
3602 assert_eq!(params.encoded_values, 0);
3603 assert_eq!(params.num_groups, 1);
3604 assert_eq!(params.true_length_last_group, 0);
3605 }
3606 other => panic!("expected complex packing, got {other:?}"),
3607 }
3608 let decoded = message.read_flat_data_as_f64().unwrap();
3609 assert!(decoded.iter().all(|value| value.is_nan()));
3610 }
3611
3612 #[test]
3613 fn writes_first_order_spatial_differencing_grib2_field() {
3614 let values = (0..70)
3615 .map(|index| f64::from((index * index + 7 * index) % 149) - 50.0)
3616 .collect::<Vec<_>>();
3617 let field = Grib2FieldBuilder::new()
3618 .identification(identification())
3619 .grid(grid_with_shape_and_scanning_mode(35, 2, 0))
3620 .product(product(0, 0))
3621 .packing(PackingStrategy::ComplexAuto {
3622 decimal_scale: 0,
3623 spatial_differencing: Some(SpatialDifferencingOrder::First),
3624 })
3625 .values(&values)
3626 .build()
3627 .unwrap();
3628
3629 let file = GribFile::from_bytes(write_message([field])).unwrap();
3630 let message = file.message(0).unwrap();
3631 match &message.metadata().data_representation {
3632 DataRepresentation::ComplexPacking(params) => {
3633 let spatial = params.spatial_differencing.unwrap();
3634 assert_eq!(spatial.order, 1);
3635 assert!(spatial.descriptor_octets >= 1);
3636 assert_eq!(params.num_groups, 3);
3637 }
3638 other => panic!("expected complex packing, got {other:?}"),
3639 }
3640 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3641 }
3642
3643 #[test]
3644 fn writes_second_order_spatial_differencing_grib2_field() {
3645 let values = (0..70)
3646 .map(|index| {
3647 let index = f64::from(index);
3648 index * index - 12.0 * index + 25.0
3649 })
3650 .collect::<Vec<_>>();
3651 let field = Grib2FieldBuilder::new()
3652 .identification(identification())
3653 .grid(grid_with_shape_and_scanning_mode(35, 2, 0))
3654 .product(product(0, 0))
3655 .packing(PackingStrategy::ComplexAuto {
3656 decimal_scale: 0,
3657 spatial_differencing: Some(SpatialDifferencingOrder::Second),
3658 })
3659 .values(&values)
3660 .build()
3661 .unwrap();
3662
3663 let file = GribFile::from_bytes(write_message([field])).unwrap();
3664 let message = file.message(0).unwrap();
3665 match &message.metadata().data_representation {
3666 DataRepresentation::ComplexPacking(params) => {
3667 let spatial = params.spatial_differencing.unwrap();
3668 assert_eq!(spatial.order, 2);
3669 assert!(spatial.descriptor_octets >= 1);
3670 assert_eq!(params.num_groups, 3);
3671 }
3672 other => panic!("expected complex packing, got {other:?}"),
3673 }
3674 assert_eq!(message.read_flat_data_as_f64().unwrap(), values);
3675 }
3676
3677 #[test]
3678 fn writes_spatial_differencing_with_bitmap_missing_values() {
3679 let values = [1.0, f64::NAN, 4.0, 9.0];
3680 let field = Grib2FieldBuilder::new()
3681 .identification(identification())
3682 .grid(grid())
3683 .product(product(0, 0))
3684 .packing(PackingStrategy::ComplexAuto {
3685 decimal_scale: 0,
3686 spatial_differencing: Some(SpatialDifferencingOrder::First),
3687 })
3688 .values(&values)
3689 .build()
3690 .unwrap();
3691
3692 let bytes = write_message([field]);
3693 assert_eq!(section_numbers(&bytes), vec![1, 3, 4, 5, 6, 7, 8]);
3694
3695 let file = GribFile::from_bytes(bytes).unwrap();
3696 let message = file.message(0).unwrap();
3697 match &message.metadata().data_representation {
3698 DataRepresentation::ComplexPacking(params) => {
3699 assert_eq!(params.encoded_values, 3);
3700 assert_eq!(params.spatial_differencing.unwrap().order, 1);
3701 }
3702 other => panic!("expected complex packing, got {other:?}"),
3703 }
3704 let decoded = message.read_flat_data_as_f64().unwrap();
3705 assert_eq!(decoded[0], 1.0);
3706 assert!(decoded[1].is_nan());
3707 assert_eq!(decoded[2], 4.0);
3708 assert_eq!(decoded[3], 9.0);
3709 }
3710
3711 #[test]
3712 fn rejects_spatial_differencing_without_enough_present_values() {
3713 let err = Grib2FieldBuilder::new()
3714 .identification(identification())
3715 .grid(grid())
3716 .product(product(0, 0))
3717 .packing(PackingStrategy::ComplexAuto {
3718 decimal_scale: 0,
3719 spatial_differencing: Some(SpatialDifferencingOrder::Second),
3720 })
3721 .values(&[1.0, f64::NAN, f64::NAN, f64::NAN])
3722 .build()
3723 .unwrap_err();
3724
3725 assert!(matches!(
3726 err,
3727 grib_core::Error::DataLengthMismatch {
3728 expected: 2,
3729 actual: 1
3730 }
3731 ));
3732 }
3733
3734 #[test]
3735 fn rejects_complex_packing_for_grib1() {
3736 let err = Grib1FieldBuilder::new()
3737 .product(grib1_product())
3738 .grid(grid())
3739 .packing(PackingStrategy::ComplexAuto {
3740 decimal_scale: 0,
3741 spatial_differencing: None,
3742 })
3743 .values(&[1.0, 2.0, 3.0, 4.0])
3744 .build()
3745 .unwrap_err();
3746
3747 assert!(
3748 matches!(err, grib_core::Error::Other(message) if message.contains("GRIB1 writer does not support complex packing"))
3749 );
3750 }
3751
3752 #[test]
3753 fn writes_constant_field_with_zero_width_simple_packing() {
3754 let field = Grib2FieldBuilder::new()
3755 .identification(identification())
3756 .grid(grid())
3757 .product(product(0, 0))
3758 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3759 .values(&[42.0, 42.0, 42.0, 42.0])
3760 .build()
3761 .unwrap();
3762
3763 let file = GribFile::from_bytes(write_message([field])).unwrap();
3764 let message = file.message(0).unwrap();
3765 match &message.metadata().data_representation {
3766 DataRepresentation::SimplePacking(params) => assert_eq!(params.bits_per_value, 0),
3767 other => panic!("expected simple packing, got {other:?}"),
3768 }
3769 assert_eq!(message.read_flat_data_as_f64().unwrap(), vec![42.0; 4]);
3770 }
3771
3772 #[test]
3773 fn writes_decimal_scaled_values_within_quantization_tolerance() {
3774 let values = [1.2, 2.3, 3.4, 4.5];
3775 let field = Grib2FieldBuilder::new()
3776 .identification(identification())
3777 .grid(grid())
3778 .product(product(0, 0))
3779 .packing(PackingStrategy::SimpleAuto { decimal_scale: 1 })
3780 .values(&values)
3781 .build()
3782 .unwrap();
3783
3784 let file = GribFile::from_bytes(write_message([field])).unwrap();
3785 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
3786 for (actual, expected) in decoded.iter().zip(values) {
3787 assert!((actual - expected).abs() <= 0.05);
3788 }
3789 }
3790
3791 #[test]
3792 fn writes_bitmap_from_nan_values() {
3793 let values = [1.0, f64::NAN, 3.0, 4.0];
3794 let field = Grib2FieldBuilder::new()
3795 .identification(identification())
3796 .grid(grid())
3797 .product(product(0, 0))
3798 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3799 .values(&values)
3800 .build()
3801 .unwrap();
3802
3803 let file = GribFile::from_bytes(write_message([field])).unwrap();
3804 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
3805 assert_eq!(decoded[0], 1.0);
3806 assert!(decoded[1].is_nan());
3807 assert_eq!(decoded[2], 3.0);
3808 assert_eq!(decoded[3], 4.0);
3809 }
3810
3811 #[test]
3812 fn writes_bitmap_from_explicit_mask() {
3813 let values = [1.0, 999.0, 3.0, 4.0];
3814 let bitmap = [true, false, true, true];
3815 let field = Grib2FieldBuilder::new()
3816 .identification(identification())
3817 .grid(grid())
3818 .product(product(0, 0))
3819 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3820 .values(&values)
3821 .bitmap(&bitmap)
3822 .build()
3823 .unwrap();
3824
3825 let file = GribFile::from_bytes(write_message([field])).unwrap();
3826 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
3827 assert_eq!(decoded[0], 1.0);
3828 assert!(decoded[1].is_nan());
3829 assert_eq!(decoded[2], 3.0);
3830 assert_eq!(decoded[3], 4.0);
3831 }
3832
3833 #[test]
3834 fn writes_all_missing_bitmap_field() {
3835 let values = [f64::NAN; 4];
3836 let field = Grib2FieldBuilder::new()
3837 .identification(identification())
3838 .grid(grid())
3839 .product(product(0, 0))
3840 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3841 .values(&values)
3842 .build()
3843 .unwrap();
3844
3845 let file = GribFile::from_bytes(write_message([field])).unwrap();
3846 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
3847 assert!(decoded.iter().all(|value| value.is_nan()));
3848 }
3849
3850 #[test]
3851 fn writes_single_grib2_message_with_multiple_fields() {
3852 let first = simple_field(&[1.0, 2.0, 3.0, 4.0], 0, 0);
3853 let second = simple_field(&[5.0, 6.0, 7.0, 8.0], 0, 2);
3854
3855 let bytes = write_message([first, second]);
3856 assert_eq!(section_numbers(&bytes), vec![1, 3, 4, 5, 7, 4, 5, 7, 8]);
3857
3858 let file = GribFile::from_bytes(bytes).unwrap();
3859 assert_eq!(file.message_count(), 2);
3860 assert_eq!(file.message(0).unwrap().parameter_name(), "TMP");
3861 assert_eq!(file.message(1).unwrap().parameter_name(), "POT");
3862 assert_eq!(file.message(0).unwrap().grid_shape(), (2, 2));
3863 assert_eq!(file.message(1).unwrap().grid_shape(), (2, 2));
3864 assert_eq!(
3865 file.message(0).unwrap().read_flat_data_as_f64().unwrap(),
3866 vec![1.0, 2.0, 3.0, 4.0]
3867 );
3868 assert_eq!(
3869 file.message(1).unwrap().read_flat_data_as_f64().unwrap(),
3870 vec![5.0, 6.0, 7.0, 8.0]
3871 );
3872 }
3873
3874 #[test]
3875 fn emits_new_grid_section_only_when_grid_changes() {
3876 let first = simple_field(&[1.0, 2.0, 3.0, 4.0], 0, 0);
3877 let second = simple_field(&[5.0, 6.0, 7.0, 8.0], 0, 2);
3878 let third = Grib2FieldBuilder::new()
3879 .identification(identification())
3880 .grid(grid_with_shape_and_scanning_mode(3, 2, 0))
3881 .product(product(0, 4))
3882 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3883 .values(&[9.0, 10.0, 11.0, 12.0, 13.0, 14.0])
3884 .build()
3885 .unwrap();
3886
3887 let bytes = write_message([first, second, third]);
3888 assert_eq!(
3889 section_numbers(&bytes),
3890 vec![1, 3, 4, 5, 7, 4, 5, 7, 3, 4, 5, 7, 8]
3891 );
3892
3893 let file = GribFile::from_bytes(bytes).unwrap();
3894 assert_eq!(file.message_count(), 3);
3895 assert_eq!(file.message(0).unwrap().parameter_name(), "TMP");
3896 assert_eq!(file.message(1).unwrap().parameter_name(), "POT");
3897 assert_eq!(file.message(2).unwrap().parameter_name(), "TMAX");
3898 assert_eq!(file.message(0).unwrap().grid_shape(), (2, 2));
3899 assert_eq!(file.message(1).unwrap().grid_shape(), (2, 2));
3900 assert_eq!(file.message(2).unwrap().grid_shape(), (3, 2));
3901 assert_eq!(
3902 file.message(2).unwrap().read_flat_data_as_f64().unwrap(),
3903 vec![9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
3904 );
3905 }
3906
3907 #[test]
3908 fn writes_reused_grid_multifield_message_with_bitmap() {
3909 let first = simple_field(&[1.0, 2.0, 3.0, 4.0], 0, 0);
3910 let second = Grib2FieldBuilder::new()
3911 .identification(identification())
3912 .grid(grid())
3913 .product(product(0, 2))
3914 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3915 .values(&[5.0, f64::NAN, 7.0, 8.0])
3916 .build()
3917 .unwrap();
3918
3919 let bytes = write_message([first, second]);
3920 assert_eq!(section_numbers(&bytes), vec![1, 3, 4, 5, 7, 4, 5, 6, 7, 8]);
3921
3922 let file = GribFile::from_bytes(bytes).unwrap();
3923 assert_eq!(file.message_count(), 2);
3924 let decoded = file.message(1).unwrap().read_flat_data_as_f64().unwrap();
3925 assert_eq!(decoded[0], 5.0);
3926 assert!(decoded[1].is_nan());
3927 assert_eq!(decoded[2], 7.0);
3928 assert_eq!(decoded[3], 8.0);
3929 }
3930
3931 #[test]
3932 fn roundtrips_logical_row_major_order_for_supported_scan_modes() {
3933 let logical = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3934 for scanning_mode in [
3935 0b0000_0000,
3936 0b1000_0000,
3937 0b0100_0000,
3938 0b1100_0000,
3939 0b0001_0000,
3940 0b1001_0000,
3941 ] {
3942 let field = Grib2FieldBuilder::new()
3943 .identification(identification())
3944 .grid(grid_with_scanning_mode(scanning_mode))
3945 .product(product(0, 0))
3946 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3947 .values(&logical)
3948 .build()
3949 .unwrap();
3950
3951 let file = GribFile::from_bytes(write_message([field])).unwrap();
3952 assert_eq!(
3953 file.message(0).unwrap().read_flat_data_as_f64().unwrap(),
3954 logical,
3955 "scanning mode {scanning_mode:08b}"
3956 );
3957 }
3958 }
3959
3960 #[test]
3961 fn accepts_grib_scan_order_fast_path() {
3962 let scan_order = [1.0, 2.0, 3.0, 6.0, 5.0, 4.0];
3963 let field = Grib2FieldBuilder::new()
3964 .identification(identification())
3965 .grid(grid_with_scanning_mode(0b0001_0000))
3966 .product(product(0, 0))
3967 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3968 .values(&scan_order)
3969 .value_order(ValueOrder::GribScanOrder)
3970 .build()
3971 .unwrap();
3972
3973 let file = GribFile::from_bytes(write_message([field])).unwrap();
3974 assert_eq!(
3975 file.message(0).unwrap().read_flat_data_as_f64().unwrap(),
3976 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
3977 );
3978 }
3979
3980 #[test]
3981 fn reorders_explicit_bitmap_with_logical_values() {
3982 let values = [1.0, 2.0, 3.0, 4.0, 999.0, 6.0];
3983 let bitmap = [true, true, true, true, false, true];
3984 let field = Grib2FieldBuilder::new()
3985 .identification(identification())
3986 .grid(grid_with_scanning_mode(0b0001_0000))
3987 .product(product(0, 0))
3988 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
3989 .values(&values)
3990 .bitmap(&bitmap)
3991 .build()
3992 .unwrap();
3993
3994 let file = GribFile::from_bytes(write_message([field])).unwrap();
3995 let decoded = file.message(0).unwrap().read_flat_data_as_f64().unwrap();
3996 assert_eq!(decoded[..4], [1.0, 2.0, 3.0, 4.0]);
3997 assert!(decoded[4].is_nan());
3998 assert_eq!(decoded[5], 6.0);
3999 }
4000
4001 #[test]
4002 fn rejects_unsupported_scan_mode_before_writing() {
4003 let err = Grib2FieldBuilder::new()
4004 .identification(identification())
4005 .grid(grid_with_scanning_mode(0b0010_0000))
4006 .product(product(0, 0))
4007 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
4008 .values(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
4009 .build()
4010 .unwrap_err();
4011
4012 assert!(matches!(
4013 err,
4014 grib_core::Error::UnsupportedScanningMode(0b0010_0000)
4015 ));
4016 }
4017
4018 #[test]
4019 fn rejects_value_count_mismatch_before_writing() {
4020 let err = Grib2FieldBuilder::new()
4021 .identification(identification())
4022 .grid(grid())
4023 .product(product(0, 0))
4024 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
4025 .values(&[1.0, 2.0, 3.0])
4026 .build()
4027 .unwrap_err();
4028 assert!(matches!(
4029 err,
4030 grib_core::Error::DataLengthMismatch {
4031 expected: 4,
4032 actual: 3
4033 }
4034 ));
4035 }
4036
4037 #[derive(Debug, Deserialize)]
4038 struct ReferenceDump {
4039 messages: Vec<ReferenceMessage>,
4040 }
4041
4042 #[derive(Debug, Deserialize)]
4043 struct ReferenceMessage {
4044 edition: u8,
4045 name: String,
4046 values: Vec<Option<f64>>,
4047 }
4048
4049 #[test]
4050 #[ignore = "requires GRIB_READER_ECCODES_HELPER"]
4051 fn generated_grib1_fixture_matches_eccodes_when_configured() {
4052 let helper = std::env::var_os("GRIB_READER_ECCODES_HELPER")
4053 .expect("GRIB_READER_ECCODES_HELPER must be set");
4054 let bytes = write_grib1_message(grib1_simple_field(&[5.0, f64::NAN, 7.0, 8.0]));
4055
4056 let dir = tempfile::tempdir().unwrap();
4057 let path = dir.path().join("writer-generated.grib1");
4058 std::fs::write(&path, &bytes).unwrap();
4059
4060 let output = Command::new(helper)
4061 .arg("dump")
4062 .arg(&path)
4063 .output()
4064 .unwrap();
4065 assert!(
4066 output.status.success(),
4067 "ecCodes helper failed:\nstdout:\n{}\nstderr:\n{}",
4068 String::from_utf8_lossy(&output.stdout),
4069 String::from_utf8_lossy(&output.stderr)
4070 );
4071 let reference: ReferenceDump = serde_json::from_slice(&output.stdout).unwrap();
4072 let rust = GribFile::from_bytes(bytes).unwrap();
4073
4074 assert_eq!(reference.messages.len(), 1);
4075 assert_eq!(rust.message_count(), reference.messages.len());
4076 let message = rust.message(0).unwrap();
4077 let actual = message.read_flat_data_as_f64().unwrap();
4078 let expected = &reference.messages[0];
4079 assert_eq!(message.edition(), expected.edition);
4080 assert_eq!(message.parameter_description(), expected.name);
4081 assert_eq!(actual.len(), expected.values.len());
4082 for (actual, expected) in actual.iter().zip(&expected.values) {
4083 match expected {
4084 Some(expected) => assert!((actual - expected).abs() <= 1e-6),
4085 None => assert!(actual.is_nan()),
4086 }
4087 }
4088 }
4089
4090 #[test]
4091 #[ignore = "requires GRIB_READER_ECCODES_HELPER"]
4092 fn generated_grib2_fixture_matches_eccodes_when_configured() {
4093 let helper = std::env::var_os("GRIB_READER_ECCODES_HELPER")
4094 .expect("GRIB_READER_ECCODES_HELPER must be set");
4095 let first = simple_field(&[1.0, 2.0, 3.0, 4.0], 0, 0);
4096 let second = Grib2FieldBuilder::new()
4097 .identification(identification())
4098 .grid(grid())
4099 .product(product(0, 2))
4100 .packing(PackingStrategy::SimpleAuto { decimal_scale: 0 })
4101 .values(&[5.0, f64::NAN, 7.0, 8.0])
4102 .build()
4103 .unwrap();
4104 let bytes = write_message([first, second]);
4105
4106 let dir = tempfile::tempdir().unwrap();
4107 let path = dir.path().join("writer-generated.grib2");
4108 std::fs::write(&path, &bytes).unwrap();
4109
4110 let output = Command::new(helper)
4111 .arg("dump")
4112 .arg(&path)
4113 .output()
4114 .unwrap();
4115 assert!(
4116 output.status.success(),
4117 "ecCodes helper failed:\nstdout:\n{}\nstderr:\n{}",
4118 String::from_utf8_lossy(&output.stdout),
4119 String::from_utf8_lossy(&output.stderr)
4120 );
4121 let reference: ReferenceDump = serde_json::from_slice(&output.stdout).unwrap();
4122 let rust = GribFile::from_bytes(bytes).unwrap();
4123
4124 assert_eq!(reference.messages.len(), 2);
4125 assert_eq!(rust.message_count(), reference.messages.len());
4126 for (index, expected) in reference.messages.iter().enumerate() {
4127 let message = rust.message(index).unwrap();
4128 let actual = message.read_flat_data_as_f64().unwrap();
4129 assert_eq!(message.edition(), expected.edition);
4130 assert_eq!(message.parameter_description(), expected.name);
4131 assert_eq!(actual.len(), expected.values.len());
4132 for (actual, expected) in actual.iter().zip(&expected.values) {
4133 match expected {
4134 Some(expected) => assert!((actual - expected).abs() <= 1e-6),
4135 None => assert!(actual.is_nan()),
4136 }
4137 }
4138 }
4139 }
4140}