use chrono::{DateTime, Duration, TimeZone, Utc};
use crate::utils::convert::{read_u16_from_bytes, read_u24_from_bytes};
#[derive(Debug, Clone)]
pub struct Grib1ProductDefinitionSection {
data: Vec<u8>,
}
impl Grib1ProductDefinitionSection {
pub fn from_data(data: &[u8]) -> Result<Self, String> {
if data.len() < 28 {
return Err("PDS too short".to_string());
}
Ok(Grib1ProductDefinitionSection {
data: data.to_vec(),
})
}
pub fn length(&self) -> usize {
read_u24_from_bytes(&self.data, 0).unwrap_or(0) as usize
}
pub fn parameter_table_version(&self) -> u8 {
self.data[3]
}
pub fn center_id(&self) -> u8 {
self.data[4]
}
pub fn generating_process_id(&self) -> u8 {
self.data[5]
}
pub fn grid_id(&self) -> u8 {
self.data[6]
}
pub fn has_gds(&self) -> bool {
(self.data[7] & 0x80) != 0
}
pub fn has_bms(&self) -> bool {
(self.data[7] & 0x40) != 0
}
pub fn parameter(&self) -> u8 {
self.data[8]
}
pub fn level_type(&self) -> u8 {
self.data[9]
}
pub fn level_value(&self) -> u16 {
read_u16_from_bytes(&self.data, 10).unwrap_or(0)
}
pub fn level_1(&self) -> u8 {
self.data[10]
}
pub fn level_2(&self) -> u8 {
self.data[11]
}
pub fn year_of_century(&self) -> u8 {
self.data[12]
}
pub fn month(&self) -> u8 {
self.data[13]
}
pub fn day(&self) -> u8 {
self.data[14]
}
pub fn hour(&self) -> u8 {
self.data[15]
}
pub fn minute(&self) -> u8 {
self.data[16]
}
pub fn reference_datetime(&self) -> Result<DateTime<Utc>, String> {
let year_of_century = self.year_of_century() as i32;
let month = self.month() as u32;
let day = self.day() as u32;
let hour = self.hour() as u32;
let minute = self.minute() as u32;
let year = if year_of_century >= 0 && year_of_century <= 99 {
if year_of_century <= 49 {
2000 + year_of_century
} else {
1900 + year_of_century
}
} else {
return Err(format!("Invalid year of century: {}", year_of_century));
};
Utc.with_ymd_and_hms(year, month, day, hour, minute, 0)
.single()
.ok_or_else(|| format!("Invalid date/time: {}-{:02}-{:02} {:02}:{:02}", year, month, day, hour, minute))
}
pub fn time_unit(&self) -> u8 {
self.data[17]
}
pub fn p1(&self) -> u8 {
self.data[18]
}
pub fn p2(&self) -> u8 {
self.data[19]
}
pub fn time_range_indicator(&self) -> u8 {
self.data[20]
}
pub fn forecast_datetime(&self) -> Result<DateTime<Utc>, String> {
let reference = self.reference_datetime()?;
let duration = self.forecast_duration()?;
Ok(reference + duration)
}
pub fn forecast_duration(&self) -> Result<Duration, String> {
let time_unit = self.time_unit();
let p1 = self.p1() as i64;
let time_range = self.time_range_indicator();
let unit_duration = match time_unit {
0 => Duration::minutes(1),
1 => Duration::hours(1),
2 => Duration::days(1),
3 => Duration::days(30), 4 => Duration::days(365), 10 => Duration::hours(3),
11 => Duration::hours(6),
12 => Duration::hours(12),
13 => Duration::seconds(1),
_ => return Err(format!("Unsupported time unit: {}", time_unit)),
};
match time_range {
0 | 1 => Ok(unit_duration * p1 as i32),
_ => Ok(unit_duration * p1 as i32), }
}
pub fn sub_center_id(&self) -> Option<u8> {
if self.length() > 28 {
Some(self.data[25])
} else {
None
}
}
pub fn decimal_scale_factor(&self) -> i16 {
if self.length() >= 28 {
read_u16_from_bytes(&self.data, 26)
.map(|v| v as i16)
.unwrap_or(0)
} else {
0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pds_parsing() {
let mut data = vec![0u8; 28];
data[0..3].copy_from_slice(&[0x00, 0x00, 0x1c]); data[3] = 3; data[4] = 98; data[8] = 11; data[9] = 100; data[10] = 2; data[11] = 0x32; data[12] = 23; data[13] = 11; data[14] = 4; data[15] = 12; data[16] = 0; data[17] = 1; data[18] = 6;
let pds = Grib1ProductDefinitionSection::from_data(&data).unwrap();
assert_eq!(pds.center_id(), 98);
assert_eq!(pds.parameter(), 11);
assert_eq!(pds.level_type(), 100);
assert_eq!(pds.level_value(), 0x0232);
}
}