Skip to main content

grib_writer/
lib.rs

1//! GRIB writer crate.
2
3#![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/// Field packing strategy.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum PackingStrategy {
26    /// Simple packing with binary scale 0 and automatic bit-width selection.
27    SimpleAuto { decimal_scale: i16 },
28    /// GRIB2 complex packing with fixed-size general groups.
29    ComplexAuto {
30        decimal_scale: i16,
31        spatial_differencing: Option<SpatialDifferencingOrder>,
32    },
33    /// GRIB2 JPEG 2000 code stream packing template 5.40.
34    Jpeg2000Auto { decimal_scale: i16 },
35    /// GRIB2 PNG image packing template 5.41.
36    PngAuto { decimal_scale: i16 },
37}
38
39/// Spatial differencing order for GRIB2 complex packing template 5.3.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum SpatialDifferencingOrder {
42    /// First-order spatial differencing.
43    First,
44    /// Second-order spatial differencing.
45    Second,
46}
47
48const COMPLEX_AUTO_GROUP_LEN: usize = 32;
49
50/// Order of values supplied to field builders.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum ValueOrder {
53    /// Logical row-major order matching `grib-reader` ndarray output.
54    #[default]
55    LogicalRowMajor,
56    /// Native GRIB scan order; skips the logical-to-scan reordering pass.
57    GribScanOrder,
58}
59
60/// Builder for a single GRIB1 message field.
61#[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    /// Use a GRIB1 predefined bitmap table reference.
108    ///
109    /// The bitmap is used to pack only present values. The written message
110    /// contains the nonzero table reference, not the bitmap payload itself.
111    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/// A validated, packed GRIB1 field ready for message serialization.
217#[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/// Builder for a single GRIB2 field.
240#[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/// A validated, packed GRIB2 field ready for message serialization.
373#[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
404/// Streaming GRIB writer.
405pub 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(&section);
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(&section);
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(&section);
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(&section);
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(&section);
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(&params.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(&params.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, &params.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, &params.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(&params.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}