1pub 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#[derive(Debug, Clone, Copy)]
66pub struct OpenOptions {
67 pub strict: bool,
71}
72
73impl Default for OpenOptions {
74 fn default() -> Self {
75 Self { strict: true }
76 }
77}
78
79#[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
100pub 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 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
140 Self::open_with_options(path, OpenOptions::default())
141 }
142
143 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 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
154 Self::from_bytes_with_options(data, OpenOptions::default())
155 }
156
157 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 pub fn edition(&self) -> u8 {
172 self.messages
173 .first()
174 .map(|message| message.metadata.edition)
175 .unwrap_or(0)
176 }
177
178 pub fn message_count(&self) -> usize {
180 self.messages.len()
181 }
182
183 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 pub fn messages(&self) -> MessageIter<'_> {
200 MessageIter {
201 file: self,
202 index: 0,
203 }
204 }
205
206 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
224pub 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
376pub 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}