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