grib/datatypes/
product_attributes.rs

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