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    /// Returns time-related calculated information associated with the
642    /// submessage.
643    ///
644    /// # Examples
645    ///
646    /// ```
647    /// use std::{
648    ///     fs::File,
649    ///     io::{BufReader, Read},
650    /// };
651    ///
652    /// use chrono::{TimeZone, Utc};
653    /// use grib::{
654    ///     codetables::grib2::{Table1_2, Table4_4},
655    ///     Code, ForecastTime, TemporalInfo, UtcDateTime,
656    /// };
657    ///
658    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
659    ///     let f = File::open(
660    ///         "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
661    ///     )?;
662    ///     let f = BufReader::new(f);
663    ///     let grib2 = grib::from_reader(f)?;
664    ///
665    ///     let mut iter = grib2.iter();
666    ///
667    ///     {
668    ///         let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
669    ///         let actual = message.temporal_info();
670    ///         let expected = TemporalInfo {
671    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
672    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
673    ///         };
674    ///         assert_eq!(actual, expected);
675    ///     }
676    ///
677    ///     {
678    ///         let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
679    ///         let actual = message.temporal_info();
680    ///         let expected = TemporalInfo {
681    ///             ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
682    ///             forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 10, 0).unwrap()),
683    ///         };
684    ///         assert_eq!(actual, expected);
685    ///     }
686    ///
687    ///     Ok(())
688    /// }
689    /// ```
690    pub fn temporal_info(&self) -> TemporalInfo {
691        let raw_info = self.temporal_raw_info();
692        TemporalInfo::from(&raw_info)
693    }
694
695    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
696    /// the i and j directions.
697    ///
698    /// # Examples
699    ///
700    /// ```
701    /// use std::{
702    ///     fs::File,
703    ///     io::{BufReader, Read},
704    /// };
705    ///
706    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
707    ///     let mut buf = Vec::new();
708    ///
709    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
710    ///     let f = BufReader::new(f);
711    ///     let mut f = xz2::bufread::XzDecoder::new(f);
712    ///     f.read_to_end(&mut buf)?;
713    ///
714    ///     let f = std::io::Cursor::new(buf);
715    ///     let grib2 = grib::from_reader(f)?;
716    ///
717    ///     let mut iter = grib2.iter();
718    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
719    ///
720    ///     let shape = message.grid_shape()?;
721    ///     assert_eq!(shape, (1440, 721));
722    ///     Ok(())
723    /// }
724    /// ```
725    pub fn grid_shape(&self) -> Result<(usize, usize), GribError> {
726        let grid_def = self.grid_def();
727        let shape = GridDefinitionTemplateValues::try_from(grid_def)?.grid_shape();
728        Ok(shape)
729    }
730
731    /// Computes and returns an iterator over `(i, j)` of grid points.
732    ///
733    /// The order of items is the same as the order of the grid point values,
734    /// defined by the scanning mode ([`ScanningMode`](`crate::ScanningMode`))
735    /// in the data.
736    ///
737    /// This iterator allows users to perform their own coordinate calculations
738    /// for unsupported grid systems and map the results to grid point values.
739    ///
740    /// # Examples
741    ///
742    /// ```
743    /// use std::{
744    ///     fs::File,
745    ///     io::{BufReader, Read},
746    /// };
747    ///
748    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
749    ///     let mut buf = Vec::new();
750    ///
751    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
752    ///     let f = BufReader::new(f);
753    ///     let mut f = xz2::bufread::XzDecoder::new(f);
754    ///     f.read_to_end(&mut buf)?;
755    ///
756    ///     let f = std::io::Cursor::new(buf);
757    ///     let grib2 = grib::from_reader(f)?;
758    ///
759    ///     let mut iter = grib2.iter();
760    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
761    ///
762    ///     let mut latlons = message.ij()?;
763    ///     assert_eq!(latlons.next(), Some((0, 0)));
764    ///     assert_eq!(latlons.next(), Some((1, 0)));
765    ///     Ok(())
766    /// }
767    /// ```
768    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
769        let grid_def = self.grid_def();
770        let num_defined = grid_def.num_points() as usize;
771        let ij = GridDefinitionTemplateValues::try_from(grid_def)?.ij()?;
772        let (num_decoded, _) = ij.size_hint();
773        if num_defined == num_decoded {
774            Ok(ij)
775        } else {
776            Err(GribError::InvalidValueError(format!(
777                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
778            )))
779        }
780    }
781
782    /// Computes and returns an iterator over latitudes and longitudes of grid
783    /// points.
784    ///
785    /// The order of lat/lon data of grid points is the same as the order of the
786    /// grid point values, defined by the scanning mode
787    /// ([`ScanningMode`](`crate::ScanningMode`)) in the data.
788    ///
789    /// # Examples
790    ///
791    /// ```
792    /// use std::{
793    ///     fs::File,
794    ///     io::{BufReader, Read},
795    /// };
796    ///
797    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
798    ///     let mut buf = Vec::new();
799    ///
800    ///     let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
801    ///     let f = BufReader::new(f);
802    ///     let mut f = xz2::bufread::XzDecoder::new(f);
803    ///     f.read_to_end(&mut buf)?;
804    ///
805    ///     let f = std::io::Cursor::new(buf);
806    ///     let grib2 = grib::from_reader(f)?;
807    ///
808    ///     let mut iter = grib2.iter();
809    ///     let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
810    ///
811    ///     let mut latlons = message.latlons()?;
812    ///     assert_eq!(latlons.next(), Some((90.0, 0.0)));
813    ///     assert_eq!(latlons.next(), Some((90.0, 0.25000003)));
814    ///     Ok(())
815    /// }
816    /// ```
817    pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
818        let grid_def = self.grid_def();
819        let num_defined = grid_def.num_points() as usize;
820        let latlons = GridDefinitionTemplateValues::try_from(grid_def)?.latlons()?;
821        let (num_decoded, _) = latlons.size_hint();
822        if num_defined == num_decoded {
823            Ok(latlons)
824        } else {
825            Err(GribError::InvalidValueError(format!(
826                "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
827            )))
828        }
829    }
830}
831
832pub struct SubMessageSection<'a> {
833    pub index: usize,
834    pub body: &'a SectionInfo,
835}
836
837impl<'a> SubMessageSection<'a> {
838    pub fn new(index: usize, body: &'a SectionInfo) -> Self {
839        Self { index, body }
840    }
841
842    pub fn template_code(&self) -> Option<TemplateInfo> {
843        self.body.get_tmpl_code()
844    }
845
846    pub fn describe(&self) -> Option<String> {
847        self.template_code().and_then(|code| code.describe())
848    }
849}
850
851#[cfg(test)]
852mod tests {
853    use std::{fs::File, io::BufReader};
854
855    use super::*;
856
857    macro_rules! sect_placeholder {
858        ($num:expr) => {{
859            SectionInfo {
860                num: $num,
861                offset: 0,
862                size: 0,
863                body: None,
864            }
865        }};
866    }
867
868    #[test]
869    fn context_from_buf_reader() {
870        let f = File::open(
871            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
872        )
873        .unwrap();
874        let f = BufReader::new(f);
875        let result = from_reader(f);
876        assert!(result.is_ok())
877    }
878
879    #[test]
880    fn context_from_bytes() {
881        let f = File::open(
882            "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
883        )
884        .unwrap();
885        let mut f = BufReader::new(f);
886        let mut buf = Vec::new();
887        f.read_to_end(&mut buf).unwrap();
888        let result = from_bytes(&buf);
889        assert!(result.is_ok())
890    }
891
892    #[test]
893    fn get_tmpl_code_normal() {
894        let sect = SectionInfo {
895            num: 5,
896            offset: 8902,
897            size: 23,
898            body: Some(SectionBody::Section5(
899                ReprDefinition::from_payload(
900                    vec![0x00, 0x01, 0x50, 0x00, 0x00, 0xc8].into_boxed_slice(),
901                )
902                .unwrap(),
903            )),
904        };
905
906        assert_eq!(sect.get_tmpl_code(), Some(TemplateInfo(5, 200)));
907    }
908
909    #[test]
910    fn get_templates_normal() {
911        let sects = vec![
912            sect_placeholder!(0),
913            sect_placeholder!(1),
914            SectionInfo {
915                num: 3,
916                offset: 0,
917                size: 0,
918                body: Some(SectionBody::Section3(
919                    GridDefinition::from_payload(vec![0; 9].into_boxed_slice()).unwrap(),
920                )),
921            },
922            SectionInfo {
923                num: 4,
924                offset: 0,
925                size: 0,
926                body: Some(SectionBody::Section4(
927                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
928                )),
929            },
930            SectionInfo {
931                num: 5,
932                offset: 0,
933                size: 0,
934                body: Some(SectionBody::Section5(
935                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
936                )),
937            },
938            sect_placeholder!(6),
939            sect_placeholder!(7),
940            SectionInfo {
941                num: 3,
942                offset: 0,
943                size: 0,
944                body: Some(SectionBody::Section3(
945                    GridDefinition::from_payload(
946                        vec![0, 0, 0, 0, 0, 0, 0, 0, 1].into_boxed_slice(),
947                    )
948                    .unwrap(),
949                )),
950            },
951            SectionInfo {
952                num: 4,
953                offset: 0,
954                size: 0,
955                body: Some(SectionBody::Section4(
956                    ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
957                )),
958            },
959            SectionInfo {
960                num: 5,
961                offset: 0,
962                size: 0,
963                body: Some(SectionBody::Section5(
964                    ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
965                )),
966            },
967            sect_placeholder!(6),
968            sect_placeholder!(7),
969            sect_placeholder!(8),
970        ]
971        .into_boxed_slice();
972
973        assert_eq!(
974            get_templates(&sects),
975            vec![
976                TemplateInfo(3, 0),
977                TemplateInfo(3, 1),
978                TemplateInfo(4, 0),
979                TemplateInfo(5, 0),
980            ]
981        );
982    }
983
984    macro_rules! test_submessage_iterator {
985        ($((
986            $name:ident,
987            $xz_compressed_input:expr,
988            $nth:expr,
989            $expected_index:expr,
990            $expected_section_indices:expr,
991        ),)*) => ($(
992            #[test]
993            fn $name() -> Result<(), Box<dyn std::error::Error>> {
994                let mut buf = Vec::new();
995
996                let f = File::open($xz_compressed_input)?;
997                let f = BufReader::new(f);
998                let mut f = xz2::bufread::XzDecoder::new(f);
999                f.read_to_end(&mut buf)?;
1000
1001                let f = Cursor::new(buf);
1002                let grib2 = crate::from_reader(f)?;
1003                let mut iter = grib2.iter();
1004
1005                let (actual_index, message) = iter.nth($nth).ok_or_else(|| "item not available")?;
1006                assert_eq!(actual_index, $expected_index);
1007                let actual_section_indices = get_section_indices(message);
1008                assert_eq!(actual_section_indices, $expected_section_indices);
1009
1010                Ok(())
1011            }
1012        )*);
1013    }
1014
1015    test_submessage_iterator! {
1016        (
1017            item_0_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1018            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1019            0,
1020            (0, 0),
1021            (0, 1, None, 2, 3, 4, 5, 6, 0),
1022        ),
1023        (
1024            item_1_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1025            "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1026            1,
1027            (0, 1),
1028            (0, 1, None, 2, 7, 8, 9, 10, 0),
1029        ),
1030        (
1031            item_0_from_submessage_iterator_for_multi_message_data,
1032            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1033            0,
1034            (0, 0),
1035            (0, 1, None, 2, 3, 4, 5, 6, 7),
1036        ),
1037        (
1038            item_1_from_submessage_iterator_for_multi_message_data,
1039            "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1040            1,
1041            (1, 0),
1042            (8, 9, None, 10, 11, 12, 13, 14, 15),
1043        ),
1044    }
1045
1046    fn get_section_indices<R>(
1047        submessage: SubMessage<'_, R>,
1048    ) -> (
1049        usize,
1050        usize,
1051        Option<usize>,
1052        usize,
1053        usize,
1054        usize,
1055        usize,
1056        usize,
1057        usize,
1058    ) {
1059        (
1060            submessage.0.index,
1061            submessage.1.index,
1062            submessage.2.map(|s| s.index),
1063            submessage.3.index,
1064            submessage.4.index,
1065            submessage.5.index,
1066            submessage.6.index,
1067            submessage.7.index,
1068            submessage.8.index,
1069        )
1070    }
1071}