1use crate::error::{GribError, Result};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct Parameter {
12 pub short_name: String,
14 pub long_name: String,
16 pub units: String,
18 pub discipline: Option<u8>,
20 pub category: u8,
22 pub number: u8,
24}
25
26impl Parameter {
27 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
47pub fn lookup_grib2_parameter(discipline: u8, category: u8, number: u8) -> Result<Parameter> {
49 if discipline == 0 {
52 match (category, number) {
53 (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 (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 (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 (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 (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 Err(GribError::InvalidParameter {
413 discipline,
414 category,
415 number,
416 })
417 }
418}
419
420pub fn lookup_grib1_parameter(table_version: u8, parameter_number: u8) -> Result<Parameter> {
422 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 Err(GribError::parse(format!(
543 "Unsupported parameter table version: {}",
544 table_version
545 )))
546 }
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum LevelType {
552 Surface,
554 Isobaric,
556 MeanSeaLevel,
558 HeightAboveGround,
560 Sigma,
562 Hybrid,
564 DepthBelowLand,
566 Isentropic,
568 EntireAtmosphere,
570 Unknown(u8),
572}
573
574impl LevelType {
575 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 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 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}