Skip to main content

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<(), std::io::Error> {
899        let write_heading =
900            |writer: &mut W, sect: &SectionInfo, sect_name: &str| -> Result<(), std::io::Error> {
901                let SectionInfo { num, size, .. } = sect;
902                let sect_name = sect_name.to_ascii_uppercase();
903                writeln!(writer, "##  SECTION {num}: {sect_name} (length = {size})")
904            };
905
906        macro_rules! write_section {
907            ($sect:expr) => {{
908                let mut pos = 1;
909                match $sect {
910                    Ok(s) => s.dump(None, &mut pos, writer)?,
911                    Err(e) => writeln!(writer, "error: {}", e)?,
912                }
913            }};
914        }
915
916        let total_length = self.indicator().total_length;
917        writeln!(writer, "#  SUBMESSAGE (total_length = {total_length})")?;
918        write_heading(writer, self.0.body, "indicator section")?;
919        write_heading(writer, self.1.body, "identification section")?;
920        write_section!(self.section1());
921        if let Some(sect) = &self.2 {
922            write_heading(writer, sect.body, "local use section")?;
923        }
924        write_heading(writer, self.3.body, "grid definition section")?;
925        write_section!(self.section3());
926        write_heading(writer, self.4.body, "product definition section")?;
927        write_section!(self.section4());
928        write_heading(writer, self.5.body, "data representation section")?;
929        write_section!(self.section5());
930        write_heading(writer, self.6.body, "bit-map section")?;
931        write_section!(self.section6());
932        write_heading(writer, self.7.body, "data section")?;
933
934        // Since `self.8.body` might be dummy, we don't use that Section 8 data.
935        writeln!(writer, "##  SECTION 8: END SECTION (length = 4)")?;
936
937        Ok(())
938    }
939
940    /// Returns time-related raw information associated with the submessage.
941    ///
942    /// # Examples
943    ///
944    /// ```
945    /// use std::{
946    ///     fs::File,
947    ///     io::{BufReader, Read},
948    /// };
949    ///
950    /// use grib::{
951    ///     Code, ForecastTime, TemporalRawInfo,
952    ///     codetables::grib2::{Table1_2, Table4_4},
953    ///     def::grib2::RefTime,
954    /// };
955    ///
956    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
957    ///     let f = File::open(
958    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
959    ///     )?;
960    ///     let f = BufReader::new(f);
961    ///     let grib2 = grib::from_reader(f)?;
962    ///
963    ///     let mut iter = grib2.iter();
964    ///
965    ///     {
966    ///         let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
967    ///         let actual = message.temporal_raw_info();
968    ///         let expected = TemporalRawInfo {
969    ///             ref_time_significance: Code::Name(Table1_2::Analysis),
970    ///             ref_time_unchecked: RefTime::new(2016, 8, 22, 2, 0, 0),
971    ///             forecast_time_diff: Some(ForecastTime {
972    ///                 unit: Code::Name(Table4_4::Minute),
973    ///                 value: 0,
974    ///             }),
975    ///         };
976    ///         assert_eq!(actual, expected);
977    ///     }
978    ///
979    ///     {
980    ///         let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
981    ///         let actual = message.temporal_raw_info();
982    ///         let expected = TemporalRawInfo {
983    ///             ref_time_significance: Code::Name(Table1_2::Analysis),
984    ///             ref_time_unchecked: RefTime::new(2016, 8, 22, 2, 0, 0),
985    ///             forecast_time_diff: Some(ForecastTime {
986    ///                 unit: Code::Name(Table4_4::Minute),
987    ///                 value: 10,
988    ///             }),
989    ///         };
990    ///         assert_eq!(actual, expected);
991    ///     }
992    ///
993    ///     Ok(())
994    /// }
995    /// ```
996    pub fn temporal_raw_info(&self) -> TemporalRawInfo {
997        let ref_time_significance = self.identification().ref_time_significance();
998        let ref_time_unchecked = self.identification().ref_time_unchecked();
999        let forecast_time = self.prod_def().forecast_time();
1000        TemporalRawInfo::new(ref_time_significance, ref_time_unchecked, forecast_time)
1001    }
1002
1003    #[cfg(feature = "time-calculation")]
1004    #[cfg_attr(docsrs, doc(cfg(feature = "time-calculation")))]
1005    /// Returns time-related calculated information associated with the
1006    /// submessage.
1007    ///
1008    /// # Examples
1009    ///
1010    /// ```
1011    /// use std::{
1012    ///     fs::File,
1013    ///     io::{BufReader, Read},
1014    /// };
1015    ///
1016    /// use chrono::{TimeZone, Utc};
1017    /// use grib::{
1018    ///     Code, ForecastTime, TemporalInfo,
1019    ///     codetables::grib2::{Table1_2, Table4_4},
1020    /// };
1021    ///
1022    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1023    ///     let f = File::open(
1024    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
1025    ///     )?;
1026    ///     let f = BufReader::new(f);
1027    ///     let grib2 = grib::from_reader(f)?;
1028    ///
1029    ///     let mut iter = grib2.iter();
1030    ///
1031    ///     {
1032    ///         let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1033    ///         let actual = message.temporal_info();
1034    ///         let expected = TemporalInfo {
1035    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1036    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1037    ///         };
1038    ///         assert_eq!(actual, expected);
1039    ///     }
1040    ///
1041    ///     {
1042    ///         let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
1043    ///         let actual = message.temporal_info();
1044    ///         let expected = TemporalInfo {
1045    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1046    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 10, 0).unwrap()),
1047    ///         };
1048    ///         assert_eq!(actual, expected);
1049    ///     }
1050    ///
1051    ///     Ok(())
1052    /// }
1053    /// ```
1054    pub fn temporal_info(&self) -> TemporalInfo {
1055        let raw_info = self.temporal_raw_info();
1056        TemporalInfo::from(&raw_info)
1057    }
1058
1059    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
1060    /// the i and j directions.
1061    ///
1062    /// # Examples
1063    ///
1064    /// ```
1065    /// use std::{
1066    ///     fs::File,
1067    ///     io::{BufReader, Read},
1068    /// };
1069    ///
1070    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1071    ///     let mut buf = Vec::new();
1072    ///
1073    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1074    ///     let f = BufReader::new(f);
1075    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1076    ///     f.read_to_end(&mut buf)?;
1077    ///
1078    ///     let f = std::io::Cursor::new(buf);
1079    ///     let grib2 = grib::from_reader(f)?;
1080    ///
1081    ///     let mut iter = grib2.iter();
1082    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1083    ///
1084    ///     let shape = message.grid_shape()?;
1085    ///     assert_eq!(shape, (1440, 721));
1086    ///     Ok(())
1087    /// }
1088    /// ```
1089    pub fn grid_shape(&self) -> Result<(usize, usize), GribError> {
1090        let grid_def = self.grid_def();
1091        let shape = GridDefinitionTemplateValues::try_from(grid_def)?.grid_shape();
1092        Ok(shape)
1093    }
1094
1095    /// Computes and returns an iterator over `(i, j)` of grid points.
1096    ///
1097    /// The order of items is the same as the order of the grid point values,
1098    /// defined by the scanning mode ([`ScanningMode`](`crate::ScanningMode`))
1099    /// in the data.
1100    ///
1101    /// This iterator allows users to perform their own coordinate calculations
1102    /// for unsupported grid systems and map the results to grid point values.
1103    ///
1104    /// # Examples
1105    ///
1106    /// ```
1107    /// use std::{
1108    ///     fs::File,
1109    ///     io::{BufReader, Read},
1110    /// };
1111    ///
1112    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1113    ///     let mut buf = Vec::new();
1114    ///
1115    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1116    ///     let f = BufReader::new(f);
1117    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1118    ///     f.read_to_end(&mut buf)?;
1119    ///
1120    ///     let f = std::io::Cursor::new(buf);
1121    ///     let grib2 = grib::from_reader(f)?;
1122    ///
1123    ///     let mut iter = grib2.iter();
1124    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1125    ///
1126    ///     let mut latlons = message.ij()?;
1127    ///     assert_eq!(latlons.next(), Some((0, 0)));
1128    ///     assert_eq!(latlons.next(), Some((1, 0)));
1129    ///     Ok(())
1130    /// }
1131    /// ```
1132    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
1133        let grid_def = self.grid_def();
1134        let num_defined = grid_def.num_points() as usize;
1135        let ij = GridDefinitionTemplateValues::try_from(grid_def)?.ij()?;
1136        let (num_decoded, _) = ij.size_hint();
1137        if num_defined == num_decoded {
1138            Ok(ij)
1139        } else {
1140            Err(GribError::InvalidValueError(format!(
1141                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1142            )))
1143        }
1144    }
1145
1146    /// Computes and returns an iterator over latitudes and longitudes of grid
1147    /// points.
1148    ///
1149    /// The order of lat/lon data of grid points is the same as the order of the
1150    /// grid point values, defined by the scanning mode
1151    /// ([`ScanningMode`](`crate::ScanningMode`)) in the data.
1152    ///
1153    /// # Examples
1154    ///
1155    /// ```
1156    /// use std::{
1157    ///     fs::File,
1158    ///     io::{BufReader, Read},
1159    /// };
1160    ///
1161    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1162    ///     let mut buf = Vec::new();
1163    ///
1164    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1165    ///     let f = BufReader::new(f);
1166    ///     let mut f = xz2::bufread::XzDecoder::new(f);
1167    ///     f.read_to_end(&mut buf)?;
1168    ///
1169    ///     let f = std::io::Cursor::new(buf);
1170    ///     let grib2 = grib::from_reader(f)?;
1171    ///
1172    ///     let mut iter = grib2.iter();
1173    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1174    ///
1175    ///     let mut latlons = message.latlons()?;
1176    ///     assert_eq!(latlons.next(), Some((90.0, 0.0)));
1177    ///     assert_eq!(latlons.next(), Some((90.0, 0.25000003)));
1178    ///     Ok(())
1179    /// }
1180    /// ```
1181    pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
1182        let grid_def = self.grid_def();
1183        let num_defined = grid_def.num_points() as usize;
1184        let latlons = GridDefinitionTemplateValues::try_from(grid_def)?.latlons()?;
1185        let (num_decoded, _) = latlons.size_hint();
1186        if num_defined == num_decoded {
1187            Ok(latlons)
1188        } else {
1189            Err(GribError::InvalidValueError(format!(
1190                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1191            )))
1192        }
1193    }
1194}
1195
1196pub struct SubMessageSection<'a> {
1197    pub index: usize,
1198    pub body: &'a SectionInfo,
1199}
1200
1201impl<'a> SubMessageSection<'a> {
1202    pub fn new(index: usize, body: &'a SectionInfo) -> Self {
1203        Self { index, body }
1204    }
1205
1206    pub fn template_code(&self) -> Option<TemplateInfo> {
1207        self.body.get_tmpl_code()
1208    }
1209
1210    pub fn describe(&self) -> Option<String> {
1211        self.template_code().and_then(|code| code.describe())
1212    }
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217    use std::{fs::File, io::BufReader};
1218
1219    use super::*;
1220
1221    macro_rules! sect_placeholder {
1222        ($num:expr) => {{
1223            SectionInfo {
1224                num: $num,
1225                offset: 0,
1226                size: 0,
1227                body: None,
1228            }
1229        }};
1230    }
1231
1232    #[test]
1233    fn context_from_buf_reader() {
1234        let f = File::open(
1235            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1236        )
1237        .unwrap();
1238        let f = BufReader::new(f);
1239        let result = from_reader(f);
1240        assert!(result.is_ok())
1241    }
1242
1243    #[test]
1244    fn context_from_bytes() {
1245        let f = File::open(
1246            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1247        )
1248        .unwrap();
1249        let mut f = BufReader::new(f);
1250        let mut buf = Vec::new();
1251        f.read_to_end(&mut buf).unwrap();
1252        let result = from_bytes(&buf);
1253        assert!(result.is_ok())
1254    }
1255
1256    #[test]
1257    fn get_tmpl_code_normal() {
1258        let sect = SectionInfo {
1259            num: 5,
1260            offset: 8902,
1261            size: 23,
1262            body: Some(SectionBody::Section5(
1263                ReprDefinition::from_payload(
1264                    vec![0x00, 0x01, 0x50, 0x00, 0x00, 0xc8].into_boxed_slice(),
1265                )
1266                .unwrap(),
1267            )),
1268        };
1269
1270        assert_eq!(sect.get_tmpl_code(), Some(TemplateInfo(5, 200)));
1271    }
1272
1273    #[test]
1274    fn get_templates_normal() {
1275        let sects = vec![
1276            sect_placeholder!(0),
1277            sect_placeholder!(1),
1278            SectionInfo {
1279                num: 3,
1280                offset: 0,
1281                size: 0,
1282                body: Some(SectionBody::Section3(
1283                    GridDefinition::from_payload(vec![0; 9].into_boxed_slice()).unwrap(),
1284                )),
1285            },
1286            SectionInfo {
1287                num: 4,
1288                offset: 0,
1289                size: 0,
1290                body: Some(SectionBody::Section4(
1291                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1292                )),
1293            },
1294            SectionInfo {
1295                num: 5,
1296                offset: 0,
1297                size: 0,
1298                body: Some(SectionBody::Section5(
1299                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1300                )),
1301            },
1302            sect_placeholder!(6),
1303            sect_placeholder!(7),
1304            SectionInfo {
1305                num: 3,
1306                offset: 0,
1307                size: 0,
1308                body: Some(SectionBody::Section3(
1309                    GridDefinition::from_payload(
1310                        vec![0, 0, 0, 0, 0, 0, 0, 0, 1].into_boxed_slice(),
1311                    )
1312                    .unwrap(),
1313                )),
1314            },
1315            SectionInfo {
1316                num: 4,
1317                offset: 0,
1318                size: 0,
1319                body: Some(SectionBody::Section4(
1320                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1321                )),
1322            },
1323            SectionInfo {
1324                num: 5,
1325                offset: 0,
1326                size: 0,
1327                body: Some(SectionBody::Section5(
1328                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1329                )),
1330            },
1331            sect_placeholder!(6),
1332            sect_placeholder!(7),
1333            sect_placeholder!(8),
1334        ]
1335        .into_boxed_slice();
1336
1337        assert_eq!(
1338            get_templates(&sects),
1339            vec![
1340                TemplateInfo(3, 0),
1341                TemplateInfo(3, 1),
1342                TemplateInfo(4, 0),
1343                TemplateInfo(5, 0),
1344            ]
1345        );
1346    }
1347
1348    macro_rules! test_submessage_iterator {
1349        ($((
1350            $name:ident,
1351            $xz_compressed_input:expr,
1352            $nth:expr,
1353            $expected_index:expr,
1354            $expected_section_indices:expr,
1355        ),)*) => ($(
1356            #[test]
1357            fn $name() -> Result<(), Box<dyn std::error::Error>> {
1358                let mut buf = Vec::new();
1359
1360                let f = File::open($xz_compressed_input)?;
1361                let f = BufReader::new(f);
1362                let mut f = xz2::bufread::XzDecoder::new(f);
1363                f.read_to_end(&mut buf)?;
1364
1365                let f = Cursor::new(buf);
1366                let grib2 = crate::from_reader(f)?;
1367                let mut iter = grib2.iter();
1368
1369                let (actual_index, message) = iter.nth($nth).ok_or_else(|| "item not available")?;
1370                assert_eq!(actual_index, $expected_index);
1371                let actual_section_indices = get_section_indices(message);
1372                assert_eq!(actual_section_indices, $expected_section_indices);
1373
1374                Ok(())
1375            }
1376        )*);
1377    }
1378
1379    test_submessage_iterator! {
1380        (
1381            item_0_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1382            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1383            0,
1384            (0, 0),
1385            (0, 1, None, 2, 3, 4, 5, 6, 0),
1386        ),
1387        (
1388            item_1_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1389            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1390            1,
1391            (0, 1),
1392            (0, 1, None, 2, 7, 8, 9, 10, 0),
1393        ),
1394        (
1395            item_0_from_submessage_iterator_for_multi_message_data,
1396            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1397            0,
1398            (0, 0),
1399            (0, 1, None, 2, 3, 4, 5, 6, 7),
1400        ),
1401        (
1402            item_1_from_submessage_iterator_for_multi_message_data,
1403            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1404            1,
1405            (1, 0),
1406            (8, 9, None, 10, 11, 12, 13, 14, 15),
1407        ),
1408    }
1409
1410    fn get_section_indices<R>(
1411        submessage: SubMessage<'_, R>,
1412    ) -> (
1413        usize,
1414        usize,
1415        Option<usize>,
1416        usize,
1417        usize,
1418        usize,
1419        usize,
1420        usize,
1421        usize,
1422    ) {
1423        (
1424            submessage.0.index,
1425            submessage.1.index,
1426            submessage.2.map(|s| s.index),
1427            submessage.3.index,
1428            submessage.4.index,
1429            submessage.5.index,
1430            submessage.6.index,
1431            submessage.7.index,
1432            submessage.8.index,
1433        )
1434    }
1435}