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