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