Skip to main content

grib_reader/
lib.rs

1//! Pure-Rust GRIB file reader.
2//!
3//! The current implementation supports the production-critical baseline for both
4//! GRIB1 and GRIB2: regular latitude/longitude grids with simple packing.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use grib_reader::GribFile;
10//!
11//! let file = GribFile::open("gfs.grib2")?;
12//! println!("messages: {}", file.message_count());
13//!
14//! for msg in file.messages() {
15//!     println!(
16//!         "  {} {:?} {:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
17//!         msg.parameter_name(),
18//!         msg.grid_shape(),
19//!         msg.reference_time().year,
20//!         msg.reference_time().month,
21//!         msg.reference_time().day,
22//!         msg.reference_time().hour,
23//!         msg.reference_time().minute,
24//!         msg.reference_time().second,
25//!     );
26//! }
27//!
28//! let data = file.message(0)?.read_data_as_f64()?;
29//! println!("shape: {:?}", data.shape());
30//! # Ok::<(), grib_reader::Error>(())
31//! ```
32
33pub mod data;
34pub mod error;
35pub mod grib1;
36pub mod grid;
37pub mod indicator;
38pub mod metadata;
39pub mod parameter;
40pub mod product;
41pub mod sections;
42mod util;
43
44pub use error::{Error, Result};
45pub use metadata::{Parameter, ReferenceTime};
46pub use product::{FixedSurface, Identification, ProductDefinition};
47
48use std::path::Path;
49
50use memmap2::Mmap;
51use ndarray::{ArrayD, IxDyn};
52
53use crate::data::{bitmap_payload as grib2_bitmap_payload, decode_field, DataRepresentation};
54use crate::grib1::{BinaryDataSection, GridDescription};
55use crate::grid::GridDefinition;
56use crate::indicator::Indicator;
57use crate::sections::{index_fields, FieldSections, SectionRef};
58
59#[cfg(feature = "rayon")]
60use rayon::prelude::*;
61
62const GRIB_MAGIC: &[u8; 4] = b"GRIB";
63
64/// Configuration for opening GRIB data.
65#[derive(Debug, Clone, Copy)]
66pub struct OpenOptions {
67    /// When `true`, the first malformed GRIB candidate aborts opening.
68    ///
69    /// When `false`, malformed candidates are skipped and scanning continues.
70    pub strict: bool,
71}
72
73impl Default for OpenOptions {
74    fn default() -> Self {
75        Self { strict: true }
76    }
77}
78
79/// A parsed GRIB field.
80#[derive(Debug, Clone)]
81pub struct MessageMetadata {
82    pub edition: u8,
83    pub center_id: u16,
84    pub subcenter_id: u16,
85    pub discipline: Option<u8>,
86    pub reference_time: ReferenceTime,
87    pub parameter: Parameter,
88    pub grid: GridDefinition,
89    pub data_representation: DataRepresentation,
90    pub forecast_time_unit: Option<u8>,
91    pub forecast_time: Option<u32>,
92    pub message_offset: u64,
93    pub message_length: u64,
94    pub field_index_in_message: usize,
95    grib1_product: Option<grib1::ProductDefinition>,
96    grib2_identification: Option<Identification>,
97    grib2_product: Option<ProductDefinition>,
98}
99
100/// A GRIB file containing one or more logical fields.
101pub struct GribFile {
102    data: GribData,
103    messages: Vec<MessageIndex>,
104}
105
106#[derive(Clone)]
107struct MessageIndex {
108    offset: usize,
109    length: usize,
110    metadata: MessageMetadata,
111    decode_plan: DecodePlan,
112}
113
114#[derive(Clone, Copy)]
115enum DecodePlan {
116    Grib1 {
117        bitmap: Option<SectionRef>,
118        data: SectionRef,
119    },
120    Grib2(FieldSections),
121}
122
123enum GribData {
124    Mmap(Mmap),
125    Bytes(Vec<u8>),
126}
127
128impl GribData {
129    fn as_bytes(&self) -> &[u8] {
130        match self {
131            GribData::Mmap(m) => m,
132            GribData::Bytes(b) => b,
133        }
134    }
135}
136
137impl GribFile {
138    /// Open a GRIB file from disk using memory-mapped I/O.
139    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
140        Self::open_with_options(path, OpenOptions::default())
141    }
142
143    /// Open a GRIB file from disk using explicit decoder options.
144    pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
145        let file = std::fs::File::open(path.as_ref())
146            .map_err(|e| Error::Io(e, path.as_ref().display().to_string()))?;
147        let mmap = unsafe { Mmap::map(&file) }
148            .map_err(|e| Error::Io(e, path.as_ref().display().to_string()))?;
149        Self::from_data(GribData::Mmap(mmap), options)
150    }
151
152    /// Open a GRIB file from an owned byte buffer.
153    pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
154        Self::from_bytes_with_options(data, OpenOptions::default())
155    }
156
157    /// Open a GRIB file from an owned byte buffer using explicit decoder options.
158    pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
159        Self::from_data(GribData::Bytes(data), options)
160    }
161
162    fn from_data(data: GribData, options: OpenOptions) -> Result<Self> {
163        let messages = scan_messages(data.as_bytes(), options)?;
164        if messages.is_empty() {
165            return Err(Error::NoMessages);
166        }
167        Ok(Self { data, messages })
168    }
169
170    /// Returns the GRIB edition of the first field.
171    pub fn edition(&self) -> u8 {
172        self.messages
173            .first()
174            .map(|message| message.metadata.edition)
175            .unwrap_or(0)
176    }
177
178    /// Returns the number of logical fields in the file.
179    pub fn message_count(&self) -> usize {
180        self.messages.len()
181    }
182
183    /// Access a field by index.
184    pub fn message(&self, index: usize) -> Result<Message<'_>> {
185        let record = self
186            .messages
187            .get(index)
188            .ok_or(Error::MessageNotFound(index))?;
189        let bytes = &self.data.as_bytes()[record.offset..record.offset + record.length];
190        Ok(Message {
191            index,
192            bytes,
193            metadata: &record.metadata,
194            decode_plan: record.decode_plan,
195        })
196    }
197
198    /// Iterate over all fields.
199    pub fn messages(&self) -> MessageIter<'_> {
200        MessageIter {
201            file: self,
202            index: 0,
203        }
204    }
205
206    /// Decode every field in the file.
207    pub fn read_all_data_as_f64(&self) -> Result<Vec<ArrayD<f64>>> {
208        #[cfg(feature = "rayon")]
209        {
210            (0..self.message_count())
211                .into_par_iter()
212                .map(|index| self.message(index)?.read_data_as_f64())
213                .collect()
214        }
215        #[cfg(not(feature = "rayon"))]
216        {
217            (0..self.message_count())
218                .map(|index| self.message(index)?.read_data_as_f64())
219                .collect()
220        }
221    }
222}
223
224/// A single logical GRIB field.
225pub struct Message<'a> {
226    bytes: &'a [u8],
227    metadata: &'a MessageMetadata,
228    decode_plan: DecodePlan,
229    index: usize,
230}
231
232impl<'a> Message<'a> {
233    pub fn edition(&self) -> u8 {
234        self.metadata.edition
235    }
236
237    pub fn index(&self) -> usize {
238        self.index
239    }
240
241    pub fn metadata(&self) -> &MessageMetadata {
242        self.metadata
243    }
244
245    pub fn reference_time(&self) -> &ReferenceTime {
246        &self.metadata.reference_time
247    }
248
249    pub fn parameter(&self) -> &Parameter {
250        &self.metadata.parameter
251    }
252
253    pub fn center_id(&self) -> u16 {
254        self.metadata.center_id
255    }
256
257    pub fn subcenter_id(&self) -> u16 {
258        self.metadata.subcenter_id
259    }
260
261    pub fn identification(&self) -> Option<&Identification> {
262        self.metadata.grib2_identification.as_ref()
263    }
264
265    pub fn product_definition(&self) -> Option<&ProductDefinition> {
266        self.metadata.grib2_product.as_ref()
267    }
268
269    pub fn grib1_product_definition(&self) -> Option<&grib1::ProductDefinition> {
270        self.metadata.grib1_product.as_ref()
271    }
272
273    pub fn grid_definition(&self) -> &GridDefinition {
274        &self.metadata.grid
275    }
276
277    pub fn parameter_name(&self) -> &'static str {
278        self.metadata.parameter.short_name
279    }
280
281    pub fn parameter_description(&self) -> &'static str {
282        self.metadata.parameter.description
283    }
284
285    pub fn forecast_time_unit(&self) -> Option<u8> {
286        self.metadata.forecast_time_unit
287    }
288
289    pub fn forecast_time(&self) -> Option<u32> {
290        self.metadata.forecast_time
291    }
292
293    pub fn valid_time(&self) -> Option<ReferenceTime> {
294        let unit = self.metadata.forecast_time_unit?;
295        let lead = self.metadata.forecast_time?;
296        self.metadata
297            .reference_time
298            .checked_add_forecast_time(unit, lead)
299    }
300
301    pub fn grid_shape(&self) -> (usize, usize) {
302        self.metadata.grid.shape()
303    }
304
305    pub fn latitudes(&self) -> Option<Vec<f64>> {
306        match &self.metadata.grid {
307            GridDefinition::LatLon(grid) => Some(grid.latitudes()),
308            GridDefinition::Unsupported(_) => None,
309        }
310    }
311
312    pub fn longitudes(&self) -> Option<Vec<f64>> {
313        match &self.metadata.grid {
314            GridDefinition::LatLon(grid) => Some(grid.longitudes()),
315            GridDefinition::Unsupported(_) => None,
316        }
317    }
318
319    pub fn read_flat_data_as_f64(&self) -> Result<Vec<f64>> {
320        let grid = match &self.metadata.grid {
321            GridDefinition::LatLon(grid) => grid,
322            GridDefinition::Unsupported(template) => {
323                return Err(Error::UnsupportedGridTemplate(*template));
324            }
325        };
326
327        let decoded = match self.decode_plan {
328            DecodePlan::Grib2(field) => {
329                let data_section = section_bytes(self.bytes, field.data);
330                let bitmap_section = match field.bitmap {
331                    Some(section) => grib2_bitmap_payload(section_bytes(self.bytes, section))?,
332                    None => None,
333                };
334                decode_field(
335                    data_section,
336                    &self.metadata.data_representation,
337                    bitmap_section,
338                    self.metadata.grid.num_points(),
339                )?
340            }
341            DecodePlan::Grib1 { bitmap, data } => {
342                let bitmap_section = match bitmap {
343                    Some(section) => grib1::bitmap_payload(section_bytes(self.bytes, section))?,
344                    None => None,
345                };
346                let data_section = section_bytes(self.bytes, data);
347                if data_section.len() < 11 {
348                    return Err(Error::InvalidSection {
349                        section: 4,
350                        reason: format!("expected at least 11 bytes, got {}", data_section.len()),
351                    });
352                }
353                grib1::decode_simple_field(
354                    &data_section[11..],
355                    &self.metadata.data_representation,
356                    bitmap_section,
357                    self.metadata.grid.num_points(),
358                )?
359            }
360        };
361
362        grid.reorder_for_ndarray(decoded)
363    }
364
365    pub fn read_data_as_f64(&self) -> Result<ArrayD<f64>> {
366        let ordered = self.read_flat_data_as_f64()?;
367        ArrayD::from_shape_vec(IxDyn(&self.metadata.grid.ndarray_shape()), ordered)
368            .map_err(|e| Error::Other(format!("failed to build ndarray from decoded field: {e}")))
369    }
370
371    pub fn raw_bytes(&self) -> &[u8] {
372        self.bytes
373    }
374}
375
376/// Iterator over fields in a GRIB file.
377pub struct MessageIter<'a> {
378    file: &'a GribFile,
379    index: usize,
380}
381
382impl<'a> Iterator for MessageIter<'a> {
383    type Item = Message<'a>;
384
385    fn next(&mut self) -> Option<Self::Item> {
386        if self.index >= self.file.message_count() {
387            return None;
388        }
389        let message = self.file.message(self.index).ok()?;
390        self.index += 1;
391        Some(message)
392    }
393}
394
395fn section_bytes(msg_bytes: &[u8], section: SectionRef) -> &[u8] {
396    &msg_bytes[section.offset..section.offset + section.length]
397}
398
399fn scan_messages(data: &[u8], options: OpenOptions) -> Result<Vec<MessageIndex>> {
400    let mut messages = Vec::new();
401    let mut pos = 0usize;
402
403    while pos + 8 <= data.len() {
404        if &data[pos..pos + 4] != GRIB_MAGIC {
405            pos += 1;
406            continue;
407        }
408
409        match try_index_message(data, pos) {
410            Ok((indexed, next_pos)) => {
411                messages.extend(indexed);
412                pos = next_pos;
413            }
414            Err(err) if !options.strict => {
415                pos += 4;
416                let _ = err;
417            }
418            Err(err) => return Err(err),
419        }
420    }
421
422    Ok(messages)
423}
424
425fn try_index_message(data: &[u8], pos: usize) -> Result<(Vec<MessageIndex>, usize)> {
426    let indicator = Indicator::parse(&data[pos..]).ok_or_else(|| {
427        Error::InvalidMessage(format!("failed to parse indicator at byte offset {pos}"))
428    })?;
429    let length = indicator.total_length as usize;
430    if length < 12 {
431        return Err(Error::InvalidMessage(format!(
432            "message at byte offset {pos} reports impossible length {length}"
433        )));
434    }
435    let end = pos
436        .checked_add(length)
437        .ok_or_else(|| Error::InvalidMessage("message length overflow".into()))?;
438    if end > data.len() {
439        return Err(Error::Truncated { offset: end as u64 });
440    }
441    if &data[end - 4..end] != b"7777" {
442        return Err(Error::InvalidMessage(format!(
443            "message at byte offset {pos} does not end with 7777"
444        )));
445    }
446
447    let message_bytes = &data[pos..end];
448    let indexed = match indicator.edition {
449        1 => index_grib1_message(message_bytes, pos)?,
450        2 => index_grib2_message(message_bytes, pos, &indicator)?,
451        other => return Err(Error::UnsupportedEdition(other)),
452    };
453
454    Ok((indexed, end))
455}
456
457fn index_grib1_message(message_bytes: &[u8], offset: usize) -> Result<Vec<MessageIndex>> {
458    let sections = grib1::parse_message_sections(message_bytes)?;
459    let grid_ref = sections.grid.ok_or_else(|| {
460        Error::InvalidSectionOrder(
461            "GRIB1 decoding requires an explicit grid definition section".into(),
462        )
463    })?;
464    let grid_description = GridDescription::parse(section_bytes(message_bytes, grid_ref))?;
465    let grid = grid_description.grid;
466
467    let bitmap_present_count = match sections.bitmap {
468        Some(bitmap) => count_bitmap_present_points(
469            grib1::bitmap_payload(section_bytes(message_bytes, bitmap))?,
470            grid.num_points(),
471        )?,
472        None => grid.num_points(),
473    };
474
475    let (_bds, data_representation) = BinaryDataSection::parse(
476        section_bytes(message_bytes, sections.data),
477        sections.product.decimal_scale,
478        bitmap_present_count,
479    )?;
480    let parameter = sections.product.parameter();
481
482    Ok(vec![MessageIndex {
483        offset,
484        length: message_bytes.len(),
485        decode_plan: DecodePlan::Grib1 {
486            bitmap: sections.bitmap,
487            data: sections.data,
488        },
489        metadata: MessageMetadata {
490            edition: 1,
491            center_id: sections.product.center_id as u16,
492            subcenter_id: sections.product.subcenter_id as u16,
493            discipline: None,
494            reference_time: sections.product.reference_time,
495            parameter,
496            grid,
497            data_representation,
498            forecast_time_unit: Some(sections.product.forecast_time_unit),
499            forecast_time: sections.product.forecast_time(),
500            message_offset: offset as u64,
501            message_length: message_bytes.len() as u64,
502            field_index_in_message: 0,
503            grib1_product: Some(sections.product),
504            grib2_identification: None,
505            grib2_product: None,
506        },
507    }])
508}
509
510fn index_grib2_message(
511    message_bytes: &[u8],
512    offset: usize,
513    indicator: &Indicator,
514) -> Result<Vec<MessageIndex>> {
515    let fields = index_fields(message_bytes)?;
516    let mut messages = Vec::with_capacity(fields.len());
517
518    for (field_index_in_message, field_sections) in fields.into_iter().enumerate() {
519        let identification =
520            Identification::parse(section_bytes(message_bytes, field_sections.identification))?;
521        let grid = GridDefinition::parse(section_bytes(message_bytes, field_sections.grid))?;
522        let product =
523            ProductDefinition::parse(section_bytes(message_bytes, field_sections.product))?;
524        let data_representation = DataRepresentation::parse(section_bytes(
525            message_bytes,
526            field_sections.data_representation,
527        ))?;
528        let parameter = Parameter::new_grib2(
529            indicator.discipline,
530            product.parameter_category,
531            product.parameter_number,
532            product.parameter_name(indicator.discipline),
533            product.parameter_description(indicator.discipline),
534        );
535
536        messages.push(MessageIndex {
537            offset,
538            length: message_bytes.len(),
539            decode_plan: DecodePlan::Grib2(field_sections),
540            metadata: MessageMetadata {
541                edition: indicator.edition,
542                center_id: identification.center_id,
543                subcenter_id: identification.subcenter_id,
544                discipline: Some(indicator.discipline),
545                reference_time: ReferenceTime {
546                    year: identification.reference_year,
547                    month: identification.reference_month,
548                    day: identification.reference_day,
549                    hour: identification.reference_hour,
550                    minute: identification.reference_minute,
551                    second: identification.reference_second,
552                },
553                parameter,
554                grid,
555                data_representation,
556                forecast_time_unit: product.forecast_time_unit,
557                forecast_time: product.forecast_time,
558                message_offset: offset as u64,
559                message_length: message_bytes.len() as u64,
560                field_index_in_message,
561                grib1_product: None,
562                grib2_identification: Some(identification),
563                grib2_product: Some(product),
564            },
565        });
566    }
567
568    Ok(messages)
569}
570
571fn count_bitmap_present_points(bitmap: Option<&[u8]>, num_grid_points: usize) -> Result<usize> {
572    let Some(payload) = bitmap else {
573        return Ok(0);
574    };
575
576    let full_bytes = num_grid_points / 8;
577    let remaining_bits = num_grid_points % 8;
578    let required_bytes = full_bytes + usize::from(remaining_bits > 0);
579    if payload.len() < required_bytes {
580        return Err(Error::MissingBitmap);
581    }
582
583    let mut present = payload[..full_bytes]
584        .iter()
585        .map(|byte| byte.count_ones() as usize)
586        .sum();
587    if remaining_bits > 0 {
588        let mask = u8::MAX << (8 - remaining_bits);
589        present += (payload[full_bytes] & mask).count_ones() as usize;
590    }
591
592    Ok(present)
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    fn grib_i32_bytes(value: i32) -> [u8; 4] {
600        if value >= 0 {
601            (value as u32).to_be_bytes()
602        } else {
603            ((-value) as u32 | 0x8000_0000).to_be_bytes()
604        }
605    }
606
607    fn build_indicator(total_len: usize, discipline: u8) -> Vec<u8> {
608        let mut indicator = Vec::with_capacity(16);
609        indicator.extend_from_slice(b"GRIB");
610        indicator.extend_from_slice(&[0, 0]);
611        indicator.push(discipline);
612        indicator.push(2);
613        indicator.extend_from_slice(&(total_len as u64).to_be_bytes());
614        indicator
615    }
616
617    fn build_identification() -> Vec<u8> {
618        let mut section = vec![0u8; 21];
619        section[..4].copy_from_slice(&(21u32).to_be_bytes());
620        section[4] = 1;
621        section[5..7].copy_from_slice(&7u16.to_be_bytes());
622        section[7..9].copy_from_slice(&0u16.to_be_bytes());
623        section[9] = 35;
624        section[10] = 1;
625        section[11] = 1;
626        section[12..14].copy_from_slice(&2026u16.to_be_bytes());
627        section[14] = 3;
628        section[15] = 20;
629        section[16] = 12;
630        section[17] = 0;
631        section[18] = 0;
632        section[19] = 0;
633        section[20] = 1;
634        section
635    }
636
637    fn build_grid(ni: u32, nj: u32, scanning_mode: u8) -> Vec<u8> {
638        let mut section = vec![0u8; 72];
639        section[..4].copy_from_slice(&(72u32).to_be_bytes());
640        section[4] = 3;
641        section[6..10].copy_from_slice(&(ni * nj).to_be_bytes());
642        section[12..14].copy_from_slice(&0u16.to_be_bytes());
643        section[30..34].copy_from_slice(&ni.to_be_bytes());
644        section[34..38].copy_from_slice(&nj.to_be_bytes());
645        section[46..50].copy_from_slice(&grib_i32_bytes(50_000_000));
646        section[50..54].copy_from_slice(&grib_i32_bytes(-120_000_000));
647        section[55..59].copy_from_slice(&grib_i32_bytes(49_000_000));
648        section[59..63].copy_from_slice(&grib_i32_bytes(-119_000_000));
649        section[63..67].copy_from_slice(&1_000_000u32.to_be_bytes());
650        section[67..71].copy_from_slice(&1_000_000u32.to_be_bytes());
651        section[71] = scanning_mode;
652        section
653    }
654
655    fn build_product(parameter_category: u8, parameter_number: u8) -> Vec<u8> {
656        let mut section = vec![0u8; 34];
657        section[..4].copy_from_slice(&(34u32).to_be_bytes());
658        section[4] = 4;
659        section[7..9].copy_from_slice(&0u16.to_be_bytes());
660        section[9] = parameter_category;
661        section[10] = parameter_number;
662        section[11] = 2;
663        section[17] = 1;
664        section[18..22].copy_from_slice(&0u32.to_be_bytes());
665        section[22] = 103;
666        section[23] = 0;
667        section[24..28].copy_from_slice(&850u32.to_be_bytes());
668        section[28] = 255;
669        section
670    }
671
672    fn build_simple_representation(encoded_values: usize, bits_per_value: u8) -> Vec<u8> {
673        let mut section = vec![0u8; 21];
674        section[..4].copy_from_slice(&(21u32).to_be_bytes());
675        section[4] = 5;
676        section[5..9].copy_from_slice(&(encoded_values as u32).to_be_bytes());
677        section[9..11].copy_from_slice(&0u16.to_be_bytes());
678        section[11..15].copy_from_slice(&0f32.to_be_bytes());
679        section[19] = bits_per_value;
680        section[20] = 0;
681        section
682    }
683
684    fn pack_u8_values(values: &[u8]) -> Vec<u8> {
685        values.to_vec()
686    }
687
688    fn build_bitmap(bits: &[bool]) -> Vec<u8> {
689        let payload_len = bits.len().div_ceil(8) + 1;
690        let mut section = vec![0u8; payload_len + 5];
691        section[..4].copy_from_slice(&((payload_len + 5) as u32).to_be_bytes());
692        section[4] = 6;
693        section[5] = 0;
694        for (index, bit) in bits.iter().copied().enumerate() {
695            if bit {
696                section[6 + index / 8] |= 1 << (7 - (index % 8));
697            }
698        }
699        section
700    }
701
702    fn build_data(payload: &[u8]) -> Vec<u8> {
703        let mut section = vec![0u8; payload.len() + 5];
704        section[..4].copy_from_slice(&((payload.len() + 5) as u32).to_be_bytes());
705        section[4] = 7;
706        section[5..].copy_from_slice(payload);
707        section
708    }
709
710    fn assemble_grib2_message(sections: &[Vec<u8>]) -> Vec<u8> {
711        let total_len = 16 + sections.iter().map(|section| section.len()).sum::<usize>() + 4;
712        let mut message = build_indicator(total_len, 0);
713        for section in sections {
714            message.extend_from_slice(section);
715        }
716        message.extend_from_slice(b"7777");
717        message
718    }
719
720    fn build_grib1_message(values: &[u8]) -> Vec<u8> {
721        let mut pds = vec![0u8; 28];
722        pds[..3].copy_from_slice(&[0, 0, 28]);
723        pds[3] = 2;
724        pds[4] = 7;
725        pds[5] = 255;
726        pds[6] = 0;
727        pds[7] = 0b1000_0000;
728        pds[8] = 11;
729        pds[9] = 100;
730        pds[10..12].copy_from_slice(&850u16.to_be_bytes());
731        pds[12] = 26;
732        pds[13] = 3;
733        pds[14] = 20;
734        pds[15] = 12;
735        pds[16] = 0;
736        pds[17] = 1;
737        pds[18] = 0;
738        pds[19] = 0;
739        pds[20] = 0;
740        pds[24] = 21;
741        pds[25] = 0;
742
743        let mut gds = vec![0u8; 32];
744        gds[..3].copy_from_slice(&[0, 0, 32]);
745        gds[5] = 0;
746        gds[6..8].copy_from_slice(&2u16.to_be_bytes());
747        gds[8..10].copy_from_slice(&2u16.to_be_bytes());
748        gds[10..13].copy_from_slice(&[0x01, 0x4d, 0x50]);
749        gds[13..16].copy_from_slice(&[0x81, 0xd4, 0xc0]);
750        gds[16] = 0x80;
751        gds[17..20].copy_from_slice(&[0x01, 0x49, 0x68]);
752        gds[20..23].copy_from_slice(&[0x81, 0xd0, 0xd8]);
753        gds[23..25].copy_from_slice(&1000u16.to_be_bytes());
754        gds[25..27].copy_from_slice(&1000u16.to_be_bytes());
755
756        let mut bds = vec![0u8; 11 + values.len()];
757        let len = bds.len() as u32;
758        bds[..3].copy_from_slice(&[(len >> 16) as u8, (len >> 8) as u8, len as u8]);
759        bds[3] = 0;
760        bds[10] = 8;
761        bds[11..].copy_from_slice(values);
762
763        let total_len = 8 + pds.len() + gds.len() + bds.len() + 4;
764        let mut message = Vec::new();
765        message.extend_from_slice(b"GRIB");
766        message.extend_from_slice(&[
767            ((total_len >> 16) & 0xff) as u8,
768            ((total_len >> 8) & 0xff) as u8,
769            (total_len & 0xff) as u8,
770            1,
771        ]);
772        message.extend_from_slice(&pds);
773        message.extend_from_slice(&gds);
774        message.extend_from_slice(&bds);
775        message.extend_from_slice(b"7777");
776        message
777    }
778
779    #[test]
780    fn scans_single_grib2_message() {
781        let message = assemble_grib2_message(&[
782            build_identification(),
783            build_grid(2, 2, 0),
784            build_product(0, 0),
785            build_simple_representation(4, 8),
786            build_data(&pack_u8_values(&[1, 2, 3, 4])),
787        ]);
788        let messages = scan_messages(&message, OpenOptions::default()).unwrap();
789        assert_eq!(messages.len(), 1);
790        assert_eq!(messages[0].metadata.parameter.short_name, "TMP");
791    }
792
793    #[test]
794    fn decodes_simple_grib2_message_to_ndarray() {
795        let message = assemble_grib2_message(&[
796            build_identification(),
797            build_grid(2, 2, 0),
798            build_product(0, 0),
799            build_simple_representation(4, 8),
800            build_data(&pack_u8_values(&[1, 2, 3, 4])),
801        ]);
802        let file = GribFile::from_bytes(message).unwrap();
803        let array = file.message(0).unwrap().read_data_as_f64().unwrap();
804        assert_eq!(array.shape(), &[2, 2]);
805        assert_eq!(
806            array.iter().copied().collect::<Vec<_>>(),
807            vec![1.0, 2.0, 3.0, 4.0]
808        );
809    }
810
811    #[test]
812    fn applies_bitmap_to_missing_values() {
813        let message = assemble_grib2_message(&[
814            build_identification(),
815            build_grid(2, 2, 0),
816            build_product(0, 1),
817            build_simple_representation(3, 8),
818            build_bitmap(&[true, false, true, true]),
819            build_data(&pack_u8_values(&[10, 20, 30])),
820        ]);
821        let file = GribFile::from_bytes(message).unwrap();
822        let array = file.message(0).unwrap().read_data_as_f64().unwrap();
823        let values = array.iter().copied().collect::<Vec<_>>();
824        assert_eq!(values[0], 10.0);
825        assert!(values[1].is_nan());
826        assert_eq!(values[2], 20.0);
827        assert_eq!(values[3], 30.0);
828    }
829
830    #[test]
831    fn indexes_multiple_fields_in_one_grib2_message() {
832        let message = assemble_grib2_message(&[
833            build_identification(),
834            build_grid(2, 2, 0),
835            build_product(0, 0),
836            build_simple_representation(4, 8),
837            build_data(&pack_u8_values(&[1, 2, 3, 4])),
838            build_product(0, 2),
839            build_simple_representation(4, 8),
840            build_data(&pack_u8_values(&[5, 6, 7, 8])),
841        ]);
842        let file = GribFile::from_bytes(message).unwrap();
843        assert_eq!(file.message_count(), 2);
844        assert_eq!(file.message(0).unwrap().parameter_name(), "TMP");
845        assert_eq!(file.message(1).unwrap().parameter_name(), "POT");
846    }
847
848    #[test]
849    fn decodes_simple_grib1_message_to_ndarray() {
850        let message = build_grib1_message(&[1, 2, 3, 4]);
851        let file = GribFile::from_bytes(message).unwrap();
852        assert_eq!(file.edition(), 1);
853        let field = file.message(0).unwrap();
854        assert_eq!(field.parameter_name(), "TMP");
855        assert!(field.identification().is_none());
856        assert!(field.grib1_product_definition().is_some());
857        let array = field.read_data_as_f64().unwrap();
858        assert_eq!(array.shape(), &[2, 2]);
859        assert_eq!(
860            array.iter().copied().collect::<Vec<_>>(),
861            vec![1.0, 2.0, 3.0, 4.0]
862        );
863    }
864}