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 grib_template_helpers::{Dump as _, TryFromSlice as _};
9
10#[cfg(feature = "time-calculation")]
11use crate::TemporalInfo;
12use crate::{
13    GridPointIndexIterator, TemporalRawInfo,
14    codetables::{
15        CodeTable3_1, CodeTable4_0, CodeTable4_1, CodeTable4_2, CodeTable4_3, CodeTable5_0, Lookup,
16    },
17    datatypes::*,
18    def::grib2::{Section1, Section3, Section4, Section5, Section6, SectionHeader},
19    error::*,
20    grid::GridPointIterator,
21    parser::Grib2SubmessageIndexStream,
22    reader::{Grib2Read, Grib2SectionStream, SECT8_ES_SIZE, SeekableGrib2Reader},
23};
24
25#[derive(Default, Debug, Clone, PartialEq, Eq)]
26pub struct SectionInfo {
27    pub num: u8,
28    pub offset: usize,
29    pub size: usize,
30    pub body: Option<SectionBody>,
31}
32
33impl SectionInfo {
34    pub fn get_tmpl_code(&self) -> Option<TemplateInfo> {
35        let tmpl_num = self.body.as_ref()?.get_tmpl_num()?;
36        Some(TemplateInfo(self.num, tmpl_num))
37    }
38
39    pub(crate) fn new_8(offset: usize) -> Self {
40        Self {
41            num: 8,
42            offset,
43            size: SECT8_ES_SIZE,
44            body: None,
45        }
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum SectionBody {
51    Section0(Indicator),
52    Section1(Identification),
53    Section2(LocalUse),
54    Section3(GridDefinition),
55    Section4(ProdDefinition),
56    Section5(ReprDefinition),
57    Section6(BitMap),
58    Section7,
59}
60
61impl SectionBody {
62    fn get_tmpl_num(&self) -> Option<u16> {
63        match self {
64            Self::Section3(s) => Some(s.grid_tmpl_num()),
65            Self::Section4(s) => Some(s.prod_tmpl_num()),
66            Self::Section5(s) => Some(s.repr_tmpl_num()),
67            _ => None,
68        }
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
73pub struct TemplateInfo(pub u8, pub u16);
74
75impl TemplateInfo {
76    pub fn describe(&self) -> Option<String> {
77        match self.0 {
78            3 => Some(CodeTable3_1.lookup(usize::from(self.1)).to_string()),
79            4 => Some(CodeTable4_0.lookup(usize::from(self.1)).to_string()),
80            5 => Some(CodeTable5_0.lookup(usize::from(self.1)).to_string()),
81            _ => None,
82        }
83    }
84}
85
86impl Display for TemplateInfo {
87    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
88        write!(f, "{}.{}", self.0, self.1)
89    }
90}
91
92/// Reads a [`Grib2`] instance from an I/O stream of GRIB2.
93///
94/// # Examples
95///
96/// ```
97/// fn main() -> Result<(), Box<dyn std::error::Error>> {
98///     let f = std::fs::File::open(
99///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
100///     )?;
101///     let f = std::io::BufReader::new(f);
102///     let result = grib::from_reader(f);
103///
104///     assert!(result.is_ok());
105///     let grib2 = result?;
106///     assert_eq!(grib2.len(), 1);
107///     Ok(())
108/// }
109/// ```
110pub fn from_reader<SR: Read + Seek>(
111    reader: SR,
112) -> Result<Grib2<SeekableGrib2Reader<SR>>, GribError> {
113    Grib2::<SeekableGrib2Reader<SR>>::read_with_seekable(reader)
114}
115
116/// Reads a [`Grib2`] instance from bytes of GRIB2.
117///
118/// # Examples
119///
120/// You can use this method to create a reader from a slice, i.e., a borrowed
121/// sequence of bytes:
122///
123/// ```
124/// use std::io::Read;
125///
126/// fn main() -> Result<(), Box<dyn std::error::Error>> {
127///     let f = std::fs::File::open(
128///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
129///     )?;
130///     let mut f = std::io::BufReader::new(f);
131///     let mut buf = Vec::new();
132///     f.read_to_end(&mut buf).unwrap();
133///     let result = grib::from_bytes(&buf);
134///
135///     assert!(result.is_ok());
136///     let grib2 = result?;
137///     assert_eq!(grib2.len(), 1);
138///     Ok(())
139/// }
140/// ```
141///
142/// Also, you can use this method to create a reader from an owned sequence of
143/// bytes:
144///
145/// ```
146/// use std::io::Read;
147///
148/// fn main() -> Result<(), Box<dyn std::error::Error>> {
149///     let f = std::fs::File::open(
150///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
151///     )?;
152///     let mut f = std::io::BufReader::new(f);
153///     let mut buf = Vec::new();
154///     f.read_to_end(&mut buf).unwrap();
155///     let result = grib::from_bytes(buf);
156///
157///     assert!(result.is_ok());
158///     let grib2 = result?;
159///     assert_eq!(grib2.len(), 1);
160///     Ok(())
161/// }
162/// ```
163pub fn from_bytes<T>(bytes: T) -> Result<Grib2<SeekableGrib2Reader<Cursor<T>>>, GribError>
164where
165    T: AsRef<[u8]>,
166{
167    let reader = Cursor::new(bytes);
168    Grib2::<SeekableGrib2Reader<Cursor<T>>>::read_with_seekable(reader)
169}
170
171pub struct Grib2<R> {
172    reader: RefCell<R>,
173    sections: Box<[SectionInfo]>,
174    submessages: Vec<Grib2SubmessageIndex>,
175}
176
177impl<R> Grib2<R> {
178    /// Returns the length of submessages in the data.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
184    ///     let f = std::fs::File::open(
185    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
186    ///     )?;
187    ///     let f = std::io::BufReader::new(f);
188    ///     let grib2 = grib::from_reader(f)?;
189    ///
190    ///     assert_eq!(grib2.len(), 1);
191    ///     Ok(())
192    /// }
193    /// ```
194    pub fn len(&self) -> usize {
195        self.submessages.len()
196    }
197
198    /// Returns `true` if `self` has zero submessages.
199    #[inline]
200    pub fn is_empty(&self) -> bool {
201        self.len() == 0
202    }
203
204    /// Returns an iterator over submessages in the data.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
210    ///     let f = std::fs::File::open(
211    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
212    ///     )?;
213    ///     let f = std::io::BufReader::new(f);
214    ///     let grib2 = grib::from_reader(f)?;
215    ///
216    ///     let mut iter = grib2.iter();
217    ///     let first = iter.next();
218    ///     assert!(first.is_some());
219    ///
220    ///     let first = first.unwrap();
221    ///     let (message_index, _) = first;
222    ///     assert_eq!(message_index, (0, 0));
223    ///
224    ///     let second = iter.next();
225    ///     assert!(second.is_none());
226    ///     Ok(())
227    /// }
228    /// ```
229    #[inline]
230    pub fn iter(&self) -> SubmessageIterator<'_, R> {
231        self.into_iter()
232    }
233
234    /// Returns an iterator over submessages in the data.
235    ///
236    /// This is an alias to [`Grib2::iter()`].
237    pub fn submessages(&self) -> SubmessageIterator<'_, R> {
238        self.into_iter()
239    }
240
241    /// Returns an iterator over sections in the data.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
247    ///     let f = std::fs::File::open(
248    ///         "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
249    ///     )?;
250    ///     let f = std::io::BufReader::new(f);
251    ///     let grib2 = grib::from_reader(f)?;
252    ///
253    ///     let mut iter = grib2.sections();
254    ///     let first = iter.next();
255    ///     assert!(first.is_some());
256    ///
257    ///     let first = first.unwrap();
258    ///     assert_eq!(first.num, 0);
259    ///
260    ///     let tenth = iter.nth(9);
261    ///     assert!(tenth.is_none());
262    ///     Ok(())
263    /// }
264    /// ```
265    pub fn sections(&self) -> std::slice::Iter<'_, SectionInfo> {
266        self.sections.iter()
267    }
268}
269
270impl<R: Grib2Read> Grib2<R> {
271    pub fn read(r: R) -> Result<Self, GribError> {
272        let mut sect_stream = Grib2SectionStream::new(r);
273        let mut cacher = Vec::new();
274        let parser = Grib2SubmessageIndexStream::new(sect_stream.by_ref()).with_cacher(&mut cacher);
275        let submessages = parser.collect::<Result<Vec<_>, _>>()?;
276        Ok(Self {
277            reader: RefCell::new(sect_stream.into_reader()),
278            sections: cacher.into_boxed_slice(),
279            submessages,
280        })
281    }
282
283    pub fn read_with_seekable<SR: Read + Seek>(
284        r: SR,
285    ) -> Result<Grib2<SeekableGrib2Reader<SR>>, GribError> {
286        let r = SeekableGrib2Reader::new(r);
287        Grib2::<SeekableGrib2Reader<SR>>::read(r)
288    }
289
290    pub fn list_templates(&self) -> Vec<TemplateInfo> {
291        get_templates(&self.sections)
292    }
293}
294
295impl<'a, R: 'a> IntoIterator for &'a Grib2<R> {
296    type Item = (MessageIndex, SubMessage<'a, R>);
297    type IntoIter = SubmessageIterator<'a, R>;
298
299    fn into_iter(self) -> Self::IntoIter {
300        Self::IntoIter::new(self)
301    }
302}
303
304fn get_templates(sects: &[SectionInfo]) -> Vec<TemplateInfo> {
305    let uniq: HashSet<_> = sects.iter().filter_map(|s| s.get_tmpl_code()).collect();
306    let mut vec: Vec<_> = uniq.into_iter().collect();
307    vec.sort_unstable();
308    vec
309}
310
311/// An iterator over submessages in the GRIB data.
312///
313/// This `struct` is created by the [`iter`] method on [`Grib2`]. See its
314/// documentation for more.
315///
316/// [`iter`]: Grib2::iter
317#[derive(Clone)]
318pub struct SubmessageIterator<'a, R> {
319    context: &'a Grib2<R>,
320    pos: usize,
321}
322
323impl<'a, R> SubmessageIterator<'a, R> {
324    fn new(context: &'a Grib2<R>) -> Self {
325        Self { context, pos: 0 }
326    }
327
328    fn new_submessage_section(&self, index: usize) -> Option<SubMessageSection<'a>> {
329        Some(SubMessageSection::new(
330            index,
331            self.context.sections.get(index)?,
332        ))
333    }
334}
335
336impl<'a, R> Iterator for SubmessageIterator<'a, R> {
337    type Item = (MessageIndex, SubMessage<'a, R>);
338
339    fn next(&mut self) -> Option<Self::Item> {
340        let submessage_index = self.context.submessages.get(self.pos)?;
341        self.pos += 1;
342
343        Some((
344            submessage_index.message_index(),
345            SubMessage(
346                self.new_submessage_section(submessage_index.0)?,
347                self.new_submessage_section(submessage_index.1)?,
348                submessage_index
349                    .2
350                    .and_then(|i| self.new_submessage_section(i)),
351                self.new_submessage_section(submessage_index.3)?,
352                self.new_submessage_section(submessage_index.4)?,
353                self.new_submessage_section(submessage_index.5)?,
354                self.new_submessage_section(submessage_index.6)?,
355                self.new_submessage_section(submessage_index.7)?,
356                self.new_submessage_section(submessage_index.8)?,
357                self.context.reader.borrow_mut(),
358            ),
359        ))
360    }
361
362    fn size_hint(&self) -> (usize, Option<usize>) {
363        let size = self.context.submessages.len() - self.pos;
364        (size, Some(size))
365    }
366
367    fn nth(&mut self, n: usize) -> Option<Self::Item> {
368        self.pos = n;
369        self.next()
370    }
371}
372
373impl<'a, R> IntoIterator for &'a SubmessageIterator<'a, R> {
374    type Item = (MessageIndex, SubMessage<'a, R>);
375    type IntoIter = SubmessageIterator<'a, R>;
376
377    fn into_iter(self) -> Self::IntoIter {
378        SubmessageIterator {
379            context: self.context,
380            pos: self.pos,
381        }
382    }
383}
384
385pub struct SubMessage<'a, R>(
386    pub SubMessageSection<'a>,
387    pub SubMessageSection<'a>,
388    pub Option<SubMessageSection<'a>>,
389    pub SubMessageSection<'a>,
390    pub SubMessageSection<'a>,
391    pub SubMessageSection<'a>,
392    pub SubMessageSection<'a>,
393    pub SubMessageSection<'a>,
394    pub SubMessageSection<'a>,
395    pub(crate) RefMut<'a, R>,
396);
397
398impl<R> SubMessage<'_, R> {
399    /// Returns the product's parameter.
400    ///
401    /// In the context of GRIB products, parameters refer to weather elements
402    /// such as air temperature, air pressure, and humidity, and other physical
403    /// quantities.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// use std::{
409    ///     fs::File,
410    ///     io::{BufReader, Read},
411    /// };
412    ///
413    /// use grib::codetables::NCEP;
414    ///
415    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
416    ///     let mut buf = Vec::new();
417    ///
418    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
419    ///     let f = BufReader::new(f);
420    ///     let mut f = xz2::bufread::XzDecoder::new(f);
421    ///     f.read_to_end(&mut buf)?;
422    ///
423    ///     let f = std::io::Cursor::new(buf);
424    ///     let grib2 = grib::from_reader(f)?;
425    ///
426    ///     let mut iter = grib2.iter();
427    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
428    ///
429    ///     let param = message.parameter();
430    ///     assert_eq!(
431    ///         param,
432    ///         Some(grib::Parameter {
433    ///             discipline: 0,
434    ///             centre: 7,
435    ///             master_ver: 2,
436    ///             local_ver: 1,
437    ///             category: 3,
438    ///             num: 1
439    ///         })
440    ///     );
441    ///     let param = param.unwrap();
442    ///     assert_eq!(
443    ///         param.description(),
444    ///         Some("Pressure reduced to MSL".to_owned())
445    ///     );
446    ///     assert!(param.is_identical_to(NCEP::PRMSL));
447    ///     Ok(())
448    /// }
449    /// ```
450    pub fn parameter(&self) -> Option<Parameter> {
451        let discipline = self.indicator().discipline;
452        let ident = self.identification();
453        let centre = ident.centre_id();
454        let master_ver = ident.master_table_version();
455        let local_ver = ident.local_table_version();
456        let prod_def = self.prod_def();
457        let category = prod_def.parameter_category()?;
458        let num = prod_def.parameter_number()?;
459        Some(Parameter {
460            discipline,
461            centre,
462            master_ver,
463            local_ver,
464            category,
465            num,
466        })
467    }
468
469    pub fn indicator(&self) -> &Indicator {
470        // panics should not happen if data is correct
471        match self.0.body.body.as_ref().unwrap() {
472            SectionBody::Section0(data) => data,
473            _ => panic!("something unexpected happened"),
474        }
475    }
476
477    pub fn identification(&self) -> &Identification {
478        // panics should not happen if data is correct
479        match self.1.body.body.as_ref().unwrap() {
480            SectionBody::Section1(data) => data,
481            _ => panic!("something unexpected happened"),
482        }
483    }
484
485    pub fn grid_def(&self) -> &GridDefinition {
486        // panics should not happen if data is correct
487        match self.3.body.body.as_ref().unwrap() {
488            SectionBody::Section3(data) => data,
489            _ => panic!("something unexpected happened"),
490        }
491    }
492
493    pub fn prod_def(&self) -> &ProdDefinition {
494        // panics should not happen if data is correct
495        match self.4.body.body.as_ref().unwrap() {
496            SectionBody::Section4(data) => data,
497            _ => panic!("something unexpected happened"),
498        }
499    }
500
501    pub fn repr_def(&self) -> &ReprDefinition {
502        // panics should not happen if data is correct
503        match self.5.body.body.as_ref().unwrap() {
504            SectionBody::Section5(data) => data,
505            _ => panic!("something unexpected happened"),
506        }
507    }
508
509    pub fn describe(&self) -> String {
510        let category = self.prod_def().parameter_category();
511        let forecast_time = self
512            .prod_def()
513            .forecast_time()
514            .map(|ft| ft.describe())
515            .unwrap_or((String::new(), String::new()));
516        let fixed_surfaces_info = self
517            .prod_def()
518            .fixed_surfaces()
519            .map(|(first, second)| (first.describe(), second.describe()))
520            .map(|(first, second)| (first.0, first.1, first.2, second.0, second.1, second.2))
521            .unwrap_or((
522                String::new(),
523                String::new(),
524                String::new(),
525                String::new(),
526                String::new(),
527                String::new(),
528            ));
529
530        format!(
531            "\
532Grid:                                   {}
533  Number of points:                     {}
534Product:                                {}
535  Parameter Category:                   {}
536  Parameter:                            {}
537  Generating Proceess:                  {}
538  Forecast Time:                        {}
539  Forecast Time Unit:                   {}
540  1st Fixed Surface Type:               {}
541  1st Scale Factor:                     {}
542  1st Scaled Value:                     {}
543  2nd Fixed Surface Type:               {}
544  2nd Scale Factor:                     {}
545  2nd Scaled Value:                     {}
546Data Representation:                    {}
547  Number of represented values:         {}
548",
549            self.3.describe().unwrap_or_default(),
550            self.grid_def().num_points(),
551            self.4.describe().unwrap_or_default(),
552            category
553                .map(|v| CodeTable4_1::new(self.indicator().discipline)
554                    .lookup(usize::from(v))
555                    .to_string())
556                .unwrap_or_default(),
557            self.prod_def()
558                .parameter_number()
559                .zip(category)
560                .map(|(n, c)| CodeTable4_2::new(self.indicator().discipline, c)
561                    .lookup(usize::from(n))
562                    .to_string())
563                .unwrap_or_default(),
564            self.prod_def()
565                .generating_process()
566                .map(|v| CodeTable4_3.lookup(usize::from(v)).to_string())
567                .unwrap_or_default(),
568            forecast_time.1,
569            forecast_time.0,
570            fixed_surfaces_info.0,
571            fixed_surfaces_info.1,
572            fixed_surfaces_info.2,
573            fixed_surfaces_info.3,
574            fixed_surfaces_info.4,
575            fixed_surfaces_info.5,
576            self.5.describe().unwrap_or_default(),
577            self.repr_def().num_points(),
578        )
579    }
580
581    /// Provides access to the parameters in Section 1.
582    ///
583    /// # Examples
584    /// ```
585    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
586    ///     let f = std::fs::File::open(
587    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
588    ///     )?;
589    ///     let f = std::io::BufReader::new(f);
590    ///     let grib2 = grib::from_reader(f)?;
591    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
592    ///
593    ///     let actual = first_submessage.section1();
594    ///     let expected = Ok(grib::def::grib2::Section1 {
595    ///         header: grib::def::grib2::SectionHeader {
596    ///             len: 21,
597    ///             sect_num: 1,
598    ///         },
599    ///         payload: grib::def::grib2::Section1Payload {
600    ///             centre_id: 34,
601    ///             subcentre_id: 0,
602    ///             master_table_version: 5,
603    ///             local_table_version: 1,
604    ///             ref_time_significance: 0,
605    ///             ref_time: grib::def::grib2::RefTime {
606    ///                 year: 2016,
607    ///                 month: 8,
608    ///                 day: 22,
609    ///                 hour: 2,
610    ///                 minute: 0,
611    ///                 second: 0,
612    ///             },
613    ///             prod_status: 0,
614    ///             data_type: 2,
615    ///             optional: None,
616    ///         },
617    ///     });
618    ///     assert_eq!(actual, expected);
619    ///
620    ///     Ok(())
621    /// }
622    /// ```
623    pub fn section1(&self) -> Result<Section1, GribError> {
624        let Identification { payload } = self.identification();
625        let mut pos = 0;
626        let payload = crate::def::grib2::Section1Payload::try_from_slice(payload, &mut pos)
627            .map_err(|e| GribError::Unknown(e.to_owned()))?;
628
629        let SectionInfo { num, size, .. } = self.1.body;
630        Ok(Section1 {
631            header: SectionHeader {
632                len: *size as u32,
633                sect_num: *num,
634            },
635            payload,
636        })
637    }
638
639    /// Provides access to the parameters in Section 3.
640    ///
641    /// # Examples
642    /// ```
643    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
644    ///     let f = std::fs::File::open(
645    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
646    ///     )?;
647    ///     let f = std::io::BufReader::new(f);
648    ///     let grib2 = grib::from_reader(f)?;
649    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
650    ///
651    ///     let actual = first_submessage.section3();
652    ///     let expected = Ok(grib::def::grib2::Section3 {
653    ///         header: grib::def::grib2::SectionHeader {
654    ///             len: 72,
655    ///             sect_num: 3,
656    ///         },
657    ///         payload: grib::def::grib2::Section3Payload {
658    ///             grid_def_source: 0,
659    ///             num_points: 86016,
660    ///             num_point_list_octets: 0,
661    ///             point_list_interpretation: 0,
662    ///             template_num: 0,
663    ///         },
664    ///     });
665    ///     assert_eq!(actual, expected);
666    ///
667    ///     Ok(())
668    /// }
669    /// ```
670    pub fn section3(&self) -> Result<Section3, GribError> {
671        let GridDefinition { payload } = self.grid_def();
672        let mut pos = 0;
673        let payload = crate::def::grib2::Section3Payload::try_from_slice(payload, &mut pos)
674            .map_err(|e| GribError::Unknown(e.to_owned()))?;
675
676        let SectionInfo { num, size, .. } = self.3.body;
677        Ok(Section3 {
678            header: SectionHeader {
679                len: *size as u32,
680                sect_num: *num,
681            },
682            payload,
683        })
684    }
685
686    /// Provides access to the parameters in Section 4.
687    ///
688    /// # Examples
689    /// ```
690    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
691    ///     let f = std::fs::File::open(
692    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
693    ///     )?;
694    ///     let f = std::io::BufReader::new(f);
695    ///     let grib2 = grib::from_reader(f)?;
696    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
697    ///
698    ///     let actual = first_submessage.section4();
699    ///     let expected = Ok(grib::def::grib2::Section4 {
700    ///         header: grib::def::grib2::SectionHeader {
701    ///             len: 34,
702    ///             sect_num: 4,
703    ///         },
704    ///         payload: grib::def::grib2::Section4Payload {
705    ///             num_coord_values: 0,
706    ///             template_num: 0,
707    ///         },
708    ///     });
709    ///     assert_eq!(actual, expected);
710    ///
711    ///     Ok(())
712    /// }
713    /// ```
714    pub fn section4(&self) -> Result<Section4, GribError> {
715        let ProdDefinition { payload }: &ProdDefinition = self.prod_def();
716        let mut pos = 0;
717        let payload = crate::def::grib2::Section4Payload::try_from_slice(payload, &mut pos)
718            .map_err(|e| GribError::Unknown(e.to_owned()))?;
719
720        let SectionInfo { num, size, .. } = self.4.body;
721        Ok(Section4 {
722            header: SectionHeader {
723                len: *size as u32,
724                sect_num: *num,
725            },
726            payload,
727        })
728    }
729
730    /// Provides access to the parameters in Section 5.
731    ///
732    /// # Examples
733    /// ```
734    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
735    ///     let f = std::fs::File::open(
736    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
737    ///     )?;
738    ///     let f = std::io::BufReader::new(f);
739    ///     let grib2 = grib::from_reader(f)?;
740    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
741    ///
742    ///     let actual = first_submessage.section5();
743    ///     let expected = Ok(grib::def::grib2::Section5 {
744    ///         header: grib::def::grib2::SectionHeader {
745    ///             len: 23,
746    ///             sect_num: 5,
747    ///         },
748    ///         payload: grib::def::grib2::Section5Payload {
749    ///             num_encoded_points: 86016,
750    ///             template_num: 200,
751    ///             template: grib::def::grib2::DataRepresentationTemplate::_5_200(
752    ///                 grib::def::grib2::template::Template5_200 {
753    ///                     num_bits: 8,
754    ///                     max_val: 3,
755    ///                     max_level: 3,
756    ///                     dec: 0,
757    ///                     level_vals: vec![1, 2, 3],
758    ///                 },
759    ///             ),
760    ///         },
761    ///     });
762    ///     assert_eq!(actual, expected);
763    ///
764    ///     Ok(())
765    /// }
766    /// ```
767    pub fn section5(&self) -> Result<Section5, GribError> {
768        let ReprDefinition { payload } = self.repr_def();
769        let mut pos = 0;
770        let payload = crate::def::grib2::Section5Payload::try_from_slice(payload, &mut pos)
771            .map_err(|e| GribError::Unknown(e.to_owned()))?;
772
773        let SectionInfo { num, size, .. } = self.5.body;
774        Ok(Section5 {
775            header: SectionHeader {
776                len: *size as u32,
777                sect_num: *num,
778            },
779            payload,
780        })
781    }
782
783    /// Provides access to the parameters in Section 6.
784    ///
785    /// # Examples
786    /// ```
787    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
788    ///     let f = std::fs::File::open(
789    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
790    ///     )?;
791    ///     let f = std::io::BufReader::new(f);
792    ///     let grib2 = grib::from_reader(f)?;
793    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
794    ///
795    ///     let actual = first_submessage.section6();
796    ///     let expected = Ok(grib::def::grib2::Section6 {
797    ///         header: grib::def::grib2::SectionHeader {
798    ///             len: 6,
799    ///             sect_num: 6,
800    ///         },
801    ///         payload: grib::def::grib2::Section6Payload {
802    ///             bitmap_indicator: 255,
803    ///         },
804    ///     });
805    ///     assert_eq!(actual, expected);
806    ///
807    ///     Ok(())
808    /// }
809    /// ```
810    pub fn section6(&self) -> Result<Section6, GribError> {
811        // panics should not happen if data is correct
812        let BitMap { bitmap_indicator } = match self.6.body.body.as_ref().unwrap() {
813            SectionBody::Section6(data) => data,
814            _ => panic!("something unexpected happened"),
815        };
816        let payload = crate::def::grib2::Section6Payload {
817            bitmap_indicator: *bitmap_indicator,
818        };
819
820        let SectionInfo { num, size, .. } = self.6.body;
821        Ok(Section6 {
822            header: SectionHeader {
823                len: *size as u32,
824                sect_num: *num,
825            },
826            payload,
827        })
828    }
829
830    /// Dumps the GRIB2 submessage.
831    ///
832    /// # Examples
833    /// ```
834    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
835    ///     let f = std::fs::File::open(
836    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
837    ///     )?;
838    ///     let f = std::io::BufReader::new(f);
839    ///     let grib2 = grib::from_reader(f)?;
840    ///     let (_index, first_submessage) = grib2.iter().next().unwrap();
841    ///
842    ///     let mut buf = std::io::Cursor::new(Vec::with_capacity(1024));
843    ///     first_submessage.dump(&mut buf)?;
844    ///     let expected = "\
845    /// ##  SUBMESSAGE (total_length = 10321)
846    /// ###  SECTION 0: INDICATOR SECTION (length = 16)
847    /// ###  SECTION 1: IDENTIFICATION SECTION (length = 21)
848    /// 1-4       header.len = 21  // Length of section in octets (nn).
849    /// 5         header.sect_num = 1  // Number of section.
850    /// 6-7       payload.centre_id = 34  // Identification of originating/generating centre (see Common Code table C-11).
851    /// 8-9       payload.subcentre_id = 0  // Identification of originating/generating subcentre (allocated by originating/generating centre).
852    /// 10        payload.master_table_version = 5  // GRIB master table version number (see Common Code table C-0 and Note 1).
853    /// 11        payload.local_table_version = 1  // Version number of GRIB Local tables used to augment Master tables (see Code table 1.1 and Note 2).
854    /// 12        payload.ref_time_significance = 0  // Significance of reference time (see Code table 1.2).
855    /// 13-14     payload.ref_time.year = 2016  // Year (4 digits).
856    /// 15        payload.ref_time.month = 8  // Month.
857    /// 16        payload.ref_time.day = 22  // Day.
858    /// 17        payload.ref_time.hour = 2  // Hour.
859    /// 18        payload.ref_time.minute = 0  // Minute.
860    /// 19        payload.ref_time.second = 0  // Second.
861    /// 20        payload.prod_status = 0  // Production status of processed data in this GRIB message (see Code table 1.3).
862    /// 21        payload.data_type = 2  // Type of processed data in this GRIB message (see Code table 1.4).
863    /// ###  SECTION 3: GRID DEFINITION SECTION (length = 72)
864    /// 1-4       header.len = 72  // Length of section in octets (nn).
865    /// 5         header.sect_num = 3  // Number of section.
866    /// 6         payload.grid_def_source = 0  // Source of grid definition (see Code table 3.0 and Note 1).
867    /// 7-10      payload.num_points = 86016  // Number of data points.
868    /// 11        payload.num_point_list_octets = 0  // Number of octets for optional list of numbers (see Note 2).
869    /// 12        payload.point_list_interpretation = 0  // Interpretation of list of numbers (see Code table 3.11).
870    /// 13-14     payload.template_num = 0  // Grid definition template number (= N) (see Code table 3.1).
871    /// ###  SECTION 4: PRODUCT DEFINITION SECTION (length = 34)
872    /// 1-4       header.len = 34  // Length of section in octets (nn).
873    /// 5         header.sect_num = 4  // Number of section.
874    /// 6-7       payload.num_coord_values = 0  // Number of coordinate values after template or number of information according to 3D vertical coordinate GRIB2 message (see Notes 1 and 5).
875    /// 8-9       payload.template_num = 0  // Product definition template number (see Code table 4.0).
876    /// ###  SECTION 5: DATA REPRESENTATION SECTION (length = 23)
877    /// 1-4       header.len = 23  // Length of section in octets (nn).
878    /// 5         header.sect_num = 5  // Number of section.
879    /// 6-9       payload.num_encoded_points = 86016  // Number of data points where one or more values are specified in Section 7 when a bit map is present, total number of data points when a bit map is absent.
880    /// 10-11     payload.template_num = 200  // Data representation template number (see Code table 5.0).
881    /// 12        payload.template.num_bits = 8  // Number of bits used for each packed value in the run length packing with level value.
882    /// 13-14     payload.template.max_val = 3  // MV - maximum value within the levels that are used in the packing.
883    /// 15-16     payload.template.max_level = 3  // MVL - maximum value of level (predefined).
884    /// 17        payload.template.dec = 0  // Decimal scale factor of representative value of each level.
885    /// 18-23     payload.template.level_vals = [1, 2, 3]  // List of MVL scaled representative values of each level from lv=1 to MVL.
886    /// ###  SECTION 6: BIT-MAP SECTION (length = 6)
887    /// 1-4       header.len = 6  // Length of section in octets (nn).
888    /// 5         header.sect_num = 6  // Number of section.
889    /// 6         payload.bitmap_indicator = 255  // Bit-map indicator (see Code table 6.0 and the Note).
890    /// ###  SECTION 7: DATA SECTION (length = 1391)
891    /// ###  SECTION 8: END SECTION (length = 4)
892    /// ";
893    ///     assert_eq!(String::from_utf8_lossy(buf.get_ref()), expected);
894    ///
895    ///     Ok(())
896    /// }
897    /// ```
898    pub fn dump<W: std::io::Write>(&self, writer: &mut W) -> Result<(), GribError> {
899        // TODO: return `Result<(), std::io::Error>` instead.
900
901        let write_heading =
902            |writer: &mut W, sect: &SectionInfo, sect_name: &str| -> Result<(), std::io::Error> {
903                let SectionInfo { num, size, .. } = sect;
904                let sect_name = sect_name.to_ascii_uppercase();
905                writeln!(writer, "##  SECTION {num}: {sect_name} (length = {size})")
906            };
907
908        macro_rules! write_section {
909            ($sect:expr) => {{
910                let mut pos = 1;
911                match $sect {
912                    Ok(s) => s.dump(None, &mut pos, writer)?,
913                    Err(e) => writeln!(writer, "error: {}", e)?,
914                }
915            }};
916        }
917
918        let total_length = self.indicator().total_length;
919        writeln!(writer, "#  SUBMESSAGE (total_length = {total_length})")?;
920        write_heading(writer, self.0.body, "indicator section")?;
921        write_heading(writer, self.1.body, "identification section")?;
922        write_section!(self.section1());
923        if let Some(sect) = &self.2 {
924            write_heading(writer, sect.body, "local use section")?;
925        }
926        write_heading(writer, self.3.body, "grid definition section")?;
927        write_section!(self.section3());
928        write_heading(writer, self.4.body, "product definition section")?;
929        write_section!(self.section4());
930        write_heading(writer, self.5.body, "data representation section")?;
931        write_section!(self.section5());
932        write_heading(writer, self.6.body, "bit-map section")?;
933        write_section!(self.section6());
934        write_heading(writer, self.7.body, "data section")?;
935
936        // Since `self.8.body` might be dummy, we don't use that Section 8 data.
937        writeln!(writer, "##  SECTION 8: END SECTION (length = 4)")?;
938
939        Ok(())
940    }
941
942    /// Returns time-related raw information associated with the submessage.
943    ///
944    /// # Examples
945    ///
946    /// ```
947    /// use std::{
948    ///     fs::File,
949    ///     io::{BufReader, Read},
950    /// };
951    ///
952    /// use grib::{
953    ///     Code, ForecastTime, TemporalRawInfo, UtcDateTime,
954    ///     codetables::grib2::{Table1_2, Table4_4},
955    /// };
956    ///
957    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
958    ///     let f = File::open(
959    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
960    ///     )?;
961    ///     let f = BufReader::new(f);
962    ///     let grib2 = grib::from_reader(f)?;
963    ///
964    ///     let mut iter = grib2.iter();
965    ///
966    ///     {
967    ///         let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
968    ///         let actual = message.temporal_raw_info();
969    ///         let expected = TemporalRawInfo {
970    ///             ref_time_significance: Code::Name(Table1_2::Analysis),
971    ///             ref_time_unchecked: UtcDateTime::new(2016, 8, 22, 2, 0, 0),
972    ///             forecast_time_diff: Some(ForecastTime {
973    ///                 unit: Code::Name(Table4_4::Minute),
974    ///                 value: 0,
975    ///             }),
976    ///         };
977    ///         assert_eq!(actual, expected);
978    ///     }
979    ///
980    ///     {
981    ///         let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
982    ///         let actual = message.temporal_raw_info();
983    ///         let expected = TemporalRawInfo {
984    ///             ref_time_significance: Code::Name(Table1_2::Analysis),
985    ///             ref_time_unchecked: UtcDateTime::new(2016, 8, 22, 2, 0, 0),
986    ///             forecast_time_diff: Some(ForecastTime {
987    ///                 unit: Code::Name(Table4_4::Minute),
988    ///                 value: 10,
989    ///             }),
990    ///         };
991    ///         assert_eq!(actual, expected);
992    ///     }
993    ///
994    ///     Ok(())
995    /// }
996    /// ```
997    pub fn temporal_raw_info(&self) -> TemporalRawInfo {
998        let ref_time_significance = self.identification().ref_time_significance();
999        let ref_time_unchecked = self.identification().ref_time_unchecked();
1000        let forecast_time = self.prod_def().forecast_time();
1001        TemporalRawInfo::new(ref_time_significance, ref_time_unchecked, forecast_time)
1002    }
1003
1004    #[cfg(feature = "time-calculation")]
1005    #[cfg_attr(docsrs, doc(cfg(feature = "time-calculation")))]
1006    /// Returns time-related calculated information associated with the
1007    /// submessage.
1008    ///
1009    /// # Examples
1010    ///
1011    /// ```
1012    /// use std::{
1013    ///     fs::File,
1014    ///     io::{BufReader, Read},
1015    /// };
1016    ///
1017    /// use chrono::{TimeZone, Utc};
1018    /// use grib::{
1019    ///     Code, ForecastTime, TemporalInfo, UtcDateTime,
1020    ///     codetables::grib2::{Table1_2, Table4_4},
1021    /// };
1022    ///
1023    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1024    ///     let f = File::open(
1025    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
1026    ///     )?;
1027    ///     let f = BufReader::new(f);
1028    ///     let grib2 = grib::from_reader(f)?;
1029    ///
1030    ///     let mut iter = grib2.iter();
1031    ///
1032    ///     {
1033    ///         let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1034    ///         let actual = message.temporal_info();
1035    ///         let expected = TemporalInfo {
1036    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1037    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1038    ///         };
1039    ///         assert_eq!(actual, expected);
1040    ///     }
1041    ///
1042    ///     {
1043    ///         let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
1044    ///         let actual = message.temporal_info();
1045    ///         let expected = TemporalInfo {
1046    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1047    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 10, 0).unwrap()),
1048    ///         };
1049    ///         assert_eq!(actual, expected);
1050    ///     }
1051    ///
1052    ///     Ok(())
1053    /// }
1054    /// ```
1055    pub fn temporal_info(&self) -> TemporalInfo {
1056        let raw_info = self.temporal_raw_info();
1057        TemporalInfo::from(&raw_info)
1058    }
1059
1060    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
1061    /// the i and j directions.
1062    ///
1063    /// # Examples
1064    ///
1065    /// ```
1066    /// use std::{
1067    ///     fs::File,
1068    ///     io::{BufReader, Read},
1069    /// };
1070    ///
1071    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1072    ///     let mut buf = Vec::new();
1073    ///
1074    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1075    ///     let f = BufReader::new(f);
1076    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1077    ///     f.read_to_end(&mut buf)?;
1078    ///
1079    ///     let f = std::io::Cursor::new(buf);
1080    ///     let grib2 = grib::from_reader(f)?;
1081    ///
1082    ///     let mut iter = grib2.iter();
1083    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1084    ///
1085    ///     let shape = message.grid_shape()?;
1086    ///     assert_eq!(shape, (1440, 721));
1087    ///     Ok(())
1088    /// }
1089    /// ```
1090    pub fn grid_shape(&self) -> Result<(usize, usize), GribError> {
1091        let grid_def = self.grid_def();
1092        let shape = GridDefinitionTemplateValues::try_from(grid_def)?.grid_shape();
1093        Ok(shape)
1094    }
1095
1096    /// Computes and returns an iterator over `(i, j)` of grid points.
1097    ///
1098    /// The order of items is the same as the order of the grid point values,
1099    /// defined by the scanning mode ([`ScanningMode`](`crate::ScanningMode`))
1100    /// in the data.
1101    ///
1102    /// This iterator allows users to perform their own coordinate calculations
1103    /// for unsupported grid systems and map the results to grid point values.
1104    ///
1105    /// # Examples
1106    ///
1107    /// ```
1108    /// use std::{
1109    ///     fs::File,
1110    ///     io::{BufReader, Read},
1111    /// };
1112    ///
1113    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1114    ///     let mut buf = Vec::new();
1115    ///
1116    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1117    ///     let f = BufReader::new(f);
1118    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1119    ///     f.read_to_end(&mut buf)?;
1120    ///
1121    ///     let f = std::io::Cursor::new(buf);
1122    ///     let grib2 = grib::from_reader(f)?;
1123    ///
1124    ///     let mut iter = grib2.iter();
1125    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1126    ///
1127    ///     let mut latlons = message.ij()?;
1128    ///     assert_eq!(latlons.next(), Some((0, 0)));
1129    ///     assert_eq!(latlons.next(), Some((1, 0)));
1130    ///     Ok(())
1131    /// }
1132    /// ```
1133    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
1134        let grid_def = self.grid_def();
1135        let num_defined = grid_def.num_points() as usize;
1136        let ij = GridDefinitionTemplateValues::try_from(grid_def)?.ij()?;
1137        let (num_decoded, _) = ij.size_hint();
1138        if num_defined == num_decoded {
1139            Ok(ij)
1140        } else {
1141            Err(GribError::InvalidValueError(format!(
1142                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1143            )))
1144        }
1145    }
1146
1147    /// Computes and returns an iterator over latitudes and longitudes of grid
1148    /// points.
1149    ///
1150    /// The order of lat/lon data of grid points is the same as the order of the
1151    /// grid point values, defined by the scanning mode
1152    /// ([`ScanningMode`](`crate::ScanningMode`)) in the data.
1153    ///
1154    /// # Examples
1155    ///
1156    /// ```
1157    /// use std::{
1158    ///     fs::File,
1159    ///     io::{BufReader, Read},
1160    /// };
1161    ///
1162    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1163    ///     let mut buf = Vec::new();
1164    ///
1165    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1166    ///     let f = BufReader::new(f);
1167    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1168    ///     f.read_to_end(&mut buf)?;
1169    ///
1170    ///     let f = std::io::Cursor::new(buf);
1171    ///     let grib2 = grib::from_reader(f)?;
1172    ///
1173    ///     let mut iter = grib2.iter();
1174    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1175    ///
1176    ///     let mut latlons = message.latlons()?;
1177    ///     assert_eq!(latlons.next(), Some((90.0, 0.0)));
1178    ///     assert_eq!(latlons.next(), Some((90.0, 0.25000003)));
1179    ///     Ok(())
1180    /// }
1181    /// ```
1182    pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
1183        let grid_def = self.grid_def();
1184        let num_defined = grid_def.num_points() as usize;
1185        let latlons = GridDefinitionTemplateValues::try_from(grid_def)?.latlons()?;
1186        let (num_decoded, _) = latlons.size_hint();
1187        if num_defined == num_decoded {
1188            Ok(latlons)
1189        } else {
1190            Err(GribError::InvalidValueError(format!(
1191                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1192            )))
1193        }
1194    }
1195}
1196
1197pub struct SubMessageSection<'a> {
1198    pub index: usize,
1199    pub body: &'a SectionInfo,
1200}
1201
1202impl<'a> SubMessageSection<'a> {
1203    pub fn new(index: usize, body: &'a SectionInfo) -> Self {
1204        Self { index, body }
1205    }
1206
1207    pub fn template_code(&self) -> Option<TemplateInfo> {
1208        self.body.get_tmpl_code()
1209    }
1210
1211    pub fn describe(&self) -> Option<String> {
1212        self.template_code().and_then(|code| code.describe())
1213    }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218    use std::{fs::File, io::BufReader};
1219
1220    use super::*;
1221
1222    macro_rules! sect_placeholder {
1223        ($num:expr) => {{
1224            SectionInfo {
1225                num: $num,
1226                offset: 0,
1227                size: 0,
1228                body: None,
1229            }
1230        }};
1231    }
1232
1233    #[test]
1234    fn context_from_buf_reader() {
1235        let f = File::open(
1236            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1237        )
1238        .unwrap();
1239        let f = BufReader::new(f);
1240        let result = from_reader(f);
1241        assert!(result.is_ok())
1242    }
1243
1244    #[test]
1245    fn context_from_bytes() {
1246        let f = File::open(
1247            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1248        )
1249        .unwrap();
1250        let mut f = BufReader::new(f);
1251        let mut buf = Vec::new();
1252        f.read_to_end(&mut buf).unwrap();
1253        let result = from_bytes(&buf);
1254        assert!(result.is_ok())
1255    }
1256
1257    #[test]
1258    fn get_tmpl_code_normal() {
1259        let sect = SectionInfo {
1260            num: 5,
1261            offset: 8902,
1262            size: 23,
1263            body: Some(SectionBody::Section5(
1264                ReprDefinition::from_payload(
1265                    vec![0x00, 0x01, 0x50, 0x00, 0x00, 0xc8].into_boxed_slice(),
1266                )
1267                .unwrap(),
1268            )),
1269        };
1270
1271        assert_eq!(sect.get_tmpl_code(), Some(TemplateInfo(5, 200)));
1272    }
1273
1274    #[test]
1275    fn get_templates_normal() {
1276        let sects = vec![
1277            sect_placeholder!(0),
1278            sect_placeholder!(1),
1279            SectionInfo {
1280                num: 3,
1281                offset: 0,
1282                size: 0,
1283                body: Some(SectionBody::Section3(
1284                    GridDefinition::from_payload(vec![0; 9].into_boxed_slice()).unwrap(),
1285                )),
1286            },
1287            SectionInfo {
1288                num: 4,
1289                offset: 0,
1290                size: 0,
1291                body: Some(SectionBody::Section4(
1292                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1293                )),
1294            },
1295            SectionInfo {
1296                num: 5,
1297                offset: 0,
1298                size: 0,
1299                body: Some(SectionBody::Section5(
1300                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1301                )),
1302            },
1303            sect_placeholder!(6),
1304            sect_placeholder!(7),
1305            SectionInfo {
1306                num: 3,
1307                offset: 0,
1308                size: 0,
1309                body: Some(SectionBody::Section3(
1310                    GridDefinition::from_payload(
1311                        vec![0, 0, 0, 0, 0, 0, 0, 0, 1].into_boxed_slice(),
1312                    )
1313                    .unwrap(),
1314                )),
1315            },
1316            SectionInfo {
1317                num: 4,
1318                offset: 0,
1319                size: 0,
1320                body: Some(SectionBody::Section4(
1321                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1322                )),
1323            },
1324            SectionInfo {
1325                num: 5,
1326                offset: 0,
1327                size: 0,
1328                body: Some(SectionBody::Section5(
1329                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1330                )),
1331            },
1332            sect_placeholder!(6),
1333            sect_placeholder!(7),
1334            sect_placeholder!(8),
1335        ]
1336        .into_boxed_slice();
1337
1338        assert_eq!(
1339            get_templates(&sects),
1340            vec![
1341                TemplateInfo(3, 0),
1342                TemplateInfo(3, 1),
1343                TemplateInfo(4, 0),
1344                TemplateInfo(5, 0),
1345            ]
1346        );
1347    }
1348
1349    macro_rules! test_submessage_iterator {
1350        ($((
1351            $name:ident,
1352            $xz_compressed_input:expr,
1353            $nth:expr,
1354            $expected_index:expr,
1355            $expected_section_indices:expr,
1356        ),)*) => ($(
1357            #[test]
1358            fn $name() -> Result<(), Box<dyn std::error::Error>> {
1359                let mut buf = Vec::new();
1360
1361                let f = File::open($xz_compressed_input)?;
1362                let f = BufReader::new(f);
1363                let mut f = xz2::bufread::XzDecoder::new(f);
1364                f.read_to_end(&mut buf)?;
1365
1366                let f = Cursor::new(buf);
1367                let grib2 = crate::from_reader(f)?;
1368                let mut iter = grib2.iter();
1369
1370                let (actual_index, message) = iter.nth($nth).ok_or_else(|| "item not available")?;
1371                assert_eq!(actual_index, $expected_index);
1372                let actual_section_indices = get_section_indices(message);
1373                assert_eq!(actual_section_indices, $expected_section_indices);
1374
1375                Ok(())
1376            }
1377        )*);
1378    }
1379
1380    test_submessage_iterator! {
1381        (
1382            item_0_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1383            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1384            0,
1385            (0, 0),
1386            (0, 1, None, 2, 3, 4, 5, 6, 0),
1387        ),
1388        (
1389            item_1_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1390            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1391            1,
1392            (0, 1),
1393            (0, 1, None, 2, 7, 8, 9, 10, 0),
1394        ),
1395        (
1396            item_0_from_submessage_iterator_for_multi_message_data,
1397            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1398            0,
1399            (0, 0),
1400            (0, 1, None, 2, 3, 4, 5, 6, 7),
1401        ),
1402        (
1403            item_1_from_submessage_iterator_for_multi_message_data,
1404            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1405            1,
1406            (1, 0),
1407            (8, 9, None, 10, 11, 12, 13, 14, 15),
1408        ),
1409    }
1410
1411    fn get_section_indices<R>(
1412        submessage: SubMessage<'_, R>,
1413    ) -> (
1414        usize,
1415        usize,
1416        Option<usize>,
1417        usize,
1418        usize,
1419        usize,
1420        usize,
1421        usize,
1422        usize,
1423    ) {
1424        (
1425            submessage.0.index,
1426            submessage.1.index,
1427            submessage.2.map(|s| s.index),
1428            submessage.3.index,
1429            submessage.4.index,
1430            submessage.5.index,
1431            submessage.6.index,
1432            submessage.7.index,
1433            submessage.8.index,
1434        )
1435    }
1436}