use std::collections::HashMap;
use chrono::{DateTime, Utc};
use crate::{
error::GribberishError,
message::{Message, MessageIterator},
templates::product::tables::{
DerivedForecastType, FixedSurfaceType, GeneratingProcess, ProbabilityType, TimeUnit,
TypeOfStatisticalProcessing,
},
utils::iter::projection::LatLngProjection,
};
#[derive(Clone, Debug)]
pub struct MessageMetadata {
pub key: String,
pub byte_offset: usize,
pub message_size: usize,
pub var: String,
pub name: String,
pub units: String,
pub generating_process: GeneratingProcess,
pub statistical_process: Option<TypeOfStatisticalProcessing>,
pub time_unit: TimeUnit,
pub time_increment_unit: Option<TimeUnit>,
pub time_interval: u32,
pub time_increment_interval: Option<u32>,
pub first_fixed_surface_type: FixedSurfaceType,
pub first_fixed_surface_value: Option<f64>,
pub second_fixed_surface_type: FixedSurfaceType,
pub second_fixed_surface_value: Option<f64>,
pub discipline: String,
pub category: String,
pub discipline_value: u8,
pub category_value: u8,
pub parameter_value: u8,
pub data_compression: String,
pub has_bitmap: bool,
pub reference_date: DateTime<Utc>,
pub forecast_date: DateTime<Utc>,
pub forecast_end_date: Option<DateTime<Utc>>,
pub proj: String,
pub crs: String,
pub is_regular_grid: bool,
pub grid_shape: (usize, usize),
pub projector: LatLngProjection,
pub perturbation_number: Option<u8>,
pub number_of_ensemble_members: Option<u8>,
pub derived_forecast_type: Option<DerivedForecastType>,
pub percentile_value: Option<u8>,
pub probability_type: Option<ProbabilityType>,
pub forecast_probability_number: Option<u8>,
pub probability_lower_limit: Option<f64>,
pub probability_upper_limit: Option<f64>,
}
impl MessageMetadata {
pub fn data_point_count(&self) -> usize {
self.grid_shape.0 * self.grid_shape.1
}
pub fn latlng(&self) -> (Vec<f64>, Vec<f64>) {
self.projector.lat_lng()
}
pub fn xy(&self) -> (Vec<f64>, Vec<f64>) {
(self.projector.x(), self.projector.y())
}
pub fn as_idx(&self, index: usize) -> String {
let formatted_date = self.reference_date.format("%Y%m%d%H").to_string();
let level = if self.first_fixed_surface_type.is_single_level() {
self.first_fixed_surface_type.name().into()
} else {
let level_value = if let Some(level_value) = self.first_fixed_surface_value {
format!("{level_value:.0}")
} else {
"".to_string()
};
if self.first_fixed_surface_type.is_sequence_level() {
format!("{level_value} in {}", self.first_fixed_surface_type.name())
} else {
let level_unit = if !self.first_fixed_surface_type.unit().is_empty() {
format!(" {}", self.first_fixed_surface_type.unit())
} else {
"".to_string()
};
format!(
"{level_value}{level_unit} {}",
self.first_fixed_surface_type.name()
)
}
};
let statistical_process =
if let Some(statistical_process) = self.statistical_process.as_ref() {
format!("{} ", statistical_process.abbv())
} else {
"".to_string()
};
let time_offset = if let Some(time_increment_interval) = self.time_increment_interval {
format!("{}-{} ", self.time_interval, time_increment_interval)
} else {
format!("{} ", self.time_interval)
};
let time_unit = format!("{} ", self.time_unit);
format!(
"{index}:{byte_offset}:d={formatted_date}:{var}:{level}:{time_offset}{time_unit}{statistical_process}{generating_process}:",
index = index + 1,
byte_offset = self.byte_offset,
var = self.var,
generating_process = self.generating_process.abbv(),
)
}
}
impl<'a> TryFrom<&Message<'a>> for MessageMetadata {
type Error = GribberishError;
fn try_from(message: &Message<'a>) -> Result<Self, Self::Error> {
let (first_fixed_surface_type, first_fixed_surface_value) =
message.first_fixed_surface()?;
let (second_fixed_surface_type, second_fixed_surface_value) =
message.second_fixed_surface()?;
Ok(MessageMetadata {
key: message.key()?,
byte_offset: message.byte_offset(),
message_size: message.len(),
var: message.variable_abbrev()?,
name: message.variable_name()?,
units: message.unit()?,
generating_process: message.generating_process()?,
statistical_process: message.statistical_process_type()?,
time_unit: message.time_unit()?,
time_increment_unit: message.time_increment_unit()?,
time_interval: message.time_interval()?,
time_increment_interval: message.time_increment_interval()?,
first_fixed_surface_type,
first_fixed_surface_value,
second_fixed_surface_type,
second_fixed_surface_value,
discipline: message.discipline()?.to_string(),
category: message.category()?,
discipline_value: message.discipline_value()?,
category_value: message.category_value()?,
parameter_value: message.parameter_value()?,
data_compression: format!(
"{}: {}",
message.data_template_number().unwrap_or(99),
message
.data_compression_type()
.unwrap_or("Unknown".to_string())
),
has_bitmap: message.has_bitmap(),
reference_date: message.reference_date()?,
forecast_date: message.forecast_date()?,
forecast_end_date: message.forecast_end_date()?,
proj: message.proj_string()?,
crs: message.crs()?,
is_regular_grid: message.is_regular_grid()?,
grid_shape: message.grid_dimensions()?,
projector: message.latlng_projector()?,
perturbation_number: message.perturbation_number()?,
number_of_ensemble_members: message.number_of_ensemble_members()?,
derived_forecast_type: message.derived_forecast_type()?,
percentile_value: message.percentile_value()?,
probability_type: message.probability_type()?,
forecast_probability_number: message.forecast_probability_number()?,
probability_lower_limit: message.probability_lower_limit()?,
probability_upper_limit: message.probability_upper_limit()?,
})
}
}
pub fn scan_message_metadata(data: &[u8]) -> HashMap<String, (usize, usize, MessageMetadata)> {
let message_iter = MessageIterator::from_data(data, 0);
message_iter
.enumerate()
.filter_map(|(index, m)| match MessageMetadata::try_from(&m) {
Ok(mm) => Some((mm.key.clone(), (index, m.byte_offset(), mm))),
Err(_) => None,
})
.collect()
}