grib/datatypes/
sections.rs

1use std::slice::Iter;
2
3use chrono::{DateTime, LocalResult, TimeZone, Utc};
4
5use crate::{
6    codetables::SUPPORTED_PROD_DEF_TEMPLATE_NUMBERS,
7    datatypes::*,
8    error::*,
9    grid::{
10        GaussianGridDefinition, GridPointIterator, LambertGridDefinition, LatLonGridDefinition,
11    },
12    helpers::{read_as, GribInt},
13    GridPointIndexIterator, PolarStereographicGridDefinition,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Indicator {
18    /// Discipline - GRIB Master Table Number (see Code Table 0.0)
19    pub discipline: u8,
20    /// Total length of GRIB message in octets (including Section 0)
21    pub total_length: u64,
22}
23
24impl Indicator {
25    pub(crate) fn from_slice(slice: &[u8]) -> Result<Self, ParseError> {
26        let discipline = slice[6];
27        let version = slice[7];
28        if version != 2 {
29            return Err(ParseError::GRIBVersionMismatch(version));
30        }
31
32        let total_length = read_as!(u64, slice, 8);
33
34        Ok(Self {
35            discipline,
36            total_length,
37        })
38    }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Identification {
43    payload: Box<[u8]>,
44}
45
46impl Identification {
47    pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
48        let size = slice.len();
49        if size < 16 {
50            Err(BuildError::SectionSizeTooSmall(size))
51        } else {
52            Ok(Self { payload: slice })
53        }
54    }
55
56    pub fn iter(&self) -> Iter<'_, u8> {
57        self.payload.iter()
58    }
59
60    /// Identification of originating/generating centre (see Common Code Table
61    /// C-1)
62    #[inline]
63    pub fn centre_id(&self) -> u16 {
64        let payload = &self.payload;
65        read_as!(u16, payload, 0)
66    }
67
68    /// Identification of originating/generating sub-centre (allocated by
69    /// originating/ generating centre)
70    #[inline]
71    pub fn subcentre_id(&self) -> u16 {
72        let payload = &self.payload;
73        read_as!(u16, payload, 2)
74    }
75
76    /// GRIB Master Tables Version Number (see Code Table 1.0)
77    #[inline]
78    pub fn master_table_version(&self) -> u8 {
79        self.payload[4]
80    }
81
82    /// GRIB Local Tables Version Number (see Code Table 1.1)
83    #[inline]
84    pub fn local_table_version(&self) -> u8 {
85        self.payload[5]
86    }
87
88    /// Significance of Reference Time (see Code Table 1.2)
89    #[inline]
90    pub fn ref_time_significance(&self) -> u8 {
91        self.payload[6]
92    }
93
94    /// Reference time of data
95    pub fn ref_time(&self) -> Result<DateTime<Utc>, GribError> {
96        let payload = &self.payload;
97        create_date_time(
98            read_as!(u16, payload, 7).into(),
99            self.payload[9].into(),
100            self.payload[10].into(),
101            self.payload[11].into(),
102            self.payload[12].into(),
103            self.payload[13].into(),
104        )
105    }
106
107    /// Production status of processed data in this GRIB message
108    /// (see Code Table 1.3)
109    #[inline]
110    pub fn prod_status(&self) -> u8 {
111        self.payload[14]
112    }
113
114    /// Type of processed data in this GRIB message (see Code Table 1.4)
115    #[inline]
116    pub fn data_type(&self) -> u8 {
117        self.payload[15]
118    }
119}
120
121#[inline]
122fn create_date_time(
123    year: i32,
124    month: u32,
125    day: u32,
126    hour: u32,
127    minute: u32,
128    second: u32,
129) -> Result<DateTime<Utc>, GribError> {
130    let result = Utc.with_ymd_and_hms(year, month, day, hour, minute, second);
131    if let LocalResult::None = result {
132        Err(GribError::InvalidValueError(format!(
133            "invalid date time: {year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}"
134        )))
135    } else {
136        Ok(result.unwrap())
137    }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct LocalUse {
142    payload: Box<[u8]>,
143}
144
145impl LocalUse {
146    pub fn from_payload(slice: Box<[u8]>) -> Self {
147        Self { payload: slice }
148    }
149
150    pub fn iter(&self) -> Iter<'_, u8> {
151        self.payload.iter()
152    }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Hash)]
156pub struct GridDefinition {
157    payload: Box<[u8]>,
158}
159
160impl GridDefinition {
161    pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
162        let size = slice.len();
163        if size < 9 {
164            Err(BuildError::SectionSizeTooSmall(size))
165        } else {
166            Ok(Self { payload: slice })
167        }
168    }
169
170    pub fn iter(&self) -> Iter<'_, u8> {
171        self.payload.iter()
172    }
173
174    /// Number of data points
175    pub fn num_points(&self) -> u32 {
176        let payload = &self.payload;
177        read_as!(u32, payload, 1)
178    }
179
180    /// Grid Definition Template Number
181    pub fn grid_tmpl_num(&self) -> u16 {
182        let payload = &self.payload;
183        read_as!(u16, payload, 7)
184    }
185}
186
187#[derive(Debug, PartialEq, Eq)]
188pub enum GridDefinitionTemplateValues {
189    Template0(LatLonGridDefinition),
190    Template20(PolarStereographicGridDefinition),
191    Template30(LambertGridDefinition),
192    Template40(GaussianGridDefinition),
193}
194
195impl GridDefinitionTemplateValues {
196    /// Returns the shape of the grid, i.e. a tuple of the number of grids in
197    /// the i and j directions.
198    pub fn grid_shape(&self) -> (usize, usize) {
199        match self {
200            Self::Template0(def) => def.grid_shape(),
201            Self::Template20(def) => def.grid_shape(),
202            Self::Template30(def) => def.grid_shape(),
203            Self::Template40(def) => def.grid_shape(),
204        }
205    }
206
207    /// Returns the grid type.
208    ///
209    /// The grid types are denoted as short strings based on `gridType` used in
210    /// ecCodes.
211    ///
212    /// This is provided primarily for debugging and simple notation purposes.
213    /// It is better to use enum variants instead of the string notation to
214    /// determine the grid type.
215    pub fn short_name(&self) -> &'static str {
216        match self {
217            Self::Template0(def) => def.short_name(),
218            Self::Template20(def) => def.short_name(),
219            Self::Template30(def) => def.short_name(),
220            Self::Template40(def) => def.short_name(),
221        }
222    }
223
224    /// Returns an iterator over `(i, j)` of grid points.
225    ///
226    /// Note that this is a low-level API and it is not checked that the number
227    /// of iterator iterations is consistent with the number of grid points
228    /// defined in the data.
229    pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
230        match self {
231            Self::Template0(def) => def.ij(),
232            Self::Template20(def) => def.ij(),
233            Self::Template30(def) => def.ij(),
234            Self::Template40(def) => def.ij(),
235        }
236    }
237
238    /// Returns an iterator over latitudes and longitudes of grid points in
239    /// degrees.
240    ///
241    /// Note that this is a low-level API and it is not checked that the number
242    /// of iterator iterations is consistent with the number of grid points
243    /// defined in the data.
244    pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
245        let iter = match self {
246            Self::Template0(def) => GridPointIterator::LatLon(def.latlons()?),
247            #[cfg(feature = "gridpoints-proj")]
248            Self::Template20(def) => GridPointIterator::Lambert(def.latlons()?),
249            #[cfg(feature = "gridpoints-proj")]
250            Self::Template30(def) => GridPointIterator::Lambert(def.latlons()?),
251            Self::Template40(def) => GridPointIterator::LatLon(def.latlons()?),
252            #[cfg(not(feature = "gridpoints-proj"))]
253            _ => {
254                return Err(GribError::NotSupported(
255                    "lat/lon computation support for the template is dropped in this build"
256                        .to_owned(),
257                ))
258            }
259        };
260        Ok(iter)
261    }
262}
263
264impl TryFrom<&GridDefinition> for GridDefinitionTemplateValues {
265    type Error = GribError;
266
267    fn try_from(value: &GridDefinition) -> Result<Self, Self::Error> {
268        let num = value.grid_tmpl_num();
269        match num {
270            0 => {
271                let buf = &value.payload;
272                if buf.len() > 67 {
273                    return Err(GribError::NotSupported(format!(
274                        "template {num} with list of number of points"
275                    )));
276                }
277                Ok(GridDefinitionTemplateValues::Template0(
278                    LatLonGridDefinition::from_buf(&buf[25..]),
279                ))
280            }
281            20 => {
282                let buf = &value.payload;
283                Ok(GridDefinitionTemplateValues::Template20(
284                    PolarStereographicGridDefinition::from_buf(&buf[9..]),
285                ))
286            }
287            30 => {
288                let buf = &value.payload;
289                Ok(GridDefinitionTemplateValues::Template30(
290                    LambertGridDefinition::from_buf(&buf[9..]),
291                ))
292            }
293            40 => {
294                let buf = &value.payload;
295                if buf.len() > 67 {
296                    return Err(GribError::NotSupported(format!(
297                        "template {num} with list of number of points"
298                    )));
299                }
300                Ok(GridDefinitionTemplateValues::Template40(
301                    GaussianGridDefinition::from_buf(&buf[25..]),
302                ))
303            }
304            _ => Err(GribError::NotSupported(format!("template {num}"))),
305        }
306    }
307}
308
309const START_OF_PROD_TEMPLATE: usize = 4;
310
311#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub struct ProdDefinition {
313    payload: Box<[u8]>,
314}
315
316impl ProdDefinition {
317    pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
318        let size = slice.len();
319        if size < START_OF_PROD_TEMPLATE {
320            Err(BuildError::SectionSizeTooSmall(size))
321        } else {
322            Ok(Self { payload: slice })
323        }
324    }
325
326    pub fn iter(&self) -> Iter<'_, u8> {
327        self.payload.iter()
328    }
329
330    /// Number of coordinate values after Template
331    pub fn num_coordinates(&self) -> u16 {
332        let payload = &self.payload;
333        read_as!(u16, payload, 0)
334    }
335
336    /// Product Definition Template Number
337    pub fn prod_tmpl_num(&self) -> u16 {
338        let payload = &self.payload;
339        read_as!(u16, payload, 2)
340    }
341
342    // pub(crate) templated(&self)-> Box<[u8]> {
343
344    // }
345
346    pub(crate) fn template_supported(&self) -> bool {
347        SUPPORTED_PROD_DEF_TEMPLATE_NUMBERS.contains(&self.prod_tmpl_num())
348    }
349
350    /// Use [CodeTable4_1](crate::codetables::CodeTable4_1) to get textual
351    /// representation of the returned numerical value.
352    pub fn parameter_category(&self) -> Option<u8> {
353        if self.template_supported() {
354            self.payload.get(START_OF_PROD_TEMPLATE).copied()
355        } else {
356            None
357        }
358    }
359
360    /// Use [CodeTable4_2](crate::codetables::CodeTable4_2) to get textual
361    /// representation of the returned numerical value.
362    pub fn parameter_number(&self) -> Option<u8> {
363        if self.template_supported() {
364            self.payload.get(START_OF_PROD_TEMPLATE + 1).copied()
365        } else {
366            None
367        }
368    }
369
370    /// Use [CodeTable4_3](crate::codetables::CodeTable4_3) to get textual
371    /// representation of the returned numerical value.
372    pub fn generating_process(&self) -> Option<u8> {
373        if self.template_supported() {
374            let index = match self.prod_tmpl_num() {
375                0..=39 => Some(2),
376                40..=43 => Some(4),
377                44..=46 => Some(15),
378                47 => Some(2),
379                48..=49 => Some(26),
380                51 => Some(2),
381                // 53 and 54 is variable and not supported as of now
382                55..=56 => Some(8),
383                // 57 and 58 is variable and not supported as of now
384                59 => Some(8),
385                60..=61 => Some(2),
386                62..=63 => Some(8),
387                // 67 and 68 is variable and not supported as of now
388                70..=73 => Some(7),
389                76..=79 => Some(5),
390                80..=81 => Some(27),
391                82 => Some(16),
392                83 => Some(2),
393                84 => Some(16),
394                85 => Some(15),
395                86..=91 => Some(2),
396                254 => Some(2),
397                1000..=1101 => Some(2),
398                _ => None,
399            }?;
400            self.payload.get(START_OF_PROD_TEMPLATE + index).copied()
401        } else {
402            None
403        }
404    }
405
406    /// Returns the unit and value of the forecast time wrapped by `Option`.
407    /// Use [CodeTable4_4](crate::codetables::CodeTable4_4) to get textual
408    /// representation of the unit.
409    pub fn forecast_time(&self) -> Option<ForecastTime> {
410        if self.template_supported() {
411            let unit_index = match self.prod_tmpl_num() {
412                0..=15 => Some(8),
413                32..=34 => Some(8),
414                40..=43 => Some(10),
415                44..=47 => Some(21),
416                48..=49 => Some(32),
417                51 => Some(8),
418                // 53 and 54 is variable and not supported as of now
419                55..=56 => Some(14),
420                // 57 and 58 is variable and not supported as of now
421                59 => Some(14),
422                60..=61 => Some(8),
423                62..=63 => Some(14),
424                // 67 and 68 is variable and not supported as of now
425                70..=73 => Some(13),
426                76..=79 => Some(11),
427                80..=81 => Some(33),
428                82..=84 => Some(22),
429                85 => Some(21),
430                86..=87 => Some(8),
431                88 => Some(26),
432                91 => Some(8),
433                1000..=1101 => Some(8),
434                _ => None,
435            }?;
436            let unit_index = START_OF_PROD_TEMPLATE + unit_index;
437            let unit = self.payload.get(unit_index).copied();
438            let start = unit_index + 1;
439            let end = unit_index + 5;
440            let time = u32::from_be_bytes(self.payload[start..end].try_into().unwrap());
441            unit.map(|v| ForecastTime::from_numbers(v, time))
442        } else {
443            None
444        }
445    }
446
447    /// Returns a tuple of two [FixedSurface], wrapped by `Option`.
448    pub fn fixed_surfaces(&self) -> Option<(FixedSurface, FixedSurface)> {
449        if self.template_supported() {
450            let index = match self.prod_tmpl_num() {
451                0..=15 => Some(13),
452                40..=43 => Some(15),
453                44 => Some(24),
454                45..=47 => Some(26),
455                48..=49 => Some(37),
456                51 => Some(13),
457                // 53 and 54 is variable and not supported as of now
458                55..=56 => Some(19),
459                // 57 and 58 is variable and not supported as of now
460                59 => Some(19),
461                60..=61 => Some(13),
462                62..=63 => Some(19),
463                // 67 and 68 is variable and not supported as of now
464                70..=73 => Some(18),
465                76..=79 => Some(16),
466                80..=81 => Some(38),
467                82..=84 => Some(27),
468                85 => Some(26),
469                86..=87 => Some(13),
470                88 => Some(5),
471                91 => Some(13),
472                1100..=1101 => Some(13),
473                _ => None,
474            }?;
475
476            let first_surface = self.read_surface_from(index);
477            let second_surface = self.read_surface_from(index + 6);
478            first_surface.zip(second_surface)
479        } else {
480            None
481        }
482    }
483
484    fn read_surface_from(&self, index: usize) -> Option<FixedSurface> {
485        let index = START_OF_PROD_TEMPLATE + index;
486        let surface_type = self.payload.get(index).copied();
487        let scale_factor = self.payload.get(index + 1).map(|v| (*v).as_grib_int());
488        let start = index + 2;
489        let end = index + 6;
490        let scaled_value =
491            u32::from_be_bytes(self.payload[start..end].try_into().unwrap()).as_grib_int();
492        surface_type
493            .zip(scale_factor)
494            .map(|(stype, factor)| FixedSurface::new(stype, factor, scaled_value))
495    }
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Hash)]
499pub struct ReprDefinition {
500    payload: Box<[u8]>,
501}
502
503impl ReprDefinition {
504    pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
505        let size = slice.len();
506        if size < 6 {
507            Err(BuildError::SectionSizeTooSmall(size))
508        } else {
509            Ok(Self { payload: slice })
510        }
511    }
512
513    pub fn iter(&self) -> Iter<'_, u8> {
514        self.payload.iter()
515    }
516
517    /// Number of data points where one or more values are
518    /// specified in Section 7 when a bit map is present, total
519    /// number of data points when a bit map is absent
520    pub fn num_points(&self) -> u32 {
521        let payload = &self.payload;
522        read_as!(u32, payload, 0)
523    }
524
525    /// Data Representation Template Number
526    pub fn repr_tmpl_num(&self) -> u16 {
527        let payload = &self.payload;
528        read_as!(u16, payload, 4)
529    }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash)]
533pub struct BitMap {
534    /// Bit-map indicator
535    pub bitmap_indicator: u8,
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    macro_rules! test_date_time_creation {
543        ($((
544            $name:ident,
545            $year:expr,
546            $month:expr,
547            $day:expr,
548            $hour:expr,
549            $minute:expr,
550            $second:expr,
551            $ok_expected:expr
552        ),)*) => ($(
553            #[test]
554            fn $name() {
555                let result = create_date_time($year, $month, $day, $hour, $minute, $second);
556                assert_eq!(result.is_ok(), $ok_expected);
557            }
558        )*);
559    }
560
561    test_date_time_creation! {
562        (date_time_creation_for_valid_date_time, 2022, 1, 1, 0, 0, 0, true),
563        (date_time_creation_for_invalid_date, 2022, 11, 31, 0, 0, 0, false),
564        (date_time_creation_for_invalid_time, 2022, 1, 1, 0, 61, 0, false),
565    }
566
567    #[test]
568    fn error_in_date_time_creation() {
569        let result = create_date_time(2022, 11, 31, 0, 0, 0);
570        assert_eq!(
571            result,
572            Err(GribError::InvalidValueError(
573                "invalid date time: 2022-11-31 00:00:00".to_owned()
574            ))
575        );
576    }
577
578    #[test]
579    fn grid_definition_template_0() {
580        // data taken from submessage #0.0 of
581        // `Z__C_RJTD_20160822020000_NOWC_GPV_Ggis10km_Pphw10_FH0000-0100_grib2.bin.xz`
582        // in `testdata`
583        let data = GridDefinition::from_payload(
584            vec![
585                0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff,
586                0xff, 0x01, 0x03, 0xcd, 0x39, 0xfa, 0x01, 0x03, 0xc9, 0xf6, 0xa3, 0x00, 0x00, 0x01,
587                0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x02,
588                0xdb, 0xc9, 0x3d, 0x07, 0x09, 0x7d, 0xa4, 0x30, 0x01, 0x31, 0xcf, 0xc3, 0x08, 0xef,
589                0xdd, 0x5c, 0x00, 0x01, 0xe8, 0x48, 0x00, 0x01, 0x45, 0x85, 0x00,
590            ]
591            .into_boxed_slice(),
592        )
593        .unwrap();
594
595        let actual = GridDefinitionTemplateValues::try_from(&data).unwrap();
596        let expected = GridDefinitionTemplateValues::Template0(LatLonGridDefinition {
597            ni: 256,
598            nj: 336,
599            first_point_lat: 47958333,
600            first_point_lon: 118062500,
601            last_point_lat: 20041667,
602            last_point_lon: 149937500,
603            scanning_mode: crate::grid::ScanningMode(0b00000000),
604        });
605        assert_eq!(actual, expected);
606    }
607
608    #[test]
609    fn prod_definition_parameters() {
610        let data = ProdDefinition::from_payload(
611            vec![
612                0, 0, 0, 0, 193, 0, 2, 153, 255, 0, 0, 0, 0, 0, 0, 0, 40, 1, 255, 255, 255, 255,
613                255, 255, 255, 255, 255, 255, 255,
614            ]
615            .into_boxed_slice(),
616        )
617        .unwrap();
618
619        assert_eq!(data.parameter_category(), Some(193));
620        assert_eq!(data.parameter_number(), Some(0));
621        assert_eq!(
622            data.forecast_time(),
623            Some(ForecastTime::from_numbers(0, 40))
624        );
625        assert_eq!(
626            data.fixed_surfaces(),
627            Some((
628                FixedSurface::new(1, -127, -2147483647),
629                FixedSurface::new(255, -127, -2147483647)
630            ))
631        );
632    }
633}