1use crate::metadata::{Parameter, ParameterTableSource};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct LocalParameterEntry<'a> {
14 pub center_id: u16,
15 pub subcenter_id: Option<u16>,
16 pub local_table_version: Option<u8>,
17 pub discipline: u8,
18 pub category: u8,
19 pub number: u8,
20 pub short_name: &'a str,
21 pub description: &'a str,
22}
23
24impl LocalParameterEntry<'_> {
25 fn matches(
26 &self,
27 discipline: u8,
28 category: u8,
29 number: u8,
30 center_id: u16,
31 subcenter_id: u16,
32 local_table_version: u8,
33 ) -> bool {
34 self.center_id == center_id
35 && self
36 .subcenter_id
37 .map_or(true, |expected| expected == subcenter_id)
38 && self
39 .local_table_version
40 .map_or(true, |expected| expected == local_table_version)
41 && self.discipline == discipline
42 && self.category == category
43 && self.number == number
44 }
45}
46
47pub const BUILTIN_LOCAL_PARAMETERS: &[LocalParameterEntry<'static>] = &[LocalParameterEntry {
49 center_id: 7,
50 subcenter_id: None,
51 local_table_version: Some(1),
52 discipline: 0,
53 category: 16,
54 number: 196,
55 short_name: "REFC",
56 description: "Maximum/Composite radar reflectivity",
57}];
58
59pub fn grib1_parameter_name(table_version: u8, number: u8) -> &'static str {
61 match (table_version, number) {
62 (_, 1) => "PRES",
63 (_, 2) => "PRMSL",
64 (_, 7) => "HGT",
65 (_, 11) => "TMP",
66 (_, 17) => "DPT",
67 (_, 33) => "UGRD",
68 (_, 34) => "VGRD",
69 (_, 39) => "VVEL",
70 (_, 52) => "RH",
71 (_, 54) => "PWAT",
72 (_, 61) => "APCP",
73 (_, 71) => "TCDC",
74 _ => "unknown",
75 }
76}
77
78pub fn grib1_parameter_description(table_version: u8, number: u8) -> &'static str {
80 match (table_version, number) {
81 (_, 1) => "Pressure",
82 (_, 2) => "Pressure reduced to mean sea level",
83 (_, 7) => "Geopotential height",
84 (_, 11) => "Temperature",
85 (_, 17) => "Dew point temperature",
86 (_, 33) => "U-component of wind",
87 (_, 34) => "V-component of wind",
88 (_, 39) => "Vertical velocity",
89 (_, 52) => "Relative humidity",
90 (_, 54) => "Precipitable water",
91 (_, 61) => "Total precipitation",
92 (_, 71) => "Total cloud cover",
93 _ => "Unknown parameter",
94 }
95}
96
97pub fn parameter_name(discipline: u8, category: u8, number: u8) -> &'static str {
99 wmo_parameter_definition(discipline, category, number)
100 .map(|(short_name, _description)| short_name)
101 .unwrap_or("unknown")
102}
103
104pub fn parameter_description(discipline: u8, category: u8, number: u8) -> &'static str {
106 wmo_parameter_definition(discipline, category, number)
107 .map(|(_short_name, description)| description)
108 .unwrap_or("Unknown parameter")
109}
110
111pub fn lookup_parameter(
113 discipline: u8,
114 category: u8,
115 number: u8,
116 center_id: u16,
117 subcenter_id: u16,
118 local_table_version: u8,
119) -> Parameter {
120 lookup_parameter_with_local_entries(
121 discipline,
122 category,
123 number,
124 center_id,
125 subcenter_id,
126 local_table_version,
127 &[],
128 )
129}
130
131pub fn lookup_parameter_with_local_entries(
138 discipline: u8,
139 category: u8,
140 number: u8,
141 center_id: u16,
142 subcenter_id: u16,
143 local_table_version: u8,
144 local_entries: &[LocalParameterEntry<'_>],
145) -> Parameter {
146 if let Some((short_name, description)) = wmo_parameter_definition(discipline, category, number)
147 {
148 return Parameter::new_grib2_with_source(
149 discipline,
150 category,
151 number,
152 short_name,
153 description,
154 ParameterTableSource::Wmo,
155 );
156 }
157
158 if is_local_parameter_code(category) || is_local_parameter_code(number) {
159 if local_table_version != 0 {
160 if let Some(entry) = local_entries
161 .iter()
162 .chain(BUILTIN_LOCAL_PARAMETERS.iter())
163 .find(|entry| {
164 entry.matches(
165 discipline,
166 category,
167 number,
168 center_id,
169 subcenter_id,
170 local_table_version,
171 )
172 })
173 {
174 return Parameter::new_grib2_with_source(
175 discipline,
176 category,
177 number,
178 entry.short_name.to_owned(),
179 entry.description.to_owned(),
180 ParameterTableSource::Local {
181 center_id,
182 subcenter_id,
183 local_table_version,
184 },
185 );
186 }
187 }
188
189 return Parameter::new_grib2_with_source(
190 discipline,
191 category,
192 number,
193 "unknown",
194 "Unknown parameter",
195 ParameterTableSource::UnknownLocal {
196 center_id,
197 subcenter_id,
198 local_table_version,
199 },
200 );
201 }
202
203 Parameter::new_grib2_with_source(
204 discipline,
205 category,
206 number,
207 "unknown",
208 "Unknown parameter",
209 ParameterTableSource::Unknown,
210 )
211}
212
213fn is_local_parameter_code(code: u8) -> bool {
214 (192..=254).contains(&code)
215}
216
217fn wmo_parameter_definition(
218 discipline: u8,
219 category: u8,
220 number: u8,
221) -> Option<(&'static str, &'static str)> {
222 match (discipline, category, number) {
223 (0, 0, 0) => Some(("TMP", "Temperature")),
226 (0, 0, 1) => Some(("VTMP", "Virtual temperature")),
227 (0, 0, 2) => Some(("POT", "Potential temperature")),
228 (0, 0, 4) => Some(("TMAX", "Maximum temperature")),
229 (0, 0, 5) => Some(("TMIN", "Minimum temperature")),
230 (0, 0, 6) => Some(("DPT", "Dew point temperature")),
231 (0, 1, 0) => Some(("SPFH", "Specific humidity")),
233 (0, 1, 1) => Some(("RH", "Relative humidity")),
234 (0, 1, 3) => Some(("PWAT", "Precipitable water")),
235 (0, 1, 8) => Some(("APCP", "Total precipitation")),
236 (0, 2, 0) => Some(("WDIR", "Wind direction")),
238 (0, 2, 1) => Some(("WIND", "Wind speed")),
239 (0, 2, 2) => Some(("UGRD", "U-component of wind")),
240 (0, 2, 3) => Some(("VGRD", "V-component of wind")),
241 (0, 2, 22) => Some(("GUST", "Wind gust")),
242 (0, 3, 0) => Some(("PRES", "Pressure")),
244 (0, 3, 1) => Some(("PRMSL", "Pressure reduced to MSL")),
245 (0, 3, 5) => Some(("HGT", "Geopotential height")),
246 (0, 4, 7) => Some(("DSWRF", "Downward short-wave radiation flux")),
248 (0, 5, 3) => Some(("DLWRF", "Downward long-wave radiation flux")),
250 (0, 6, 1) => Some(("TCDC", "Total cloud cover")),
252 (0, 7, 6) => Some(("CAPE", "Convective available potential energy")),
254 (0, 7, 7) => Some(("CIN", "Convective inhibition")),
255 (0, 16, 5) => Some(("REFC", "Composite reflectivity")),
257 (0, 19, 1) => Some(("ALBDO", "Albedo")),
259
260 (10, 0, 3) => Some((
263 "HTSGW",
264 "Significant height of combined wind waves and swell",
265 )),
266 (10, 0, 4) => Some(("WVDIR", "Direction of wind waves")),
267 (10, 0, 5) => Some(("WVPER", "Mean period of wind waves")),
268 (10, 3, 0) => Some(("WTMP", "Water temperature")),
270
271 _ => None,
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn known_parameters() {
281 assert_eq!(grib1_parameter_name(2, 11), "TMP");
282 assert_eq!(grib1_parameter_name(2, 33), "UGRD");
283 assert_eq!(parameter_name(0, 0, 0), "TMP");
284 assert_eq!(parameter_name(0, 2, 2), "UGRD");
285 assert_eq!(parameter_name(0, 3, 5), "HGT");
286 assert_eq!(parameter_name(0, 16, 5), "REFC");
287 assert_eq!(parameter_name(0, 19, 1), "ALBDO");
288 assert_eq!(parameter_name(10, 0, 3), "HTSGW");
289 assert_eq!(parameter_description(0, 0, 2), "Potential temperature");
290 assert_eq!(parameter_description(0, 16, 5), "Composite reflectivity");
291 assert_eq!(parameter_description(0, 19, 1), "Albedo");
292 assert_eq!(parameter_description(10, 3, 0), "Water temperature");
293 }
294
295 #[test]
296 fn unknown_parameter() {
297 assert_eq!(parameter_name(255, 255, 255), "unknown");
298 assert_eq!(parameter_name(0, 16, 196), "unknown");
299 assert_eq!(parameter_description(0, 16, 196), "Unknown parameter");
300 }
301
302 #[test]
303 fn ncep_refc_uses_local_table_context() {
304 let parameter = lookup_parameter(0, 16, 196, 7, 0, 1);
305
306 assert_eq!(parameter.short_name, "REFC");
307 assert_eq!(
308 parameter.description,
309 "Maximum/Composite radar reflectivity"
310 );
311 assert_eq!(
312 parameter.source,
313 ParameterTableSource::Local {
314 center_id: 7,
315 subcenter_id: 0,
316 local_table_version: 1,
317 }
318 );
319 }
320
321 #[test]
322 fn unknown_local_parameter_remains_local_unknown() {
323 let parameter = lookup_parameter(0, 16, 196, 8, 0, 1);
324
325 assert_eq!(parameter.short_name, "unknown");
326 assert_eq!(parameter.description, "Unknown parameter");
327 assert_eq!(
328 parameter.source,
329 ParameterTableSource::UnknownLocal {
330 center_id: 8,
331 subcenter_id: 0,
332 local_table_version: 1,
333 }
334 );
335 }
336
337 #[test]
338 fn local_table_version_must_match_local_entry() {
339 let parameter = lookup_parameter(0, 16, 196, 7, 0, 2);
340
341 assert_eq!(parameter.short_name, "unknown");
342 assert_eq!(parameter.description, "Unknown parameter");
343 assert_eq!(
344 parameter.source,
345 ParameterTableSource::UnknownLocal {
346 center_id: 7,
347 subcenter_id: 0,
348 local_table_version: 2,
349 }
350 );
351 }
352
353 #[test]
354 fn user_local_entries_resolve_local_use_codes() {
355 let entries = [LocalParameterEntry {
356 center_id: 42,
357 subcenter_id: Some(5),
358 local_table_version: Some(3),
359 discipline: 0,
360 category: 192,
361 number: 1,
362 short_name: "XFOO",
363 description: "Example local parameter",
364 }];
365
366 let parameter = lookup_parameter_with_local_entries(0, 192, 1, 42, 5, 3, &entries);
367
368 assert_eq!(parameter.short_name, "XFOO");
369 assert_eq!(parameter.description, "Example local parameter");
370 assert_eq!(
371 parameter.source,
372 ParameterTableSource::Local {
373 center_id: 42,
374 subcenter_id: 5,
375 local_table_version: 3,
376 }
377 );
378 }
379
380 #[test]
381 fn wmo_parameters_win_over_local_entries() {
382 let entries = [LocalParameterEntry {
383 center_id: 7,
384 subcenter_id: None,
385 local_table_version: Some(1),
386 discipline: 0,
387 category: 0,
388 number: 0,
389 short_name: "BADTMP",
390 description: "Bad local temperature",
391 }];
392
393 let parameter = lookup_parameter_with_local_entries(0, 0, 0, 7, 0, 1, &entries);
394
395 assert_eq!(parameter.short_name, "TMP");
396 assert_eq!(parameter.description, "Temperature");
397 assert_eq!(parameter.source, ParameterTableSource::Wmo);
398 }
399}