grib/
context.rs

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