Skip to main content

openmeteo_rs/variables/
shared.rs

1use std::borrow::Cow;
2
3use crate::query::AsApiStr;
4
5/// Aggregation applied to a variable, primarily for daily values.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
7#[non_exhaustive]
8pub enum Aggregation {
9    /// Minimum over the aggregation period.
10    Minimum,
11    /// Maximum over the aggregation period.
12    Maximum,
13    /// Mean over the aggregation period.
14    Mean,
15    /// Median over the aggregation period.
16    Median,
17    /// Sum over the aggregation period.
18    Sum,
19    /// 10th percentile over the aggregation period.
20    P10,
21    /// 25th percentile over the aggregation period.
22    P25,
23    /// 75th percentile over the aggregation period.
24    P75,
25    /// 90th percentile over the aggregation period.
26    P90,
27    /// Dominant direction or category.
28    Dominant,
29}
30
31impl Aggregation {
32    /// Returns the Open-Meteo suffix for this aggregation.
33    pub fn suffix(self) -> &'static str {
34        match self {
35            Self::Minimum => "_min",
36            Self::Maximum => "_max",
37            Self::Mean => "_mean",
38            Self::Median => "_median",
39            Self::Sum => "_sum",
40            Self::P10 => "_p10",
41            Self::P25 => "_p25",
42            Self::P75 => "_p75",
43            Self::P90 => "_p90",
44            Self::Dominant => "_dominant",
45        }
46    }
47}
48
49/// Pressure levels exposed by the general forecast endpoint.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51#[repr(i16)]
52#[non_exhaustive]
53pub enum PressureLevel {
54    /// 1000 hPa.
55    Hpa1000 = 1000,
56    /// 975 hPa.
57    Hpa975 = 975,
58    /// 950 hPa.
59    Hpa950 = 950,
60    /// 925 hPa.
61    Hpa925 = 925,
62    /// 900 hPa.
63    Hpa900 = 900,
64    /// 850 hPa.
65    Hpa850 = 850,
66    /// 800 hPa.
67    Hpa800 = 800,
68    /// 700 hPa.
69    Hpa700 = 700,
70    /// 600 hPa.
71    Hpa600 = 600,
72    /// 500 hPa.
73    Hpa500 = 500,
74    /// 400 hPa.
75    Hpa400 = 400,
76    /// 300 hPa.
77    Hpa300 = 300,
78    /// 250 hPa.
79    Hpa250 = 250,
80    /// 200 hPa.
81    Hpa200 = 200,
82    /// 150 hPa.
83    Hpa150 = 150,
84    /// 100 hPa.
85    Hpa100 = 100,
86    /// 70 hPa.
87    Hpa70 = 70,
88    /// 50 hPa.
89    Hpa50 = 50,
90    /// 30 hPa.
91    Hpa30 = 30,
92}
93
94impl PressureLevel {
95    pub(crate) fn hpa(self) -> i16 {
96        self as i16
97    }
98}
99
100/// Tower and near-surface measurement levels exposed by forecast variables.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102#[repr(i16)]
103#[non_exhaustive]
104pub enum TowerLevel {
105    /// 10 m above ground.
106    M10 = 10,
107    /// 80 m above ground.
108    M80 = 80,
109    /// 120 m above ground.
110    M120 = 120,
111    /// 180 m above ground.
112    M180 = 180,
113}
114
115impl TowerLevel {
116    pub(crate) fn meters(self) -> i16 {
117        self as i16
118    }
119}
120
121/// Weather model selector for forecast requests.
122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
123#[non_exhaustive]
124pub enum WeatherModel {
125    /// Open-Meteo's automatic best-match model.
126    BestMatch,
127    /// NOAA GFS seamless model.
128    GfsSeamless,
129    /// ECMWF IFS 0.25 degree model.
130    EcmwfIfs025,
131    /// DWD ICON seamless model.
132    IconSeamless,
133    /// MeteoFrance seamless model.
134    MeteofranceSeamless,
135    /// Exact Open-Meteo model token not yet represented by this enum.
136    ///
137    /// This is an escape hatch for documented model names that have not been
138    /// promoted to first-class variants yet. The token is not validated by the
139    /// crate.
140    Other(Cow<'static, str>),
141}
142
143/// Reanalysis model selector for archive requests.
144#[derive(Debug, Clone, PartialEq, Eq, Hash)]
145#[non_exhaustive]
146pub enum ArchiveModel {
147    /// Open-Meteo's automatic best-match reanalysis model.
148    BestMatch,
149    /// Open-Meteo's seamless ERA5 model selection.
150    Era5Seamless,
151    /// ECMWF ERA5 reanalysis.
152    Era5,
153    /// ECMWF ERA5-Land reanalysis.
154    Era5Land,
155    /// ECMWF ERA5 ensemble reanalysis.
156    Era5Ensemble,
157    /// Open-Meteo's assembled ECMWF IFS archive.
158    EcmwfIfs,
159    /// ECMWF IFS assimilation long-window archive.
160    EcmwfIfsAnalysisLongWindow,
161    /// Copernicus European Regional Reanalysis.
162    Cerra,
163    /// Exact Open-Meteo archive model token not yet represented by this enum.
164    ///
165    /// This is an escape hatch for documented model names that have not been
166    /// promoted to first-class variants yet. The token is not validated by the
167    /// crate.
168    Other(Cow<'static, str>),
169}
170
171/// Model selector for ensemble forecast requests.
172#[derive(Debug, Clone, PartialEq, Eq, Hash)]
173#[non_exhaustive]
174pub enum EnsembleModel {
175    /// DWD ICON EPS seamless ensemble model.
176    IconSeamlessEps,
177    /// DWD ICON EPS global model.
178    IconGlobalEps,
179    /// DWD ICON EPS Europe model.
180    IconEuEps,
181    /// DWD ICON EPS D2 model.
182    IconD2Eps,
183    /// NOAA/NCEP GEFS seamless ensemble model.
184    NcepGefsSeamless,
185    /// NOAA/NCEP GEFS 0.25 degree ensemble model.
186    NcepGefs025,
187    /// NOAA/NCEP GEFS 0.5 degree ensemble model.
188    NcepGefs05,
189    /// NOAA/NCEP AIGEPS 0.25 degree ensemble model.
190    NcepAigefs025,
191    /// ECMWF IFS 0.25 degree ensemble model.
192    EcmwfIfs025Ensemble,
193    /// ECMWF AIFS 0.25 degree ensemble model.
194    EcmwfAifs025Ensemble,
195    /// Canadian GEM global ensemble model.
196    GemGlobalEnsemble,
197    /// Australian BOM ACCESS global ensemble model.
198    BomAccessGlobalEnsemble,
199    /// UK Met Office global 20 km ensemble model.
200    UkmoGlobalEnsemble20km,
201    /// UK Met Office UK 2 km ensemble model.
202    UkmoUkEnsemble2km,
203    /// MeteoSwiss ICON CH1 ensemble model.
204    MeteoswissIconCh1Ensemble,
205    /// MeteoSwiss ICON CH2 ensemble model.
206    MeteoswissIconCh2Ensemble,
207    /// Exact Open-Meteo ensemble model token not yet represented by this enum.
208    ///
209    /// The token is passed through unchanged and is not validated by the crate.
210    Other(Cow<'static, str>),
211}
212
213impl EnsembleModel {
214    /// Creates an exact Open-Meteo ensemble model token not yet represented by this enum.
215    ///
216    /// The token is passed through unchanged and is not validated by the crate.
217    pub fn other(token: impl Into<Cow<'static, str>>) -> Self {
218        Self::Other(token.into())
219    }
220}
221
222impl AsApiStr for EnsembleModel {
223    fn as_api_str(&self) -> Cow<'static, str> {
224        match self {
225            Self::IconSeamlessEps => Cow::Borrowed("icon_seamless_eps"),
226            Self::IconGlobalEps => Cow::Borrowed("icon_global_eps"),
227            Self::IconEuEps => Cow::Borrowed("icon_eu_eps"),
228            Self::IconD2Eps => Cow::Borrowed("icon_d2_eps"),
229            Self::NcepGefsSeamless => Cow::Borrowed("ncep_gefs_seamless"),
230            Self::NcepGefs025 => Cow::Borrowed("ncep_gefs025"),
231            Self::NcepGefs05 => Cow::Borrowed("ncep_gefs05"),
232            Self::NcepAigefs025 => Cow::Borrowed("ncep_aigefs025"),
233            Self::EcmwfIfs025Ensemble => Cow::Borrowed("ecmwf_ifs025_ensemble"),
234            Self::EcmwfAifs025Ensemble => Cow::Borrowed("ecmwf_aifs025_ensemble"),
235            Self::GemGlobalEnsemble => Cow::Borrowed("gem_global_ensemble"),
236            Self::BomAccessGlobalEnsemble => Cow::Borrowed("bom_access_global_ensemble"),
237            Self::UkmoGlobalEnsemble20km => Cow::Borrowed("ukmo_global_ensemble_20km"),
238            Self::UkmoUkEnsemble2km => Cow::Borrowed("ukmo_uk_ensemble_2km"),
239            Self::MeteoswissIconCh1Ensemble => Cow::Borrowed("meteoswiss_icon_ch1_ensemble"),
240            Self::MeteoswissIconCh2Ensemble => Cow::Borrowed("meteoswiss_icon_ch2_ensemble"),
241            Self::Other(token) => token.clone(),
242        }
243    }
244}
245
246impl ArchiveModel {
247    /// Creates an exact Open-Meteo archive model token not yet represented by this enum.
248    ///
249    /// The token is passed through unchanged and is not validated by the crate.
250    pub fn other(token: impl Into<Cow<'static, str>>) -> Self {
251        Self::Other(token.into())
252    }
253}
254
255impl AsApiStr for ArchiveModel {
256    fn as_api_str(&self) -> Cow<'static, str> {
257        match self {
258            Self::BestMatch => Cow::Borrowed("best_match"),
259            Self::Era5Seamless => Cow::Borrowed("era5_seamless"),
260            Self::Era5 => Cow::Borrowed("era5"),
261            Self::Era5Land => Cow::Borrowed("era5_land"),
262            Self::Era5Ensemble => Cow::Borrowed("era5_ensemble"),
263            Self::EcmwfIfs => Cow::Borrowed("ecmwf_ifs"),
264            Self::EcmwfIfsAnalysisLongWindow => Cow::Borrowed("ecmwf_ifs_analysis_long_window"),
265            Self::Cerra => Cow::Borrowed("cerra"),
266            Self::Other(token) => token.clone(),
267        }
268    }
269}
270
271impl WeatherModel {
272    /// Creates an exact Open-Meteo model token not yet represented by this enum.
273    ///
274    /// The token is passed through unchanged and is not validated by the crate.
275    pub fn other(token: impl Into<Cow<'static, str>>) -> Self {
276        Self::Other(token.into())
277    }
278}
279
280impl AsApiStr for WeatherModel {
281    fn as_api_str(&self) -> Cow<'static, str> {
282        match self {
283            Self::BestMatch => Cow::Borrowed("best_match"),
284            Self::GfsSeamless => Cow::Borrowed("gfs_seamless"),
285            Self::EcmwfIfs025 => Cow::Borrowed("ecmwf_ifs025"),
286            Self::IconSeamless => Cow::Borrowed("icon_seamless"),
287            Self::MeteofranceSeamless => Cow::Borrowed("meteofrance_seamless"),
288            Self::Other(token) => token.clone(),
289        }
290    }
291}
292
293/// Soil-temperature depth supported by the forecast API.
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
295#[repr(i16)]
296#[non_exhaustive]
297pub enum SoilTemperatureDepth {
298    /// Surface soil temperature.
299    Cm0 = 0,
300    /// Soil temperature at 6 cm.
301    Cm6 = 6,
302    /// Soil temperature at 18 cm.
303    Cm18 = 18,
304    /// Soil temperature at 54 cm.
305    Cm54 = 54,
306}
307
308impl SoilTemperatureDepth {
309    pub(crate) fn cm(self) -> i16 {
310        self as i16
311    }
312}
313
314/// Soil-moisture depth boundary.
315#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
316#[repr(i16)]
317#[non_exhaustive]
318pub enum SoilMoistureDepth {
319    /// 0 cm.
320    Cm0 = 0,
321    /// 1 cm.
322    Cm1 = 1,
323    /// 3 cm.
324    Cm3 = 3,
325    /// 9 cm.
326    Cm9 = 9,
327    /// 27 cm.
328    Cm27 = 27,
329    /// 81 cm.
330    Cm81 = 81,
331}
332
333impl SoilMoistureDepth {
334    pub(crate) fn cm(self) -> i16 {
335        self as i16
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use crate::query::AsApiStr;
343
344    #[test]
345    fn archive_model_tokens_are_formatted() {
346        assert_eq!(ArchiveModel::Era5Land.as_api_str(), "era5_land");
347        assert_eq!(ArchiveModel::EcmwfIfs.as_api_str(), "ecmwf_ifs");
348        assert_eq!(ArchiveModel::Cerra.as_api_str(), "cerra");
349        assert_eq!(
350            ArchiveModel::other("custom_archive_model").as_api_str(),
351            "custom_archive_model"
352        );
353    }
354
355    #[test]
356    fn ensemble_model_tokens_are_formatted() {
357        assert_eq!(
358            EnsembleModel::IconSeamlessEps.as_api_str(),
359            "icon_seamless_eps"
360        );
361        assert_eq!(EnsembleModel::IconGlobalEps.as_api_str(), "icon_global_eps");
362        assert_eq!(EnsembleModel::IconEuEps.as_api_str(), "icon_eu_eps");
363        assert_eq!(EnsembleModel::IconD2Eps.as_api_str(), "icon_d2_eps");
364        assert_eq!(
365            EnsembleModel::NcepGefsSeamless.as_api_str(),
366            "ncep_gefs_seamless"
367        );
368        assert_eq!(EnsembleModel::NcepGefs025.as_api_str(), "ncep_gefs025");
369        assert_eq!(EnsembleModel::NcepGefs05.as_api_str(), "ncep_gefs05");
370        assert_eq!(EnsembleModel::NcepAigefs025.as_api_str(), "ncep_aigefs025");
371        assert_eq!(
372            EnsembleModel::EcmwfIfs025Ensemble.as_api_str(),
373            "ecmwf_ifs025_ensemble"
374        );
375        assert_eq!(
376            EnsembleModel::EcmwfAifs025Ensemble.as_api_str(),
377            "ecmwf_aifs025_ensemble"
378        );
379        assert_eq!(
380            EnsembleModel::GemGlobalEnsemble.as_api_str(),
381            "gem_global_ensemble"
382        );
383        assert_eq!(
384            EnsembleModel::BomAccessGlobalEnsemble.as_api_str(),
385            "bom_access_global_ensemble"
386        );
387        assert_eq!(
388            EnsembleModel::UkmoGlobalEnsemble20km.as_api_str(),
389            "ukmo_global_ensemble_20km"
390        );
391        assert_eq!(
392            EnsembleModel::UkmoUkEnsemble2km.as_api_str(),
393            "ukmo_uk_ensemble_2km"
394        );
395        assert_eq!(
396            EnsembleModel::MeteoswissIconCh1Ensemble.as_api_str(),
397            "meteoswiss_icon_ch1_ensemble"
398        );
399        assert_eq!(
400            EnsembleModel::MeteoswissIconCh2Ensemble.as_api_str(),
401            "meteoswiss_icon_ch2_ensemble"
402        );
403        assert_eq!(
404            EnsembleModel::other("custom_ensemble_model").as_api_str(),
405            "custom_ensemble_model"
406        );
407    }
408}