Skip to main content

grib/datatypes/
product_attributes.rs

1use std::fmt::{self, Display, Formatter};
2
3use grib_template_derive::{Dump, TryFromSlice, WriteToBuffer};
4
5use crate::{
6    MissingValue,
7    codetables::{grib2::*, *},
8};
9
10/// Parameter of the product.
11///
12/// In the context of GRIB products, parameters refer to weather elements such
13/// as air temperature, air pressure, and humidity, and other physical
14/// quantities.
15///
16/// With [`is_identical_to`], users can check if the parameter is identical to a
17/// third-party code, such as [`NCEP`].
18///
19/// [`is_identical_to`]: Parameter::is_identical_to
20#[derive(Debug, PartialEq, Eq)]
21pub struct Parameter {
22    /// Discipline of processed data in the GRIB message.
23    pub discipline: u8,
24    /// GRIB master tables version number.
25    pub centre: u16,
26    /// Parameter category by product discipline.
27    pub master_ver: u8,
28    /// GRIB local tables version number.
29    pub local_ver: u8,
30    /// Identification of originating/generating centre.
31    pub category: u8,
32    /// Parameter number by product discipline and parameter category.
33    pub num: u8,
34}
35
36impl Parameter {
37    /// Looks up the parameter's WMO description.
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// // Extracted from the first submessage of JMA MSM GRIB2 data.
43    /// let param = grib::Parameter {
44    ///     discipline: 0,
45    ///     centre: 34,
46    ///     master_ver: 2,
47    ///     local_ver: 1,
48    ///     category: 3,
49    ///     num: 5,
50    /// };
51    /// assert_eq!(param.description(), Some("Geopotential height".to_owned()))
52    /// ```
53    pub fn description(&self) -> Option<String> {
54        CodeTable4_2::new(self.discipline, self.category)
55            .lookup(usize::from(self.num))
56            .description()
57    }
58
59    /// Checks if the parameter is identical to a third-party `code`, such as
60    /// [`NCEP`].
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use grib::codetables::NCEP;
66    ///
67    /// // Extracted from the first submessage of JMA MSM GRIB2 data.
68    /// let param = grib::Parameter {
69    ///     discipline: 0,
70    ///     centre: 34,
71    ///     master_ver: 2,
72    ///     local_ver: 1,
73    ///     category: 3,
74    ///     num: 5,
75    /// };
76    /// assert!(param.is_identical_to(NCEP::HGT));
77    /// ```
78    pub fn is_identical_to<'a, T>(&'a self, code: T) -> bool
79    where
80        T: TryFrom<&'a Self>,
81        T: PartialEq,
82    {
83        let self_ = T::try_from(self);
84        self_.is_ok_and(|v| v == code)
85    }
86
87    pub(crate) fn as_u32(&self) -> u32 {
88        (u32::from(self.discipline) << 16) + (u32::from(self.category) << 8) + u32::from(self.num)
89    }
90}
91
92#[derive(Debug, PartialEq, Eq)]
93pub struct ForecastTime {
94    pub unit: Code<grib2::Table4_4, u8>,
95    pub value: u32,
96}
97
98impl ForecastTime {
99    pub fn new(unit: Code<grib2::Table4_4, u8>, value: u32) -> Self {
100        Self { unit, value }
101    }
102
103    pub fn from_numbers(unit: u8, value: u32) -> Self {
104        let unit = Table4_4::try_from(unit).into();
105        Self { unit, value }
106    }
107
108    pub fn describe(&self) -> (String, String) {
109        let unit = match &self.unit {
110            Name(unit) => format!("{unit:#?}"),
111            Num(num) => format!("code {num:#?}"),
112        };
113        let value = self.value.to_string();
114        (unit, value)
115    }
116}
117
118impl Display for ForecastTime {
119    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
120        write!(f, "{}", self.value)?;
121
122        match &self.unit {
123            Name(unit) => {
124                if let Some(expr) = unit.short_expr() {
125                    write!(f, " [{expr}]")?;
126                }
127            }
128            Num(num) => {
129                write!(f, " [unit: {num}]")?;
130            }
131        }
132
133        Ok(())
134    }
135}
136
137#[derive(Debug, PartialEq, Eq, TryFromSlice, WriteToBuffer, Dump)]
138pub struct FixedSurface {
139    /// Use [CodeTable4_5] to get textual representation.
140    pub surface_type: u8,
141    pub scale_factor: i8,
142    pub scaled_value: i32,
143}
144
145impl FixedSurface {
146    pub fn new(surface_type: u8, scale_factor: i8, scaled_value: i32) -> Self {
147        Self {
148            surface_type,
149            scale_factor,
150            scaled_value,
151        }
152    }
153
154    pub fn value(&self) -> f64 {
155        if self.scaled_value.is_missing() {
156            f64::NAN
157        } else {
158            let factor: f64 = 10_f64.powi(-i32::from(self.scale_factor));
159            f64::from(self.scaled_value) * factor
160        }
161    }
162
163    /// Returns the unit string defined for the type of the surface, if any.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// assert_eq!(grib::FixedSurface::new(100, 0, 0).unit(), Some("Pa"));
169    /// ```
170    pub fn unit(&self) -> Option<&str> {
171        // Tentative implementation; pattern matching should be generated from the
172        // CodeFlag CSV file.
173        let unit = match self.surface_type {
174            11 => "m",
175            12 => "m",
176            13 => "%",
177            18 => "Pa",
178            20 => "K",
179            21 => "kg m-3",
180            22 => "kg m-3",
181            23 => "Bq m-3",
182            24 => "Bq m-3",
183            25 => "dBZ",
184            26 => "m",
185            27 => "m",
186            30 => "m",
187            100 => "Pa",
188            102 => "m",
189            103 => "m",
190            104 => r#""sigma" value"#,
191            106 => "m",
192            107 => "K",
193            108 => "Pa",
194            109 => "K m2 kg-1 s-1",
195            114 => "Numeric",
196            117 => "m",
197            151 => "Numeric",
198            152 => "Numeric",
199            160 => "m",
200            161 => "m",
201            168 => "Numeric",
202            169 => "kg m-3",
203            170 => "K",
204            171 => "m2 s-1",
205            _ => return None,
206        };
207        Some(unit)
208    }
209
210    pub fn describe(&self) -> (String, String, String) {
211        let stype = CodeTable4_5
212            .lookup(usize::from(self.surface_type))
213            .to_string();
214        let scale_factor = if self.scale_factor.is_missing() {
215            "Missing".to_owned()
216        } else {
217            self.scale_factor.to_string()
218        };
219        let scaled_value = if self.scaled_value.is_missing() {
220            "Missing".to_owned()
221        } else {
222            self.scaled_value.to_string()
223        };
224        (stype, scale_factor, scaled_value)
225    }
226}