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 GridDefinitionTemplateValues, GridPointIndex, GridPointIndexIterator, LatLons, 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::GridPointLatLons,
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 /// template: grib::def::grib2::GridDefinitionTemplate::_3_0(grib::def::grib2::template::Template3_0 {
664 /// earth: grib::def::grib2::template::param_set::EarthShape {
665 /// shape: 4,
666 /// spherical_earth_radius_scale_factor: 0xff,
667 /// spherical_earth_radius_scaled_value: 0xffffffff,
668 /// major_axis_scale_factor: 1,
669 /// major_axis_scaled_value: 63781370,
670 /// minor_axis_scale_factor: 1,
671 /// minor_axis_scaled_value: 63567523,
672 /// },
673 /// lat_lon: grib::def::grib2::template::param_set::LatLonGrid {
674 /// grid: grib::def::grib2::template::param_set::Grid {
675 /// ni: 256,
676 /// nj: 336,
677 /// initial_production_domain_basic_angle: 0,
678 /// basic_angle_subdivisions: 0xffffffff,
679 /// first_point_lat: 47958333,
680 /// first_point_lon: 118062500,
681 /// resolution_and_component_flags: grib::def::grib2::template::param_set::ResolutionAndComponentFlags(0b00110000),
682 /// last_point_lat: 20041667,
683 /// last_point_lon: 149937500,
684 /// },
685 /// i_direction_inc: 125000,
686 /// j_direction_inc: 83333,
687 /// scanning_mode: grib::def::grib2::template::param_set::ScanningMode(0b00000000),
688 /// },
689 /// }),
690 /// },
691 /// });
692 /// assert_eq!(actual, expected);
693 ///
694 /// Ok(())
695 /// }
696 /// ```
697 pub fn section3(&self) -> Result<Section3, GribError> {
698 let GridDefinition { payload } = self.grid_def();
699 let mut pos = 0;
700 let payload = crate::def::grib2::Section3Payload::try_from_slice(payload, &mut pos)
701 .map_err(|e| GribError::Unknown(e.to_owned()))?;
702
703 let SectionInfo { num, size, .. } = self.3.body;
704 Ok(Section3 {
705 header: SectionHeader {
706 len: *size as u32,
707 sect_num: *num,
708 },
709 payload,
710 })
711 }
712
713 /// Provides access to the parameters in Section 4.
714 ///
715 /// # Examples
716 /// ```
717 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
718 /// let f = std::fs::File::open(
719 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
720 /// )?;
721 /// let f = std::io::BufReader::new(f);
722 /// let grib2 = grib::from_reader(f)?;
723 /// let (_index, first_submessage) = grib2.iter().next().unwrap();
724 ///
725 /// let actual = first_submessage.section4();
726 /// let expected = Ok(grib::def::grib2::Section4 {
727 /// header: grib::def::grib2::SectionHeader {
728 /// len: 34,
729 /// sect_num: 4,
730 /// },
731 /// payload: grib::def::grib2::Section4Payload {
732 /// num_coord_values: 0,
733 /// template_num: 0,
734 /// },
735 /// });
736 /// assert_eq!(actual, expected);
737 ///
738 /// Ok(())
739 /// }
740 /// ```
741 pub fn section4(&self) -> Result<Section4, GribError> {
742 let ProdDefinition { payload }: &ProdDefinition = self.prod_def();
743 let mut pos = 0;
744 let payload = crate::def::grib2::Section4Payload::try_from_slice(payload, &mut pos)
745 .map_err(|e| GribError::Unknown(e.to_owned()))?;
746
747 let SectionInfo { num, size, .. } = self.4.body;
748 Ok(Section4 {
749 header: SectionHeader {
750 len: *size as u32,
751 sect_num: *num,
752 },
753 payload,
754 })
755 }
756
757 /// Provides access to the parameters in Section 5.
758 ///
759 /// # Examples
760 /// ```
761 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
762 /// let f = std::fs::File::open(
763 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
764 /// )?;
765 /// let f = std::io::BufReader::new(f);
766 /// let grib2 = grib::from_reader(f)?;
767 /// let (_index, first_submessage) = grib2.iter().next().unwrap();
768 ///
769 /// let actual = first_submessage.section5();
770 /// let expected = Ok(grib::def::grib2::Section5 {
771 /// header: grib::def::grib2::SectionHeader {
772 /// len: 23,
773 /// sect_num: 5,
774 /// },
775 /// payload: grib::def::grib2::Section5Payload {
776 /// num_encoded_points: 86016,
777 /// template_num: 200,
778 /// template: grib::def::grib2::DataRepresentationTemplate::_5_200(
779 /// grib::def::grib2::template::Template5_200 {
780 /// num_bits: 8,
781 /// max_val: 3,
782 /// max_level: 3,
783 /// dec: 0,
784 /// level_vals: vec![1, 2, 3],
785 /// },
786 /// ),
787 /// },
788 /// });
789 /// assert_eq!(actual, expected);
790 ///
791 /// Ok(())
792 /// }
793 /// ```
794 pub fn section5(&self) -> Result<Section5, GribError> {
795 let ReprDefinition { payload } = self.repr_def();
796 let mut pos = 0;
797 let payload = crate::def::grib2::Section5Payload::try_from_slice(payload, &mut pos)
798 .map_err(|e| GribError::Unknown(e.to_owned()))?;
799
800 let SectionInfo { num, size, .. } = self.5.body;
801 Ok(Section5 {
802 header: SectionHeader {
803 len: *size as u32,
804 sect_num: *num,
805 },
806 payload,
807 })
808 }
809
810 /// Provides access to the parameters in Section 6.
811 ///
812 /// # Examples
813 /// ```
814 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
815 /// let f = std::fs::File::open(
816 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
817 /// )?;
818 /// let f = std::io::BufReader::new(f);
819 /// let grib2 = grib::from_reader(f)?;
820 /// let (_index, first_submessage) = grib2.iter().next().unwrap();
821 ///
822 /// let actual = first_submessage.section6();
823 /// let expected = Ok(grib::def::grib2::Section6 {
824 /// header: grib::def::grib2::SectionHeader {
825 /// len: 6,
826 /// sect_num: 6,
827 /// },
828 /// payload: grib::def::grib2::Section6Payload {
829 /// bitmap_indicator: 255,
830 /// },
831 /// });
832 /// assert_eq!(actual, expected);
833 ///
834 /// Ok(())
835 /// }
836 /// ```
837 pub fn section6(&self) -> Result<Section6, GribError> {
838 // panics should not happen if data is correct
839 let BitMap { bitmap_indicator } = match self.6.body.body.as_ref().unwrap() {
840 SectionBody::Section6(data) => data,
841 _ => panic!("something unexpected happened"),
842 };
843 let payload = crate::def::grib2::Section6Payload {
844 bitmap_indicator: *bitmap_indicator,
845 };
846
847 let SectionInfo { num, size, .. } = self.6.body;
848 Ok(Section6 {
849 header: SectionHeader {
850 len: *size as u32,
851 sect_num: *num,
852 },
853 payload,
854 })
855 }
856
857 /// Dumps the GRIB2 submessage.
858 ///
859 /// # Examples
860 /// ```
861 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
862 /// let f = std::fs::File::open(
863 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
864 /// )?;
865 /// let f = std::io::BufReader::new(f);
866 /// let grib2 = grib::from_reader(f)?;
867 /// let (_index, first_submessage) = grib2.iter().next().unwrap();
868 ///
869 /// let mut buf = std::io::Cursor::new(Vec::with_capacity(1024));
870 /// first_submessage.dump(&mut buf)?;
871 /// let expected = "\
872 /// ## SUBMESSAGE (total_length = 10321)
873 /// ### SECTION 0: INDICATOR SECTION (length = 16)
874 /// ### SECTION 1: IDENTIFICATION SECTION (length = 21)
875 /// 1-4 header.len = 21 // Length of section in octets (nn).
876 /// 5 header.sect_num = 1 // Number of section.
877 /// 6-7 payload.centre_id = 34 // Identification of originating/generating centre (see Common Code table C-11).
878 /// 8-9 payload.subcentre_id = 0 // Identification of originating/generating subcentre (allocated by originating/generating centre).
879 /// 10 payload.master_table_version = 5 // GRIB master table version number (see Common Code table C-0 and Note 1).
880 /// 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).
881 /// 12 payload.ref_time_significance = 0 // Significance of reference time (see Code table 1.2).
882 /// 13-14 payload.ref_time.year = 2016 // Year (4 digits).
883 /// 15 payload.ref_time.month = 8 // Month.
884 /// 16 payload.ref_time.day = 22 // Day.
885 /// 17 payload.ref_time.hour = 2 // Hour.
886 /// 18 payload.ref_time.minute = 0 // Minute.
887 /// 19 payload.ref_time.second = 0 // Second.
888 /// 20 payload.prod_status = 0 // Production status of processed data in this GRIB message (see Code table 1.3).
889 /// 21 payload.data_type = 2 // Type of processed data in this GRIB message (see Code table 1.4).
890 /// ### SECTION 3: GRID DEFINITION SECTION (length = 72)
891 /// 1-4 header.len = 72 // Length of section in octets (nn).
892 /// 5 header.sect_num = 3 // Number of section.
893 /// 6 payload.grid_def_source = 0 // Source of grid definition (see Code table 3.0 and Note 1).
894 /// 7-10 payload.num_points = 86016 // Number of data points.
895 /// 11 payload.num_point_list_octets = 0 // Number of octets for optional list of numbers (see Note 2).
896 /// 12 payload.point_list_interpretation = 0 // Interpretation of list of numbers (see Code table 3.11).
897 /// 13-14 payload.template_num = 0 // Grid definition template number (= N) (see Code table 3.1).
898 /// 15 payload.template.earth.shape = 4 // Shape of the Earth (see Code table 3.2).
899 /// 16 payload.template.earth.spherical_earth_radius_scale_factor = 255 // Scale factor of radius of spherical Earth.
900 /// 17-20 payload.template.earth.spherical_earth_radius_scaled_value = 4294967295 // Scaled value of radius of spherical Earth.
901 /// 21 payload.template.earth.major_axis_scale_factor = 1 // Scale factor of major axis of oblate spheroid Earth.
902 /// 22-25 payload.template.earth.major_axis_scaled_value = 63781370 // Scaled value of major axis of oblate spheroid Earth.
903 /// 26 payload.template.earth.minor_axis_scale_factor = 1 // Scale factor of minor axis of oblate spheroid Earth.
904 /// 27-30 payload.template.earth.minor_axis_scaled_value = 63567523 // Scaled value of minor axis of oblate spheroid Earth.
905 /// 31-34 payload.template.lat_lon.grid.ni = 256 // Ni - number of points along a parallel.
906 /// 35-38 payload.template.lat_lon.grid.nj = 336 // Nj - number of points along a meridian.
907 /// 39-42 payload.template.lat_lon.grid.initial_production_domain_basic_angle = 0 // Basic angle of the initial production domain (see Note 1).
908 /// 43-46 payload.template.lat_lon.grid.basic_angle_subdivisions = 4294967295 // Subdivisions of basic angle used to define extreme longitudes and latitudes, and direction increments (see Note 1).
909 /// 47-50 payload.template.lat_lon.grid.first_point_lat = 47958333 // La1 - latitude of first grid point (see Note 1).
910 /// 51-54 payload.template.lat_lon.grid.first_point_lon = 118062500 // Lo1 - longitude of first grid point (see Note 1).
911 /// 55 payload.template.lat_lon.grid.resolution_and_component_flags = 0b00110000 // Resolution and component flags (see Flag table 3.3).
912 /// 56-59 payload.template.lat_lon.grid.last_point_lat = 20041667 // La2 - latitude of last grid point (see Note 1).
913 /// 60-63 payload.template.lat_lon.grid.last_point_lon = 149937500 // Lo2 - longitude of last grid point (see Note 1).
914 /// 64-67 payload.template.lat_lon.i_direction_inc = 125000 // Di - i direction increment (see Notes 1 and 5).
915 /// 68-71 payload.template.lat_lon.j_direction_inc = 83333 // Dj - j direction increment (see Notes 1 and 5).
916 /// 72 payload.template.lat_lon.scanning_mode = 0b00000000 // Scanning mode (flags - see Flag table 3.4).
917 /// ### SECTION 4: PRODUCT DEFINITION SECTION (length = 34)
918 /// 1-4 header.len = 34 // Length of section in octets (nn).
919 /// 5 header.sect_num = 4 // Number of section.
920 /// 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).
921 /// 8-9 payload.template_num = 0 // Product definition template number (see Code table 4.0).
922 /// ### SECTION 5: DATA REPRESENTATION SECTION (length = 23)
923 /// 1-4 header.len = 23 // Length of section in octets (nn).
924 /// 5 header.sect_num = 5 // Number of section.
925 /// 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.
926 /// 10-11 payload.template_num = 200 // Data representation template number (see Code table 5.0).
927 /// 12 payload.template.num_bits = 8 // Number of bits used for each packed value in the run length packing with level value.
928 /// 13-14 payload.template.max_val = 3 // MV - maximum value within the levels that are used in the packing.
929 /// 15-16 payload.template.max_level = 3 // MVL - maximum value of level (predefined).
930 /// 17 payload.template.dec = 0 // Decimal scale factor of representative value of each level.
931 /// 18-23 payload.template.level_vals = [1, 2, 3] // List of MVL scaled representative values of each level from lv=1 to MVL.
932 /// ### SECTION 6: BIT-MAP SECTION (length = 6)
933 /// 1-4 header.len = 6 // Length of section in octets (nn).
934 /// 5 header.sect_num = 6 // Number of section.
935 /// 6 payload.bitmap_indicator = 255 // Bit-map indicator (see Code table 6.0 and the Note).
936 /// ### SECTION 7: DATA SECTION (length = 1391)
937 /// ### SECTION 8: END SECTION (length = 4)
938 /// ";
939 /// assert_eq!(String::from_utf8_lossy(buf.get_ref()), expected);
940 ///
941 /// Ok(())
942 /// }
943 /// ```
944 pub fn dump<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
945 let write_heading =
946 |writer: &mut W, sect: &SectionInfo, sect_name: &str| -> Result<(), std::io::Error> {
947 let SectionInfo { num, size, .. } = sect;
948 let sect_name = sect_name.to_ascii_uppercase();
949 writeln!(writer, "## SECTION {num}: {sect_name} (length = {size})")
950 };
951
952 macro_rules! write_section {
953 ($sect:expr) => {{
954 let mut pos = 1;
955 match $sect {
956 Ok(s) => s.dump(None, &mut pos, writer)?,
957 Err(e) => writeln!(writer, "error: {}", e)?,
958 }
959 }};
960 }
961
962 let total_length = self.indicator().total_length;
963 writeln!(writer, "# SUBMESSAGE (total_length = {total_length})")?;
964 write_heading(writer, self.0.body, "indicator section")?;
965 write_heading(writer, self.1.body, "identification section")?;
966 write_section!(self.section1());
967 if let Some(sect) = &self.2 {
968 write_heading(writer, sect.body, "local use section")?;
969 }
970 write_heading(writer, self.3.body, "grid definition section")?;
971 write_section!(self.section3());
972 write_heading(writer, self.4.body, "product definition section")?;
973 write_section!(self.section4());
974 write_heading(writer, self.5.body, "data representation section")?;
975 write_section!(self.section5());
976 write_heading(writer, self.6.body, "bit-map section")?;
977 write_section!(self.section6());
978 write_heading(writer, self.7.body, "data section")?;
979
980 // Since `self.8.body` might be dummy, we don't use that Section 8 data.
981 writeln!(writer, "## SECTION 8: END SECTION (length = 4)")?;
982
983 Ok(())
984 }
985
986 /// Returns time-related raw information associated with the submessage.
987 ///
988 /// # Examples
989 ///
990 /// ```
991 /// use std::{
992 /// fs::File,
993 /// io::{BufReader, Read},
994 /// };
995 ///
996 /// use grib::{
997 /// Code, ForecastTime, TemporalRawInfo,
998 /// codetables::grib2::{Table1_2, Table4_4},
999 /// def::grib2::RefTime,
1000 /// };
1001 ///
1002 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1003 /// let f = File::open(
1004 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
1005 /// )?;
1006 /// let f = BufReader::new(f);
1007 /// let grib2 = grib::from_reader(f)?;
1008 ///
1009 /// let mut iter = grib2.iter();
1010 ///
1011 /// {
1012 /// let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1013 /// let actual = message.temporal_raw_info();
1014 /// let expected = TemporalRawInfo {
1015 /// ref_time_significance: Code::Name(Table1_2::Analysis),
1016 /// ref_time_unchecked: RefTime::new(2016, 8, 22, 2, 0, 0),
1017 /// forecast_time_diff: Some(ForecastTime {
1018 /// unit: Code::Name(Table4_4::Minute),
1019 /// value: 0,
1020 /// }),
1021 /// };
1022 /// assert_eq!(actual, expected);
1023 /// }
1024 ///
1025 /// {
1026 /// let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
1027 /// let actual = message.temporal_raw_info();
1028 /// let expected = TemporalRawInfo {
1029 /// ref_time_significance: Code::Name(Table1_2::Analysis),
1030 /// ref_time_unchecked: RefTime::new(2016, 8, 22, 2, 0, 0),
1031 /// forecast_time_diff: Some(ForecastTime {
1032 /// unit: Code::Name(Table4_4::Minute),
1033 /// value: 10,
1034 /// }),
1035 /// };
1036 /// assert_eq!(actual, expected);
1037 /// }
1038 ///
1039 /// Ok(())
1040 /// }
1041 /// ```
1042 pub fn temporal_raw_info(&self) -> TemporalRawInfo {
1043 let ref_time_significance = self.identification().ref_time_significance();
1044 let ref_time_unchecked = self.identification().ref_time_unchecked();
1045 let forecast_time = self.prod_def().forecast_time();
1046 TemporalRawInfo::new(ref_time_significance, ref_time_unchecked, forecast_time)
1047 }
1048
1049 #[cfg(feature = "time-calculation")]
1050 #[cfg_attr(docsrs, doc(cfg(feature = "time-calculation")))]
1051 /// Returns time-related calculated information associated with the
1052 /// submessage.
1053 ///
1054 /// # Examples
1055 ///
1056 /// ```
1057 /// use std::{
1058 /// fs::File,
1059 /// io::{BufReader, Read},
1060 /// };
1061 ///
1062 /// use chrono::{TimeZone, Utc};
1063 /// use grib::{
1064 /// Code, ForecastTime, TemporalInfo,
1065 /// codetables::grib2::{Table1_2, Table4_4},
1066 /// };
1067 ///
1068 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1069 /// let f = File::open(
1070 /// "testdata/Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin",
1071 /// )?;
1072 /// let f = BufReader::new(f);
1073 /// let grib2 = grib::from_reader(f)?;
1074 ///
1075 /// let mut iter = grib2.iter();
1076 ///
1077 /// {
1078 /// let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1079 /// let actual = message.temporal_info();
1080 /// let expected = TemporalInfo {
1081 /// ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1082 /// forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1083 /// };
1084 /// assert_eq!(actual, expected);
1085 /// }
1086 ///
1087 /// {
1088 /// let (_, message) = iter.next().ok_or_else(|| "second message is not found")?;
1089 /// let actual = message.temporal_info();
1090 /// let expected = TemporalInfo {
1091 /// ref_time: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 0, 0).unwrap()),
1092 /// forecast_time_target: Some(Utc.with_ymd_and_hms(2016, 8, 22, 2, 10, 0).unwrap()),
1093 /// };
1094 /// assert_eq!(actual, expected);
1095 /// }
1096 ///
1097 /// Ok(())
1098 /// }
1099 /// ```
1100 pub fn temporal_info(&self) -> TemporalInfo {
1101 let raw_info = self.temporal_raw_info();
1102 TemporalInfo::from(&raw_info)
1103 }
1104
1105 /// Returns the shape of the grid, i.e. a tuple of the number of grids in
1106 /// the i and j directions.
1107 ///
1108 /// # Examples
1109 ///
1110 /// ```
1111 /// use std::{
1112 /// fs::File,
1113 /// io::{BufReader, Read},
1114 /// };
1115 ///
1116 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1117 /// let mut buf = Vec::new();
1118 ///
1119 /// let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1120 /// let f = BufReader::new(f);
1121 /// let mut f = xz2::bufread::XzDecoder::new(f);
1122 /// f.read_to_end(&mut buf)?;
1123 ///
1124 /// let f = std::io::Cursor::new(buf);
1125 /// let grib2 = grib::from_reader(f)?;
1126 ///
1127 /// let mut iter = grib2.iter();
1128 /// let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1129 ///
1130 /// let shape = message.grid_shape()?;
1131 /// assert_eq!(shape, (1440, 721));
1132 /// Ok(())
1133 /// }
1134 /// ```
1135 pub fn grid_shape(&self) -> Result<(usize, usize), GribError> {
1136 let grid_def = self.grid_def();
1137 let shape = GridDefinitionTemplateValues::try_from(grid_def)?.grid_shape();
1138 Ok(shape)
1139 }
1140
1141 /// Computes and returns an iterator over `(i, j)` of grid points.
1142 ///
1143 /// The order of items is the same as the order of the grid point values,
1144 /// defined by the scanning mode
1145 /// ([`ScanningMode`](`crate::def::grib2::template::param_set::ScanningMode`))
1146 /// in the data.
1147 ///
1148 /// This iterator allows users to perform their own coordinate calculations
1149 /// for unsupported grid systems and map the results to grid point values.
1150 ///
1151 /// # Examples
1152 ///
1153 /// ```
1154 /// use std::{
1155 /// fs::File,
1156 /// io::{BufReader, Read},
1157 /// };
1158 ///
1159 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1160 /// let mut buf = Vec::new();
1161 ///
1162 /// let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1163 /// let f = BufReader::new(f);
1164 /// let mut f = xz2::bufread::XzDecoder::new(f);
1165 /// f.read_to_end(&mut buf)?;
1166 ///
1167 /// let f = std::io::Cursor::new(buf);
1168 /// let grib2 = grib::from_reader(f)?;
1169 ///
1170 /// let mut iter = grib2.iter();
1171 /// let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1172 ///
1173 /// let mut latlons = message.ij()?;
1174 /// assert_eq!(latlons.next(), Some((0, 0)));
1175 /// assert_eq!(latlons.next(), Some((1, 0)));
1176 /// Ok(())
1177 /// }
1178 /// ```
1179 pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
1180 let grid_def = self.grid_def();
1181 let num_defined = grid_def.num_points() as usize;
1182 let ij = GridDefinitionTemplateValues::try_from(grid_def)?.ij()?;
1183 let (num_decoded, _) = ij.size_hint();
1184 if num_defined == num_decoded {
1185 Ok(ij)
1186 } else {
1187 Err(GribError::InvalidValueError(format!(
1188 "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1189 )))
1190 }
1191 }
1192}
1193
1194impl<'s, R> LatLons for SubMessage<'s, R> {
1195 type Iter<'a>
1196 = GridPointLatLons
1197 where
1198 Self: 'a;
1199
1200 /// Computes and returns an iterator over latitudes and longitudes of grid
1201 /// points in degrees. [Read more](`crate::LatLons::latlons`)
1202 ///
1203 /// # Examples
1204 ///
1205 /// ```
1206 /// use std::{
1207 /// fs::File,
1208 /// io::{BufReader, Read},
1209 /// };
1210 ///
1211 /// use grib::LatLons;
1212 ///
1213 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1214 /// let mut buf = Vec::new();
1215 ///
1216 /// let f = File::open("testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz")?;
1217 /// let f = BufReader::new(f);
1218 /// let mut f = xz2::bufread::XzDecoder::new(f);
1219 /// f.read_to_end(&mut buf)?;
1220 ///
1221 /// let f = std::io::Cursor::new(buf);
1222 /// let grib2 = grib::from_reader(f)?;
1223 ///
1224 /// let mut iter = grib2.iter();
1225 /// let (_, message) = iter.next().ok_or_else(|| "first message is not found")?;
1226 ///
1227 /// let mut latlons = message.latlons()?;
1228 /// assert_eq!(latlons.next(), Some((90.0, 0.0)));
1229 /// assert_eq!(latlons.next(), Some((90.0, 0.25)));
1230 /// Ok(())
1231 /// }
1232 /// ```
1233 fn latlons_unchecked<'a>(&'a self) -> Result<Self::Iter<'a>, GribError> {
1234 let grid_def = self.grid_def();
1235 let num_defined = grid_def.num_points() as usize;
1236 let latlons = GridDefinitionTemplateValues::try_from(grid_def)?.latlons_unchecked()?;
1237 let (num_decoded, _) = latlons.size_hint();
1238 if num_defined == num_decoded {
1239 Ok(latlons)
1240 } else {
1241 Err(GribError::InvalidValueError(format!(
1242 "number of grid points does not match: {num_defined} (defined) vs {num_decoded} (decoded)"
1243 )))
1244 }
1245 }
1246}
1247
1248pub struct SubMessageSection<'a> {
1249 pub index: usize,
1250 pub body: &'a SectionInfo,
1251}
1252
1253impl<'a> SubMessageSection<'a> {
1254 pub fn new(index: usize, body: &'a SectionInfo) -> Self {
1255 Self { index, body }
1256 }
1257
1258 pub fn template_code(&self) -> Option<TemplateInfo> {
1259 self.body.get_tmpl_code()
1260 }
1261
1262 pub fn describe(&self) -> Option<String> {
1263 self.template_code().and_then(|code| code.describe())
1264 }
1265}
1266
1267#[cfg(test)]
1268mod tests {
1269 use std::{fs::File, io::BufReader};
1270
1271 use super::*;
1272
1273 macro_rules! sect_placeholder {
1274 ($num:expr) => {{
1275 SectionInfo {
1276 num: $num,
1277 offset: 0,
1278 size: 0,
1279 body: None,
1280 }
1281 }};
1282 }
1283
1284 #[test]
1285 fn context_from_buf_reader() {
1286 let f = File::open(
1287 "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1288 )
1289 .unwrap();
1290 let f = BufReader::new(f);
1291 let result = from_reader(f);
1292 assert!(result.is_ok())
1293 }
1294
1295 #[test]
1296 fn context_from_bytes() {
1297 let f = File::open(
1298 "testdata/icon_global_icosahedral_single-level_2021112018_000_TOT_PREC.grib2",
1299 )
1300 .unwrap();
1301 let mut f = BufReader::new(f);
1302 let mut buf = Vec::new();
1303 f.read_to_end(&mut buf).unwrap();
1304 let result = from_bytes(&buf);
1305 assert!(result.is_ok())
1306 }
1307
1308 #[test]
1309 fn get_tmpl_code_normal() {
1310 let sect = SectionInfo {
1311 num: 5,
1312 offset: 8902,
1313 size: 23,
1314 body: Some(SectionBody::Section5(
1315 ReprDefinition::from_payload(
1316 vec![0x00, 0x01, 0x50, 0x00, 0x00, 0xc8].into_boxed_slice(),
1317 )
1318 .unwrap(),
1319 )),
1320 };
1321
1322 assert_eq!(sect.get_tmpl_code(), Some(TemplateInfo(5, 200)));
1323 }
1324
1325 #[test]
1326 fn get_templates_normal() {
1327 let sects = vec![
1328 sect_placeholder!(0),
1329 sect_placeholder!(1),
1330 SectionInfo {
1331 num: 3,
1332 offset: 0,
1333 size: 0,
1334 body: Some(SectionBody::Section3(
1335 GridDefinition::from_payload(vec![0; 9].into_boxed_slice()).unwrap(),
1336 )),
1337 },
1338 SectionInfo {
1339 num: 4,
1340 offset: 0,
1341 size: 0,
1342 body: Some(SectionBody::Section4(
1343 ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1344 )),
1345 },
1346 SectionInfo {
1347 num: 5,
1348 offset: 0,
1349 size: 0,
1350 body: Some(SectionBody::Section5(
1351 ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1352 )),
1353 },
1354 sect_placeholder!(6),
1355 sect_placeholder!(7),
1356 SectionInfo {
1357 num: 3,
1358 offset: 0,
1359 size: 0,
1360 body: Some(SectionBody::Section3(
1361 GridDefinition::from_payload(
1362 vec![0, 0, 0, 0, 0, 0, 0, 0, 1].into_boxed_slice(),
1363 )
1364 .unwrap(),
1365 )),
1366 },
1367 SectionInfo {
1368 num: 4,
1369 offset: 0,
1370 size: 0,
1371 body: Some(SectionBody::Section4(
1372 ProdDefinition::from_payload(vec![0; 4].into_boxed_slice()).unwrap(),
1373 )),
1374 },
1375 SectionInfo {
1376 num: 5,
1377 offset: 0,
1378 size: 0,
1379 body: Some(SectionBody::Section5(
1380 ReprDefinition::from_payload(vec![0; 6].into_boxed_slice()).unwrap(),
1381 )),
1382 },
1383 sect_placeholder!(6),
1384 sect_placeholder!(7),
1385 sect_placeholder!(8),
1386 ]
1387 .into_boxed_slice();
1388
1389 assert_eq!(
1390 get_templates(§s),
1391 vec![
1392 TemplateInfo(3, 0),
1393 TemplateInfo(3, 1),
1394 TemplateInfo(4, 0),
1395 TemplateInfo(5, 0),
1396 ]
1397 );
1398 }
1399
1400 macro_rules! test_submessage_iterator {
1401 ($((
1402 $name:ident,
1403 $xz_compressed_input:expr,
1404 $nth:expr,
1405 $expected_index:expr,
1406 $expected_section_indices:expr,
1407 ),)*) => ($(
1408 #[test]
1409 fn $name() -> Result<(), Box<dyn std::error::Error>> {
1410 let mut buf = Vec::new();
1411
1412 let f = File::open($xz_compressed_input)?;
1413 let f = BufReader::new(f);
1414 let mut f = xz2::bufread::XzDecoder::new(f);
1415 f.read_to_end(&mut buf)?;
1416
1417 let f = Cursor::new(buf);
1418 let grib2 = crate::from_reader(f)?;
1419 let mut iter = grib2.iter();
1420
1421 let (actual_index, message) = iter.nth($nth).ok_or_else(|| "item not available")?;
1422 assert_eq!(actual_index, $expected_index);
1423 let actual_section_indices = get_section_indices(message);
1424 assert_eq!(actual_section_indices, $expected_section_indices);
1425
1426 Ok(())
1427 }
1428 )*);
1429 }
1430
1431 test_submessage_iterator! {
1432 (
1433 item_0_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1434 "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1435 0,
1436 (0, 0),
1437 (0, 1, None, 2, 3, 4, 5, 6, 0),
1438 ),
1439 (
1440 item_1_from_submessage_iterator_for_single_message_data_with_multiple_submessages,
1441 "testdata/Z__C_RJTD_20190304000000_MSM_GUID_Rjp_P-all_FH03-39_Toorg_grib2.bin.xz",
1442 1,
1443 (0, 1),
1444 (0, 1, None, 2, 7, 8, 9, 10, 0),
1445 ),
1446 (
1447 item_0_from_submessage_iterator_for_multi_message_data,
1448 "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1449 0,
1450 (0, 0),
1451 (0, 1, None, 2, 3, 4, 5, 6, 7),
1452 ),
1453 (
1454 item_1_from_submessage_iterator_for_multi_message_data,
1455 "testdata/gdas.t12z.pgrb2.0p25.f000.0-10.xz",
1456 1,
1457 (1, 0),
1458 (8, 9, None, 10, 11, 12, 13, 14, 15),
1459 ),
1460 }
1461
1462 fn get_section_indices<R>(
1463 submessage: SubMessage<'_, R>,
1464 ) -> (
1465 usize,
1466 usize,
1467 Option<usize>,
1468 usize,
1469 usize,
1470 usize,
1471 usize,
1472 usize,
1473 usize,
1474 ) {
1475 (
1476 submessage.0.index,
1477 submessage.1.index,
1478 submessage.2.map(|s| s.index),
1479 submessage.3.index,
1480 submessage.4.index,
1481 submessage.5.index,
1482 submessage.6.index,
1483 submessage.7.index,
1484 submessage.8.index,
1485 )
1486 }
1487}