grib/
context.rs

1use std::{
2    cell::{RefCell, RefMut},
3    collections::HashSet,
4    fmt::{self, Display, Formatter},
5    io::{Cursor, Read, Seek},
6};
7
8use crate::{
9    codetables::{
10        CodeTable3_1, CodeTable4_0, CodeTable4_1, CodeTable4_2, CodeTable4_3, CodeTable5_0, Lookup,
11    },
12    datatypes::*,
13    error::*,
14    grid::GridPointIterator,
15    parser::Grib2SubmessageIndexStream,
16    reader::{Grib2Read, Grib2SectionStream, SeekableGrib2Reader, SECT8_ES_SIZE},
17    GridPointIndexIterator,
18};
19
20#[derive(Default, Debug, Clone, PartialEq, Eq)]
21pub struct SectionInfo {
22    pub num: u8,
23    pub offset: usize,
24    pub size: usize,
25    pub body: Option<SectionBody>,
26}
27
28impl SectionInfo {
29    pub fn get_tmpl_code(&self) -> Option<TemplateInfo> {
30        let tmpl_num = self.body.as_ref()?.get_tmpl_num()?;
31        Some(TemplateInfo(self.num, tmpl_num))
32    }
33
34    pub(crate) fn new_8(offset: usize) -> Self {
35        Self {
36            num: 8,
37            offset,
38            size: SECT8_ES_SIZE,
39            body: None,
40        }
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum SectionBody {
46    Section0(Indicator),
47    Section1(Identification),
48    Section2(LocalUse),
49    Section3(GridDefinition),
50    Section4(ProdDefinition),
51    Section5(ReprDefinition),
52    Section6(BitMap),
53    Section7,
54}
55
56impl SectionBody {
57    fn get_tmpl_num(&self) -> Option<u16> {
58        match self {
59            Self::Section3(s) => Some(s.grid_tmpl_num()),
60            Self::Section4(s) => Some(s.prod_tmpl_num()),
61            Self::Section5(s) => Some(s.repr_tmpl_num()),
62            _ => None,
63        }
64    }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
68pub struct TemplateInfo(pub u8, pub u16);
69
70impl TemplateInfo {
71    pub fn describe(&self) -> Option<String> {
72        match self.0 {
73            3 => Some(CodeTable3_1.lookup(usize::from(self.1)).to_string()),
74            4 => Some(CodeTable4_0.lookup(usize::from(self.1)).to_string()),
75            5 => Some(CodeTable5_0.lookup(usize::from(self.1)).to_string()),
76            _ => None,
77        }
78    }
79}
80
81impl Display for TemplateInfo {
82    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
83        write!(f, "{}.{}", self.0, self.1)
84    }
85}
86
87/// Reads a [`Grib2`] instance from an I/O stream of GRIB2.
88///
89/// # Examples
90///
91/// ```
92/// fn main() -> Result<(), Box<dyn std::error::Error>> {
93///     let f = std::fs::File::open(
94///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
95///     )?;
96///     let f = std::io::BufReader::new(f);
97///     let result = grib::from_reader(f);
98///
99///     assert!(result.is_ok());
100///     let grib2 = result?;
101///     assert_eq!(grib2.len(), 1);
102///     Ok(())
103/// }
104/// ```
105pub fn from_reader<SR: Read + Seek>(
106    reader: SR,
107) -> Result<Grib2<SeekableGrib2Reader<SR>>, GribError> {
108    Grib2::<SeekableGrib2Reader<SR>>::read_with_seekable(reader)
109}
110
111/// Reads a [`Grib2`] instance from bytes of GRIB2.
112///
113/// # Examples
114///
115/// You can use this method to create a reader from a slice, i.e., a borrowed
116/// sequence of bytes:
117///
118/// ```
119/// use std::io::Read;
120///
121/// fn main() -> Result<(), Box<dyn std::error::Error>> {
122///     let f = std::fs::File::open(
123///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
124///     )?;
125///     let mut f = std::io::BufReader::new(f);
126///     let mut buf = Vec::new();
127///     f.read_to_end(&mut buf).unwrap();
128///     let result = grib::from_bytes(&buf);
129///
130///     assert!(result.is_ok());
131///     let grib2 = result?;
132///     assert_eq!(grib2.len(), 1);
133///     Ok(())
134/// }
135/// ```
136///
137/// Also, you can use this method to create a reader from an owned sequence of
138/// bytes:
139///
140/// ```
141/// use std::io::Read;
142///
143/// fn main() -> Result<(), Box<dyn std::error::Error>> {
144///     let f = std::fs::File::open(
145///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
146///     )?;
147///     let mut f = std::io::BufReader::new(f);
148///     let mut buf = Vec::new();
149///     f.read_to_end(&mut buf).unwrap();
150///     let result = grib::from_bytes(buf);
151///
152///     assert!(result.is_ok());
153///     let grib2 = result?;
154///     assert_eq!(grib2.len(), 1);
155///     Ok(())
156/// }
157/// ```
158pub fn from_bytes<T>(bytes: T) -> Result<Grib2<SeekableGrib2Reader<Cursor<T>>>, GribError>
159where
160    T: AsRef<[u8]>,
161{
162    let reader = Cursor::new(bytes);
163    Grib2::<SeekableGrib2Reader<Cursor<T>>>::read_with_seekable(reader)
164}
165
166pub struct Grib2<R> {
167    reader: RefCell<R>,
168    sections: Box<[SectionInfo]>,
169    submessages: Vec<Grib2SubmessageIndex>,
170}
171
172impl<R> Grib2<R> {
173    /// Returns the length of submessages in the data.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
179    ///     let f = std::fs::File::open(
180    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
181    ///     )?;
182    ///     let f = std::io::BufReader::new(f);
183    ///     let grib2 = grib::from_reader(f)?;
184    ///
185    ///     assert_eq!(grib2.len(), 1);
186    ///     Ok(())
187    /// }
188    /// ```
189    pub fn len(&self) -> usize {
190        self.submessages.len()
191    }
192
193    /// Returns `true` if `self` has zero submessages.
194    #[inline]
195    pub fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198
199    /// Returns an iterator over submessages in the data.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
205    ///     let f = std::fs::File::open(
206    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
207    ///     )?;
208    ///     let f = std::io::BufReader::new(f);
209    ///     let grib2 = grib::from_reader(f)?;
210    ///
211    ///     let mut iter = grib2.iter();
212    ///     let first = iter.next();
213    ///     assert!(first.is_some());
214    ///
215    ///     let first = first.unwrap();
216    ///     let (message_index, _) = first;
217    ///     assert_eq!(message_index, (0, 0));
218    ///
219    ///     let second = iter.next();
220    ///     assert!(second.is_none());
221    ///     Ok(())
222    /// }
223    /// ```
224    #[inline]
225    pub fn iter(&self) -> SubmessageIterator<'_, R> {
226        self.into_iter()
227    }
228
229    /// Returns an iterator over submessages in the data.
230    ///
231    /// This is an alias to [`Grib2::iter()`].
232    pub fn submessages(&self) -> SubmessageIterator<'_, R> {
233        self.into_iter()
234    }
235
236    /// Returns an iterator over sections in the data.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
242    ///     let f = std::fs::File::open(
243    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
244    ///     )?;
245    ///     let f = std::io::BufReader::new(f);
246    ///     let grib2 = grib::from_reader(f)?;
247    ///
248    ///     let mut iter = grib2.sections();
249    ///     let first = iter.next();
250    ///     assert!(first.is_some());
251    ///
252    ///     let first = first.unwrap();
253    ///     assert_eq!(first.num, 0);
254    ///
255    ///     let tenth = iter.nth(9);
256    ///     assert!(tenth.is_none());
257    ///     Ok(())
258    /// }
259    /// ```
260    pub fn sections(&self) -> std::slice::Iter<'_, SectionInfo> {
261        self.sections.iter()
262    }
263}
264
265impl<R: Grib2Read> Grib2<R> {
266    pub fn read(r: R) -> Result<Self, GribError> {
267        let mut sect_stream = Grib2SectionStream::new(r);
268        let mut cacher = Vec::new();
269        let parser = Grib2SubmessageIndexStream::new(sect_stream.by_ref()).with_cacher(&mut cacher);
270        let submessages = parser.collect::<Result<Vec<_>, _>>()?;
271        Ok(Self {
272            reader: RefCell::new(sect_stream.into_reader()),
273            sections: cacher.into_boxed_slice(),
274            submessages,
275        })
276    }
277
278    pub fn read_with_seekable<SR: Read + Seek>(
279        r: SR,
280    ) -> Result<Grib2<SeekableGrib2Reader<SR>>, GribError> {
281        let r = SeekableGrib2Reader::new(r);
282        Grib2::<SeekableGrib2Reader<SR>>::read(r)
283    }
284
285    pub fn list_templates(&self) -> Vec<TemplateInfo> {
286        get_templates(&self.sections)
287    }
288}
289
290impl<'a, R: 'a> IntoIterator for &'a Grib2<R> {
291    type Item = (MessageIndex, SubMessage<'a, R>);
292    type IntoIter = SubmessageIterator<'a, R>;
293
294    fn into_iter(self) -> Self::IntoIter {
295        Self::IntoIter::new(self)
296    }
297}
298
299fn get_templates(sects: &[SectionInfo]) -> Vec<TemplateInfo> {
300    let uniq: HashSet<_> = sects.iter().filter_map(|s| s.get_tmpl_code()).collect();
301    let mut vec: Vec<_> = uniq.into_iter().collect();
302    vec.sort_unstable();
303    vec
304}
305
306/// An iterator over submessages in the GRIB data.
307///
308/// This `struct` is created by the [`iter`] method on [`Grib2`]. See its
309/// documentation for more.
310///
311/// [`iter`]: Grib2::iter
312#[derive(Clone)]
313pub struct SubmessageIterator<'a, R> {
314    context: &'a Grib2<R>,
315    pos: usize,
316}
317
318impl<'a, R> SubmessageIterator<'a, R> {
319    fn new(context: &'a Grib2<R>) -> Self {
320        Self { context, pos: 0 }
321    }
322
323    fn new_submessage_section(&self, index: usize) -> Option<SubMessageSection<'a>> {
324        Some(SubMessageSection::new(
325            index,
326            self.context.sections.get(index)?,
327        ))
328    }
329}
330
331impl<'a, R> Iterator for SubmessageIterator<'a, R> {
332    type Item = (MessageIndex, SubMessage<'a, R>);
333
334    fn next(&mut self) -> Option<Self::Item> {
335        let submessage_index = self.context.submessages.get(self.pos)?;
336        self.pos += 1;
337
338        Some((
339            submessage_index.message_index(),
340            SubMessage(
341                self.new_submessage_section(submessage_index.0)?,
342                self.new_submessage_section(submessage_index.1)?,
343                submessage_index
344                    .2
345                    .and_then(|i| self.new_submessage_section(i)),
346                self.new_submessage_section(submessage_index.3)?,
347                self.new_submessage_section(submessage_index.4)?,
348                self.new_submessage_section(submessage_index.5)?,
349                self.new_submessage_section(submessage_index.6)?,
350                self.new_submessage_section(submessage_index.7)?,
351                self.new_submessage_section(submessage_index.8)?,
352                self.context.reader.borrow_mut(),
353            ),
354        ))
355    }
356
357    fn size_hint(&self) -> (usize, Option<usize>) {
358        let size = self.context.submessages.len() - self.pos;
359        (size, Some(size))
360    }
361
362    fn nth(&mut self, n: usize) -> Option<Self::Item> {
363        self.pos = n;
364        self.next()
365    }
366}
367
368impl<'a, R> IntoIterator for &'a SubmessageIterator<'a, R> {
369    type Item = (MessageIndex, SubMessage<'a, R>);
370    type IntoIter = SubmessageIterator<'a, R>;
371
372    fn into_iter(self) -> Self::IntoIter {
373        SubmessageIterator {
374            context: self.context,
375            pos: self.pos,
376        }
377    }
378}
379
380pub struct SubMessage<'a, R>(
381    pub SubMessageSection<'a>,
382    pub SubMessageSection<'a>,
383    pub Option<SubMessageSection<'a>>,
384    pub SubMessageSection<'a>,
385    pub SubMessageSection<'a>,
386    pub SubMessageSection<'a>,
387    pub SubMessageSection<'a>,
388    pub SubMessageSection<'a>,
389    pub SubMessageSection<'a>,
390    pub(crate) RefMut<'a, R>,
391);
392
393impl<R> SubMessage<'_, R> {
394    /// Returns the product's parameter.
395    ///
396    /// In the context of GRIB products, parameters refer to weather elements
397    /// such as air temperature, air pressure, and humidity, and other physical
398    /// quantities.
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// use std::{
404    ///     fs::File,
405    ///     io::{BufReader, Read},
406    /// };
407    ///
408    /// use grib::codetables::NCEP;
409    ///
410    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
411    ///     let mut buf = Vec::new();
412    ///
413    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
414    ///     let f = BufReader::new(f);
415    ///     let mut f = xz2::bufread::XzDecoder::new(f);
416    ///     f.read_to_end(&mut buf)?;
417    ///
418    ///     let f = std::io::Cursor::new(buf);
419    ///     let grib2 = grib::from_reader(f)?;
420    ///
421    ///     let mut iter = grib2.iter();
422    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
423    ///
424    ///     let param = message.parameter();
425    ///     assert_eq!(
426    ///         param,
427    ///         Some(grib::Parameter {
428    ///             discipline: 0,
429    ///             centre: 7,
430    ///             master_ver: 2,
431    ///             local_ver: 1,
432    ///             category: 3,
433    ///             num: 1
434    ///         })
435    ///     );
436    ///     let param = param.unwrap();
437    ///     assert_eq!(
438    ///         param.description(),
439    ///         Some("Pressure reduced to MSL".to_owned())
440    ///     );
441    ///     assert!(param.is_identical_to(NCEP::PRMSL));
442    ///     Ok(())
443    /// }
444    /// ```
445    pub fn parameter(&self) -> Option<Parameter> {
446        let discipline = self.indicator().discipline;
447        let ident = self.identification();
448        let centre = ident.centre_id();
449        let master_ver = ident.master_table_version();
450        let local_ver = ident.local_table_version();
451        let prod_def = self.prod_def();
452        let category = prod_def.parameter_category()?;
453        let num = prod_def.parameter_number()?;
454        Some(Parameter {
455            discipline,
456            centre,
457            master_ver,
458            local_ver,
459            category,
460            num,
461        })
462    }
463
464    pub fn indicator(&self) -> &Indicator {
465        // panics should not happen if data is correct
466        match self.0.body.body.as_ref().unwrap() {
467            SectionBody::Section0(data) => data,
468            _ => panic!("something unexpected happened"),
469        }
470    }
471
472    fn identification(&self) -> &Identification {
473        // panics should not happen if data is correct
474        match self.1.body.body.as_ref().unwrap() {
475            SectionBody::Section1(data) => data,
476            _ => panic!("something unexpected happened"),
477        }
478    }
479
480    pub fn grid_def(&self) -> &GridDefinition {
481        // panics should not happen if data is correct
482        match self.3.body.body.as_ref().unwrap() {
483            SectionBody::Section3(data) => data,
484            _ => panic!("something unexpected happened"),
485        }
486    }
487
488    pub fn prod_def(&self) -> &ProdDefinition {
489        // panics should not happen if data is correct
490        match self.4.body.body.as_ref().unwrap() {
491            SectionBody::Section4(data) => data,
492            _ => panic!("something unexpected happened"),
493        }
494    }
495
496    pub fn repr_def(&self) -> &ReprDefinition {
497        // panics should not happen if data is correct
498        match self.5.body.body.as_ref().unwrap() {
499            SectionBody::Section5(data) => data,
500            _ => panic!("something unexpected happened"),
501        }
502    }
503
504    pub fn describe(&self) -> String {
505        let category = self.prod_def().parameter_category();
506        let forecast_time = self
507            .prod_def()
508            .forecast_time()
509            .map(|ft| ft.describe())
510            .unwrap_or((String::new(), String::new()));
511        let fixed_surfaces_info = self
512            .prod_def()
513            .fixed_surfaces()
514            .map(|(first, second)| (first.describe(), second.describe()))
515            .map(|(first, second)| (first.0, first.1, first.2, second.0, second.1, second.2))
516            .unwrap_or((
517                String::new(),
518                String::new(),
519                String::new(),
520                String::new(),
521                String::new(),
522                String::new(),
523            ));
524
525        format!(
526            "\
527Grid:                                   {}
528  Number of points:                     {}
529Product:                                {}
530  Parameter Category:                   {}
531  Parameter:                            {}
532  Generating Proceess:                  {}
533  Forecast Time:                        {}
534  Forecast Time Unit:                   {}
535  1st Fixed Surface Type:               {}
536  1st Scale Factor:                     {}
537  1st Scaled Value:                     {}
538  2nd Fixed Surface Type:               {}
539  2nd Scale Factor:                     {}
540  2nd Scaled Value:                     {}
541Data Representation:                    {}
542  Number of represented values:         {}
543",
544            self.3.describe().unwrap_or_default(),
545            self.grid_def().num_points(),
546            self.4.describe().unwrap_or_default(),
547            category
548                .map(|v| CodeTable4_1::new(self.indicator().discipline)
549                    .lookup(usize::from(v))
550                    .to_string())
551                .unwrap_or_default(),
552            self.prod_def()
553                .parameter_number()
554                .zip(category)
555                .map(|(n, c)| CodeTable4_2::new(self.indicator().discipline, c)
556                    .lookup(usize::from(n))
557                    .to_string())
558                .unwrap_or_default(),
559            self.prod_def()
560                .generating_process()
561                .map(|v| CodeTable4_3.lookup(usize::from(v)).to_string())
562                .unwrap_or_default(),
563            forecast_time.1,
564            forecast_time.0,
565            fixed_surfaces_info.0,
566            fixed_surfaces_info.1,
567            fixed_surfaces_info.2,
568            fixed_surfaces_info.3,
569            fixed_surfaces_info.4,
570            fixed_surfaces_info.5,
571            self.5.describe().unwrap_or_default(),
572            self.repr_def().num_points(),
573        )
574    }
575
576    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
577    /// the i and j directions.
578    ///
579    /// # Examples
580    ///
581    /// ```
582    /// use std::{
583    ///     fs::File,
584    ///     io::{BufReader, Read},
585    /// };
586    ///
587    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
588    ///     let mut buf = Vec::new();
589    ///
590    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
591    ///     let f = BufReader::new(f);
592    ///     let mut f = xz2::bufread::XzDecoder::new(f);
593    ///     f.read_to_end(&mut buf)?;
594    ///
595    ///     let f = std::io::Cursor::new(buf);
596    ///     let grib2 = grib::from_reader(f)?;
597    ///
598    ///     let mut iter = grib2.iter();
599    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
600    ///
601    ///     let shape = message.grid_shape()?;
602    ///     assert_eq!(shape, (1440, 721));
603    ///     Ok(())
604    /// }
605    /// ```
606    pub fn grid_shape(&self) -> Result<(usize, usize), GribError> {
607        let grid_def = self.grid_def();
608        let shape = GridDefinitionTemplateValues::try_from(grid_def)?.grid_shape();
609        Ok(shape)
610    }
611
612    /// Computes and returns an iterator over `(i, j)` of grid points.
613    ///
614    /// The order of items is the same as the order of the grid point values,
615    /// defined by the scanning mode ([`ScanningMode`](`crate::ScanningMode`))
616    /// in the data.
617    ///
618    /// This iterator allows users to perform their own coordinate calculations
619    /// for unsupported grid systems and map the results to grid point values.
620    ///
621    /// # Examples
622    ///
623    /// ```
624    /// use std::{
625    ///     fs::File,
626    ///     io::{BufReader, Read},
627    /// };
628    ///
629    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
630    ///     let mut buf = Vec::new();
631    ///
632    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
633    ///     let f = BufReader::new(f);
634    ///     let mut f = xz2::bufread::XzDecoder::new(f);
635    ///     f.read_to_end(&mut buf)?;
636    ///
637    ///     let f = std::io::Cursor::new(buf);
638    ///     let grib2 = grib::from_reader(f)?;
639    ///
640    ///     let mut iter = grib2.iter();
641    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
642    ///
643    ///     let mut latlons = message.ij()?;
644    ///     assert_eq!(latlons.next(), Some((0, 0)));
645    ///     assert_eq!(latlons.next(), Some((1, 0)));
646    ///     Ok(())
647    /// }
648    /// ```
649    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
650        let grid_def = self.grid_def();
651        let num_defined = grid_def.num_points() as usize;
652        let ij = GridDefinitionTemplateValues::try_from(grid_def)?.ij()?;
653        let (num_decoded, _) = ij.size_hint();
654        if num_defined == num_decoded {
655            Ok(ij)
656        } else {
657            Err(GribError::InvalidValueError(format!(
658                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
659            )))
660        }
661    }
662
663    /// Computes and returns an iterator over latitudes and longitudes of grid
664    /// points.
665    ///
666    /// The order of lat/lon data of grid points is the same as the order of the
667    /// grid point values, defined by the scanning mode
668    /// ([`ScanningMode`](`crate::ScanningMode`)) in the data.
669    ///
670    /// # Examples
671    ///
672    /// ```
673    /// use std::{
674    ///     fs::File,
675    ///     io::{BufReader, Read},
676    /// };
677    ///
678    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
679    ///     let mut buf = Vec::new();
680    ///
681    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
682    ///     let f = BufReader::new(f);
683    ///     let mut f = xz2::bufread::XzDecoder::new(f);
684    ///     f.read_to_end(&mut buf)?;
685    ///
686    ///     let f = std::io::Cursor::new(buf);
687    ///     let grib2 = grib::from_reader(f)?;
688    ///
689    ///     let mut iter = grib2.iter();
690    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
691    ///
692    ///     let mut latlons = message.latlons()?;
693    ///     assert_eq!(latlons.next(), Some((90.0, 0.0)));
694    ///     assert_eq!(latlons.next(), Some((90.0, 0.25000003)));
695    ///     Ok(())
696    /// }
697    /// ```
698    pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
699        let grid_def = self.grid_def();
700        let num_defined = grid_def.num_points() as usize;
701        let latlons = GridDefinitionTemplateValues::try_from(grid_def)?.latlons()?;
702        let (num_decoded, _) = latlons.size_hint();
703        if num_defined == num_decoded {
704            Ok(latlons)
705        } else {
706            Err(GribError::InvalidValueError(format!(
707                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
708            )))
709        }
710    }
711}
712
713pub struct SubMessageSection<'a> {
714    pub index: usize,
715    pub body: &'a SectionInfo,
716}
717
718impl<'a> SubMessageSection<'a> {
719    pub fn new(index: usize, body: &'a SectionInfo) -> Self {
720        Self { index, body }
721    }
722
723    pub fn template_code(&self) -> Option<TemplateInfo> {
724        self.body.get_tmpl_code()
725    }
726
727    pub fn describe(&self) -> Option<String> {
728        self.template_code().and_then(|code| code.describe())
729    }
730}
731
732#[cfg(test)]
733mod tests {
734    use std::{fs::File, io::BufReader};
735
736    use super::*;
737
738    macro_rules! sect_placeholder {
739        ($num:expr) => {{
740            SectionInfo {
741                num: $num,
742                offset: 0,
743                size: 0,
744                body: None,
745            }
746        }};
747    }
748
749    #[test]
750    fn context_from_buf_reader() {
751        let f = File::open(
752            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
753        )
754        .unwrap();
755        let f = BufReader::new(f);
756        let result = from_reader(f);
757        assert!(result.is_ok())
758    }
759
760    #[test]
761    fn context_from_bytes() {
762        let f = File::open(
763            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
764        )
765        .unwrap();
766        let mut f = BufReader::new(f);
767        let mut buf = Vec::new();
768        f.read_to_end(&mut buf).unwrap();
769        let result = from_bytes(&buf);
770        assert!(result.is_ok())
771    }
772
773    #[test]
774    fn get_tmpl_code_normal() {
775        let sect = SectionInfo {
776            num: 5,
777            offset: 8902,
778            size: 23,
779            body: Some(SectionBody::Section5(
780                ReprDefinition::from_payload(
781                    vec![0x00, 0x01, 0x50, 0x00, 0x00, 0xc8].into_boxed_slice(),
782                )
783                .unwrap(),
784            )),
785        };
786
787        assert_eq!(sect.get_tmpl_code(), Some(TemplateInfo(5, 200)));
788    }
789
790    #[test]
791    fn get_templates_normal() {
792        let sects = vec![
793            sect_placeholder!(0),
794            sect_placeholder!(1),
795            SectionInfo {
796                num: 3,
797                offset: 0,
798                size: 0,
799                body: Some(SectionBody::Section3(
800                    GridDefinition::from_payload(vec![0; 9].into_boxed_slice()).unwrap(),
801                )),
802            },
803            SectionInfo {
804                num: 4,
805                offset: 0,
806                size: 0,
807                body: Some(SectionBody::Section4(
808                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
809                )),
810            },
811            SectionInfo {
812                num: 5,
813                offset: 0,
814                size: 0,
815                body: Some(SectionBody::Section5(
816                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
817                )),
818            },
819            sect_placeholder!(6),
820            sect_placeholder!(7),
821            SectionInfo {
822                num: 3,
823                offset: 0,
824                size: 0,
825                body: Some(SectionBody::Section3(
826                    GridDefinition::from_payload(
827                        vec![0, 0, 0, 0, 0, 0, 0, 0, 1].into_boxed_slice(),
828                    )
829                    .unwrap(),
830                )),
831            },
832            SectionInfo {
833                num: 4,
834                offset: 0,
835                size: 0,
836                body: Some(SectionBody::Section4(
837                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
838                )),
839            },
840            SectionInfo {
841                num: 5,
842                offset: 0,
843                size: 0,
844                body: Some(SectionBody::Section5(
845                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
846                )),
847            },
848            sect_placeholder!(6),
849            sect_placeholder!(7),
850            sect_placeholder!(8),
851        ]
852        .into_boxed_slice();
853
854        assert_eq!(
855            get_templates(&sects),
856            vec![
857                TemplateInfo(3, 0),
858                TemplateInfo(3, 1),
859                TemplateInfo(4, 0),
860                TemplateInfo(5, 0),
861            ]
862        );
863    }
864
865    macro_rules! test_submessage_iterator {
866        ($((
867            $name:ident,
868            $xz_compressed_input:expr,
869            $nth:expr,
870            $expected_index:expr,
871            $expected_section_indices:expr,
872        ),)*) => ($(
873            #[test]
874            fn $name() -> Result<(), Box<dyn std::error::Error>> {
875                let mut buf = Vec::new();
876
877                let f = File::open($xz_compressed_input)?;
878                let f = BufReader::new(f);
879                let mut f = xz2::bufread::XzDecoder::new(f);
880                f.read_to_end(&mut buf)?;
881
882                let f = Cursor::new(buf);
883                let grib2 = crate::from_reader(f)?;
884                let mut iter = grib2.iter();
885
886                let (actual_index, message) = iter.nth($nth).ok_or_else(|| "item not available")?;
887                assert_eq!(actual_index, $expected_index);
888                let actual_section_indices = get_section_indices(message);
889                assert_eq!(actual_section_indices, $expected_section_indices);
890
891                Ok(())
892            }
893        )*);
894    }
895
896    test_submessage_iterator! {
897        (
898            item_0_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
899            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
900            0,
901            (0, 0),
902            (0, 1, None, 2, 3, 4, 5, 6, 0),
903        ),
904        (
905            item_1_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
906            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
907            1,
908            (0, 1),
909            (0, 1, None, 2, 7, 8, 9, 10, 0),
910        ),
911        (
912            item_0_from_submessage_iterator_for_multi_message_data,
913            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
914            0,
915            (0, 0),
916            (0, 1, None, 2, 3, 4, 5, 6, 7),
917        ),
918        (
919            item_1_from_submessage_iterator_for_multi_message_data,
920            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
921            1,
922            (1, 0),
923            (8, 9, None, 10, 11, 12, 13, 14, 15),
924        ),
925    }
926
927    fn get_section_indices<R>(
928        submessage: SubMessage<'_, R>,
929    ) -> (
930        usize,
931        usize,
932        Option<usize>,
933        usize,
934        usize,
935        usize,
936        usize,
937        usize,
938        usize,
939    ) {
940        (
941            submessage.0.index,
942            submessage.1.index,
943            submessage.2.map(|s| s.index),
944            submessage.3.index,
945            submessage.4.index,
946            submessage.5.index,
947            submessage.6.index,
948            submessage.7.index,
949            submessage.8.index,
950        )
951    }
952}