use time::OffsetDateTime;
use super::{HourlyRowIter, Variable, VariableDescriptor};
use crate::query::AsApiStr;
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct ForecastResponse {
pub latitude: f64,
pub longitude: f64,
pub elevation: Option<f64>,
pub generation_time_ms: Option<f32>,
pub utc_offset_seconds: i32,
pub timezone: Option<String>,
pub timezone_abbreviation: Option<String>,
pub current: Option<CurrentData>,
pub hourly: Option<HourlyData>,
pub minutely_15: Option<Minutely15Data>,
pub daily: Option<DailyData>,
pub monthly: Option<MonthlyData>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct CurrentData {
pub time: Option<OffsetDateTime>,
pub variables: Vec<VariableSeries>,
}
impl CurrentData {
pub fn get(&self, api_name: &str) -> Option<&VariableSeries> {
find_by_api_name(&self.variables, api_name)
}
pub fn get_var(&self, var: crate::CurrentVar) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_unique_by_descriptor(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
)
}
pub fn get_var_for_model(
&self,
var: crate::CurrentVar,
model: &str,
) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_by_descriptor_for_model(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
model,
)
}
pub fn temperature_2m(&self) -> Option<&VariableSeries> {
self.get_var(crate::CurrentVar::Temperature2m)
}
pub fn is_day(&self) -> Option<bool> {
self.get_var(crate::CurrentVar::IsDay)
.and_then(VariableSeries::values_f32)
.and_then(|values| values.first().copied().flatten())
.map(value_to_bool)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct HourlyData {
pub time: Vec<OffsetDateTime>,
pub variables: Vec<VariableSeries>,
}
impl HourlyData {
pub fn get(&self, api_name: &str) -> Option<&VariableSeries> {
find_by_api_name(&self.variables, api_name)
}
pub fn get_var(&self, var: crate::HourlyVar) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_unique_by_descriptor(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
)
}
pub fn get_var_for_model(&self, var: crate::HourlyVar, model: &str) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_by_descriptor_for_model(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
model,
)
}
pub fn temperature_2m(&self) -> Option<&VariableSeries> {
self.get_var(crate::HourlyVar::Temperature2m)
}
pub fn precipitation(&self) -> Option<&VariableSeries> {
self.get_var(crate::HourlyVar::Precipitation)
}
pub fn is_day_at(&self, index: usize) -> Option<bool> {
self.get_var(crate::HourlyVar::IsDay)
.and_then(VariableSeries::values_f32)
.and_then(|values| values.get(index).copied().flatten())
.map(value_to_bool)
}
pub fn pressure_msl(&self) -> Option<&VariableSeries> {
self.get_var(crate::HourlyVar::PressureMsl)
}
pub fn surface_pressure(&self) -> Option<&VariableSeries> {
self.get_var(crate::HourlyVar::SurfacePressure)
}
pub fn wind_speed_at(&self, level: crate::TowerLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::WindSpeed
&& series.descriptor.altitude == Some(level.meters())
})
}
pub fn wind_direction_at(&self, level: crate::TowerLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::WindDirection
&& series.descriptor.altitude == Some(level.meters())
})
}
pub fn temperature_at_tower(&self, level: crate::TowerLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::Temperature
&& series.descriptor.altitude == Some(level.meters())
})
}
pub fn temperature_at_pressure(&self, level: crate::PressureLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::Temperature
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn relative_humidity_at_pressure(
&self,
level: crate::PressureLevel,
) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::RelativeHumidity
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn dew_point_at_pressure(&self, level: crate::PressureLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::DewPoint
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn cloud_cover_at_pressure(&self, level: crate::PressureLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::CloudCover
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn wind_speed_at_pressure(&self, level: crate::PressureLevel) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::WindSpeed
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn wind_direction_at_pressure(
&self,
level: crate::PressureLevel,
) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::WindDirection
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn geopotential_height_at_pressure(
&self,
level: crate::PressureLevel,
) -> Option<&VariableSeries> {
self.variables.iter().find(|series| {
series.descriptor.variable == Variable::GeopotentialHeight
&& series.descriptor.pressure_level == Some(level.hpa())
})
}
pub fn iter_rows(&self) -> HourlyRowIter<'_> {
HourlyRowIter {
data: self,
index: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct Minutely15Data {
pub time: Vec<OffsetDateTime>,
pub variables: Vec<VariableSeries>,
}
impl Minutely15Data {
pub fn get(&self, api_name: &str) -> Option<&VariableSeries> {
find_by_api_name(&self.variables, api_name)
}
pub fn get_var(&self, var: crate::Minutely15Var) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_unique_by_descriptor(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
)
}
pub fn get_var_for_model(
&self,
var: crate::Minutely15Var,
model: &str,
) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_by_descriptor_for_model(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
model,
)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct DailyData {
pub time: Vec<OffsetDateTime>,
pub variables: Vec<VariableSeries>,
}
impl DailyData {
pub fn temperature_2m_max(&self) -> Option<&VariableSeries> {
self.get_var(crate::DailyVar::Temperature2mMax)
}
pub fn get(&self, api_name: &str) -> Option<&VariableSeries> {
find_by_api_name(&self.variables, api_name)
}
pub fn get_var(&self, var: crate::DailyVar) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_unique_by_descriptor(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
)
}
pub fn get_var_for_model(&self, var: crate::DailyVar, model: &str) -> Option<&VariableSeries> {
let api_name = var.as_api_str();
find_by_descriptor_for_model(
&self.variables,
&VariableDescriptor::from_api_name(&api_name),
model,
)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct MonthlyData {
pub time: Vec<OffsetDateTime>,
pub variables: Vec<VariableSeries>,
}
impl MonthlyData {
pub fn get(&self, api_name: &str) -> Option<&VariableSeries> {
find_by_api_name(&self.variables, api_name)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub struct VariableSeries {
pub descriptor: VariableDescriptor,
pub unit: Option<String>,
pub values: SeriesValues,
}
impl VariableSeries {
pub fn values_f32(&self) -> Option<&[Option<f32>]> {
match &self.values {
SeriesValues::F32(values) => Some(values),
SeriesValues::Strings(_) => None,
}
}
pub fn values_str(&self) -> Option<&[Option<String>]> {
match &self.values {
SeriesValues::F32(_) => None,
SeriesValues::Strings(values) => Some(values),
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[non_exhaustive]
pub enum SeriesValues {
F32(Vec<Option<f32>>),
Strings(Vec<Option<String>>),
}
impl SeriesValues {
pub(crate) fn len(&self) -> usize {
match self {
Self::F32(values) => values.len(),
Self::Strings(values) => values.len(),
}
}
}
fn find_by_api_name<'a>(
variables: &'a [VariableSeries],
api_name: &str,
) -> Option<&'a VariableSeries> {
variables
.iter()
.find(|series| series.descriptor.api_name == api_name)
}
fn find_unique_by_descriptor<'a>(
variables: &'a [VariableSeries],
descriptor: &VariableDescriptor,
) -> Option<&'a VariableSeries> {
let mut matches = variables
.iter()
.filter(|series| descriptors_match_for_typed_lookup(&series.descriptor, descriptor));
let first = matches.next()?;
if matches.next().is_some() {
None
} else {
Some(first)
}
}
fn find_by_descriptor_for_model<'a>(
variables: &'a [VariableSeries],
descriptor: &VariableDescriptor,
model: &str,
) -> Option<&'a VariableSeries> {
variables.iter().find(|series| {
series.descriptor.model.as_deref() == Some(model)
&& descriptors_match_for_typed_lookup(&series.descriptor, descriptor)
})
}
fn descriptors_match_for_typed_lookup(
actual: &VariableDescriptor,
expected: &VariableDescriptor,
) -> bool {
model_stripped_api_name(&actual.api_name) == expected.api_name
&& actual.variable == expected.variable
&& actual.altitude == expected.altitude
&& actual.pressure_level == expected.pressure_level
&& actual.depth == expected.depth
&& actual.depth_to == expected.depth_to
&& actual.aggregation == expected.aggregation
&& actual.previous_day == expected.previous_day
&& actual.ensemble_member == expected.ensemble_member
}
fn model_stripped_api_name(api_name: &str) -> &str {
let descriptor = VariableDescriptor::from_api_name(api_name);
if let Some(model) = descriptor.model.as_deref()
&& let Some(base) = api_name.strip_suffix(model)
&& let Some(base) = base.strip_suffix('_')
{
return base;
}
api_name
}
fn value_to_bool(value: f32) -> bool {
value != 0.0
}