Skip to main content

oxigdal_grib/
parameter.rs

1//! Parameter tables for GRIB1 and GRIB2 formats.
2//!
3//! This module provides parameter definitions and lookups for WMO standard tables,
4//! including meteorological variables like temperature, pressure, wind, precipitation, etc.
5
6use crate::error::{GribError, Result};
7use serde::{Deserialize, Serialize};
8
9/// GRIB parameter definition
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct Parameter {
12    /// Parameter short name (e.g., "TMP", "UGRD", "VGRD")
13    pub short_name: String,
14    /// Parameter long name (e.g., "Temperature", "U component of wind")
15    pub long_name: String,
16    /// Units (e.g., "K", "m/s", "kg/m^2")
17    pub units: String,
18    /// Discipline (GRIB2 only, 0 for meteorological products)
19    pub discipline: Option<u8>,
20    /// Category within discipline
21    pub category: u8,
22    /// Parameter number within category
23    pub number: u8,
24}
25
26impl Parameter {
27    /// Create a new parameter definition
28    pub fn new(
29        short_name: impl Into<String>,
30        long_name: impl Into<String>,
31        units: impl Into<String>,
32        discipline: Option<u8>,
33        category: u8,
34        number: u8,
35    ) -> Self {
36        Self {
37            short_name: short_name.into(),
38            long_name: long_name.into(),
39            units: units.into(),
40            discipline,
41            category,
42            number,
43        }
44    }
45}
46
47/// GRIB2 parameter lookup using discipline-category-number
48pub fn lookup_grib2_parameter(discipline: u8, category: u8, number: u8) -> Result<Parameter> {
49    // WMO GRIB2 Code Table 4.2 - Parameter number by product discipline and parameter category
50    // Discipline 0 = Meteorological products
51    if discipline == 0 {
52        match (category, number) {
53            // Category 0: Temperature
54            (0, 0) => Ok(Parameter::new("TMP", "Temperature", "K", Some(0), 0, 0)),
55            (0, 1) => Ok(Parameter::new(
56                "VTMP",
57                "Virtual temperature",
58                "K",
59                Some(0),
60                0,
61                1,
62            )),
63            (0, 2) => Ok(Parameter::new(
64                "POT",
65                "Potential temperature",
66                "K",
67                Some(0),
68                0,
69                2,
70            )),
71            (0, 3) => Ok(Parameter::new(
72                "EPOT",
73                "Pseudo-adiabatic potential temperature",
74                "K",
75                Some(0),
76                0,
77                3,
78            )),
79            (0, 4) => Ok(Parameter::new(
80                "TMAX",
81                "Maximum temperature",
82                "K",
83                Some(0),
84                0,
85                4,
86            )),
87            (0, 5) => Ok(Parameter::new(
88                "TMIN",
89                "Minimum temperature",
90                "K",
91                Some(0),
92                0,
93                5,
94            )),
95            (0, 6) => Ok(Parameter::new(
96                "DPT",
97                "Dew point temperature",
98                "K",
99                Some(0),
100                0,
101                6,
102            )),
103            (0, 7) => Ok(Parameter::new(
104                "DEPR",
105                "Dew point depression",
106                "K",
107                Some(0),
108                0,
109                7,
110            )),
111            (0, 8) => Ok(Parameter::new("LAPR", "Lapse rate", "K/m", Some(0), 0, 8)),
112
113            // Category 1: Moisture
114            (1, 0) => Ok(Parameter::new(
115                "SPFH",
116                "Specific humidity",
117                "kg/kg",
118                Some(0),
119                1,
120                0,
121            )),
122            (1, 1) => Ok(Parameter::new(
123                "RH",
124                "Relative humidity",
125                "%",
126                Some(0),
127                1,
128                1,
129            )),
130            (1, 2) => Ok(Parameter::new(
131                "MIXR",
132                "Humidity mixing ratio",
133                "kg/kg",
134                Some(0),
135                1,
136                2,
137            )),
138            (1, 3) => Ok(Parameter::new(
139                "PWAT",
140                "Precipitable water",
141                "kg/m^2",
142                Some(0),
143                1,
144                3,
145            )),
146            (1, 4) => Ok(Parameter::new(
147                "VAPP",
148                "Vapor pressure",
149                "Pa",
150                Some(0),
151                1,
152                4,
153            )),
154            (1, 5) => Ok(Parameter::new(
155                "SATD",
156                "Saturation deficit",
157                "Pa",
158                Some(0),
159                1,
160                5,
161            )),
162            (1, 6) => Ok(Parameter::new(
163                "EVP",
164                "Evaporation",
165                "kg/m^2",
166                Some(0),
167                1,
168                6,
169            )),
170            (1, 7) => Ok(Parameter::new(
171                "PRATE",
172                "Precipitation rate",
173                "kg/m^2/s",
174                Some(0),
175                1,
176                7,
177            )),
178            (1, 8) => Ok(Parameter::new(
179                "APCP",
180                "Total precipitation",
181                "kg/m^2",
182                Some(0),
183                1,
184                8,
185            )),
186            (1, 9) => Ok(Parameter::new(
187                "NCPCP",
188                "Large scale precipitation",
189                "kg/m^2",
190                Some(0),
191                1,
192                9,
193            )),
194            (1, 10) => Ok(Parameter::new(
195                "ACPCP",
196                "Convective precipitation",
197                "kg/m^2",
198                Some(0),
199                1,
200                10,
201            )),
202
203            // Category 2: Momentum
204            (2, 0) => Ok(Parameter::new(
205                "WDIR",
206                "Wind direction",
207                "degree",
208                Some(0),
209                2,
210                0,
211            )),
212            (2, 1) => Ok(Parameter::new("WIND", "Wind speed", "m/s", Some(0), 2, 1)),
213            (2, 2) => Ok(Parameter::new(
214                "UGRD",
215                "U component of wind",
216                "m/s",
217                Some(0),
218                2,
219                2,
220            )),
221            (2, 3) => Ok(Parameter::new(
222                "VGRD",
223                "V component of wind",
224                "m/s",
225                Some(0),
226                2,
227                3,
228            )),
229            (2, 4) => Ok(Parameter::new(
230                "STRM",
231                "Stream function",
232                "m^2/s",
233                Some(0),
234                2,
235                4,
236            )),
237            (2, 5) => Ok(Parameter::new(
238                "VPOT",
239                "Velocity potential",
240                "m^2/s",
241                Some(0),
242                2,
243                5,
244            )),
245            (2, 6) => Ok(Parameter::new(
246                "MNTSF",
247                "Montgomery stream function",
248                "m^2/s^2",
249                Some(0),
250                2,
251                6,
252            )),
253            (2, 7) => Ok(Parameter::new(
254                "SGCVV",
255                "Sigma coordinate vertical velocity",
256                "1/s",
257                Some(0),
258                2,
259                7,
260            )),
261            (2, 8) => Ok(Parameter::new(
262                "VVEL",
263                "Vertical velocity (pressure)",
264                "Pa/s",
265                Some(0),
266                2,
267                8,
268            )),
269            (2, 9) => Ok(Parameter::new(
270                "DZDT",
271                "Vertical velocity (geometric)",
272                "m/s",
273                Some(0),
274                2,
275                9,
276            )),
277            (2, 10) => Ok(Parameter::new(
278                "ABSV",
279                "Absolute vorticity",
280                "1/s",
281                Some(0),
282                2,
283                10,
284            )),
285
286            // Category 3: Mass
287            (3, 0) => Ok(Parameter::new("PRES", "Pressure", "Pa", Some(0), 3, 0)),
288            (3, 1) => Ok(Parameter::new(
289                "PRMSL",
290                "Pressure reduced to MSL",
291                "Pa",
292                Some(0),
293                3,
294                1,
295            )),
296            (3, 2) => Ok(Parameter::new(
297                "PTEND",
298                "Pressure tendency",
299                "Pa/s",
300                Some(0),
301                3,
302                2,
303            )),
304            (3, 3) => Ok(Parameter::new(
305                "ICAHT",
306                "ICAO Standard Atmosphere reference height",
307                "m",
308                Some(0),
309                3,
310                3,
311            )),
312            (3, 4) => Ok(Parameter::new(
313                "GP",
314                "Geopotential",
315                "m^2/s^2",
316                Some(0),
317                3,
318                4,
319            )),
320            (3, 5) => Ok(Parameter::new(
321                "HGT",
322                "Geopotential height",
323                "gpm",
324                Some(0),
325                3,
326                5,
327            )),
328            (3, 6) => Ok(Parameter::new(
329                "DIST",
330                "Geometric height",
331                "m",
332                Some(0),
333                3,
334                6,
335            )),
336            (3, 7) => Ok(Parameter::new(
337                "HSTDV",
338                "Standard deviation of height",
339                "m",
340                Some(0),
341                3,
342                7,
343            )),
344            (3, 8) => Ok(Parameter::new(
345                "PRESA",
346                "Pressure anomaly",
347                "Pa",
348                Some(0),
349                3,
350                8,
351            )),
352
353            // Category 6: Cloud
354            (6, 0) => Ok(Parameter::new("CICE", "Cloud ice", "kg/m^2", Some(0), 6, 0)),
355            (6, 1) => Ok(Parameter::new(
356                "TCDC",
357                "Total cloud cover",
358                "%",
359                Some(0),
360                6,
361                1,
362            )),
363            (6, 2) => Ok(Parameter::new(
364                "CDCON",
365                "Convective cloud cover",
366                "%",
367                Some(0),
368                6,
369                2,
370            )),
371            (6, 3) => Ok(Parameter::new(
372                "LCDC",
373                "Low cloud cover",
374                "%",
375                Some(0),
376                6,
377                3,
378            )),
379            (6, 4) => Ok(Parameter::new(
380                "MCDC",
381                "Medium cloud cover",
382                "%",
383                Some(0),
384                6,
385                4,
386            )),
387            (6, 5) => Ok(Parameter::new(
388                "HCDC",
389                "High cloud cover",
390                "%",
391                Some(0),
392                6,
393                5,
394            )),
395            (6, 6) => Ok(Parameter::new(
396                "CWAT",
397                "Cloud water",
398                "kg/m^2",
399                Some(0),
400                6,
401                6,
402            )),
403
404            _ => Err(GribError::InvalidParameter {
405                discipline,
406                category,
407                number,
408            }),
409        }
410    } else {
411        // Other disciplines not implemented yet
412        Err(GribError::InvalidParameter {
413            discipline,
414            category,
415            number,
416        })
417    }
418}
419
420/// GRIB1 parameter lookup using parameter table version and parameter number
421pub fn lookup_grib1_parameter(table_version: u8, parameter_number: u8) -> Result<Parameter> {
422    // WMO GRIB1 Table 2 - Parameters (for table version 3)
423    // Most common parameters from NCEP/NCAR tables
424    if table_version == 3 {
425        match parameter_number {
426            1 => Ok(Parameter::new("PRES", "Pressure", "Pa", None, 3, 0)),
427            2 => Ok(Parameter::new(
428                "PRMSL",
429                "Pressure reduced to MSL",
430                "Pa",
431                None,
432                3,
433                1,
434            )),
435            7 => Ok(Parameter::new(
436                "HGT",
437                "Geopotential height",
438                "gpm",
439                None,
440                3,
441                5,
442            )),
443            11 => Ok(Parameter::new("TMP", "Temperature", "K", None, 0, 0)),
444            15 => Ok(Parameter::new(
445                "TMAX",
446                "Maximum temperature",
447                "K",
448                None,
449                0,
450                4,
451            )),
452            16 => Ok(Parameter::new(
453                "TMIN",
454                "Minimum temperature",
455                "K",
456                None,
457                0,
458                5,
459            )),
460            17 => Ok(Parameter::new(
461                "DPT",
462                "Dew point temperature",
463                "K",
464                None,
465                0,
466                6,
467            )),
468            33 => Ok(Parameter::new(
469                "UGRD",
470                "U component of wind",
471                "m/s",
472                None,
473                2,
474                2,
475            )),
476            34 => Ok(Parameter::new(
477                "VGRD",
478                "V component of wind",
479                "m/s",
480                None,
481                2,
482                3,
483            )),
484            39 => Ok(Parameter::new(
485                "DZDT",
486                "Vertical velocity",
487                "m/s",
488                None,
489                2,
490                9,
491            )),
492            51 => Ok(Parameter::new(
493                "SPFH",
494                "Specific humidity",
495                "kg/kg",
496                None,
497                1,
498                0,
499            )),
500            52 => Ok(Parameter::new("RH", "Relative humidity", "%", None, 1, 1)),
501            59 => Ok(Parameter::new(
502                "PRATE",
503                "Precipitation rate",
504                "kg/m^2/s",
505                None,
506                1,
507                7,
508            )),
509            61 => Ok(Parameter::new(
510                "APCP",
511                "Total precipitation",
512                "kg/m^2",
513                None,
514                1,
515                8,
516            )),
517            63 => Ok(Parameter::new(
518                "ACPCP",
519                "Convective precipitation",
520                "kg/m^2",
521                None,
522                1,
523                10,
524            )),
525            65 => Ok(Parameter::new(
526                "WEASD",
527                "Water equiv. of accum. snow depth",
528                "kg/m^2",
529                None,
530                1,
531                13,
532            )),
533            71 => Ok(Parameter::new("TCDC", "Total cloud cover", "%", None, 6, 1)),
534            _ => Err(GribError::InvalidParameter {
535                discipline: 0,
536                category: 255,
537                number: parameter_number,
538            }),
539        }
540    } else {
541        // Other table versions not fully implemented
542        Err(GribError::parse(format!(
543            "Unsupported parameter table version: {}",
544            table_version
545        )))
546    }
547}
548
549/// Level type definitions for GRIB
550#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum LevelType {
552    /// Surface (ground or water surface)
553    Surface,
554    /// Isobaric surface (pressure level in Pa)
555    Isobaric,
556    /// Mean sea level
557    MeanSeaLevel,
558    /// Specified height above ground (m)
559    HeightAboveGround,
560    /// Sigma level
561    Sigma,
562    /// Hybrid level
563    Hybrid,
564    /// Depth below land surface (m)
565    DepthBelowLand,
566    /// Isentropic level (K)
567    Isentropic,
568    /// Entire atmosphere (single layer)
569    EntireAtmosphere,
570    /// Unknown or unsupported level type
571    Unknown(u8),
572}
573
574impl LevelType {
575    /// Create level type from GRIB2 fixed surface type code
576    pub fn from_grib2_code(code: u8) -> Self {
577        match code {
578            1 => Self::Surface,
579            100 => Self::Isobaric,
580            101 => Self::MeanSeaLevel,
581            103 => Self::HeightAboveGround,
582            104 => Self::Sigma,
583            105 => Self::Hybrid,
584            106 => Self::DepthBelowLand,
585            107 => Self::Isentropic,
586            200 => Self::EntireAtmosphere,
587            _ => Self::Unknown(code),
588        }
589    }
590
591    /// Create level type from GRIB1 level type code
592    pub fn from_grib1_code(code: u8) -> Self {
593        match code {
594            1 => Self::Surface,
595            100 => Self::Isobaric,
596            102 => Self::MeanSeaLevel,
597            105 => Self::HeightAboveGround,
598            107 => Self::Sigma,
599            109 => Self::Hybrid,
600            111 => Self::DepthBelowLand,
601            113 => Self::Isentropic,
602            200 => Self::EntireAtmosphere,
603            _ => Self::Unknown(code),
604        }
605    }
606
607    /// Get human-readable description
608    pub fn description(&self) -> &'static str {
609        match self {
610            Self::Surface => "Surface",
611            Self::Isobaric => "Isobaric (pressure level)",
612            Self::MeanSeaLevel => "Mean sea level",
613            Self::HeightAboveGround => "Height above ground",
614            Self::Sigma => "Sigma level",
615            Self::Hybrid => "Hybrid level",
616            Self::DepthBelowLand => "Depth below land surface",
617            Self::Isentropic => "Isentropic (potential temperature)",
618            Self::EntireAtmosphere => "Entire atmosphere",
619            Self::Unknown(_) => "Unknown level type",
620        }
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627
628    #[test]
629    fn test_grib2_temperature_lookup() {
630        let param = lookup_grib2_parameter(0, 0, 0).expect("Temperature lookup failed");
631        assert_eq!(param.short_name, "TMP");
632        assert_eq!(param.units, "K");
633        assert_eq!(param.discipline, Some(0));
634    }
635
636    #[test]
637    fn test_grib2_wind_lookup() {
638        let u_wind = lookup_grib2_parameter(0, 2, 2).expect("U-wind lookup failed");
639        assert_eq!(u_wind.short_name, "UGRD");
640        assert_eq!(u_wind.units, "m/s");
641
642        let v_wind = lookup_grib2_parameter(0, 2, 3).expect("V-wind lookup failed");
643        assert_eq!(v_wind.short_name, "VGRD");
644    }
645
646    #[test]
647    fn test_grib2_invalid_parameter() {
648        let result = lookup_grib2_parameter(0, 99, 99);
649        assert!(result.is_err());
650    }
651
652    #[test]
653    fn test_grib1_parameter_lookup() {
654        let temp = lookup_grib1_parameter(3, 11).expect("Temperature lookup failed");
655        assert_eq!(temp.short_name, "TMP");
656        assert_eq!(temp.units, "K");
657    }
658
659    #[test]
660    fn test_level_type_grib2() {
661        assert_eq!(LevelType::from_grib2_code(1), LevelType::Surface);
662        assert_eq!(LevelType::from_grib2_code(100), LevelType::Isobaric);
663        assert_eq!(
664            LevelType::from_grib2_code(103),
665            LevelType::HeightAboveGround
666        );
667    }
668
669    #[test]
670    fn test_level_type_description() {
671        assert_eq!(LevelType::Surface.description(), "Surface");
672        assert_eq!(
673            LevelType::Isobaric.description(),
674            "Isobaric (pressure level)"
675        );
676    }
677}