use super::{
binary_data::Grib1BinaryDataSection,
bitmap::Grib1BitmapSection,
grid_description::Grib1Grid,
indicator::Grib1IndicatorSection,
parameters::{get_level_type_info, get_parameter},
product_definition::Grib1ProductDefinitionSection,
};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct Grib1Message {
pds: Grib1ProductDefinitionSection,
grid: Option<Grib1Grid>,
bitmap: Option<Grib1BitmapSection>,
bds: Grib1BinaryDataSection,
_data_offset: usize, message_length: usize, }
impl Grib1Message {
pub fn from_data(data: &[u8], offset: usize) -> Result<Self, String> {
if data.len() < offset + 8 {
return Err("Data too short for GRIB1 message".to_string());
}
let indicator = Grib1IndicatorSection::from_data(&data[offset..])?;
let total_length = indicator.total_length();
if data.len() < offset + total_length {
return Err("Incomplete GRIB1 message".to_string());
}
let msg_data = &data[offset..offset + total_length];
let mut pos = 8;
if pos + 3 > msg_data.len() {
return Err("Message too short for PDS".to_string());
}
let pds_length = read_24bit(&msg_data[pos..pos + 3]);
let pds = Grib1ProductDefinitionSection::from_data(&msg_data[pos..pos + pds_length])?;
pos += pds_length;
let grid = if pds.has_gds() {
if pos + 3 > msg_data.len() {
return Err("Message too short for GDS".to_string());
}
let gds_length = read_24bit(&msg_data[pos..pos + 3]);
let grid = Grib1Grid::from_data(&msg_data[pos..pos + gds_length])?;
pos += gds_length;
Some(grid)
} else {
None
};
let bitmap = if pds.has_bms() {
if pos + 3 > msg_data.len() {
return Err("Message too short for BMS".to_string());
}
let bms_length = read_24bit(&msg_data[pos..pos + 3]);
let bitmap = Grib1BitmapSection::from_data(&msg_data[pos..pos + bms_length])?;
pos += bms_length;
Some(bitmap)
} else {
None
};
if pos + 3 > msg_data.len() {
return Err("Message too short for BDS".to_string());
}
let bds_length = read_24bit(&msg_data[pos..pos + 3]);
if pos + bds_length > msg_data.len() {
return Err("Incomplete BDS".to_string());
}
let bds = Grib1BinaryDataSection::from_data(&msg_data[pos..pos + bds_length])?;
Ok(Grib1Message {
pds,
grid,
bitmap,
bds,
_data_offset: offset,
message_length: total_length,
})
}
pub fn length(&self) -> usize {
self.message_length
}
pub fn reference_datetime(&self) -> Result<DateTime<Utc>, String> {
self.pds.reference_datetime()
}
pub fn forecast_datetime(&self) -> Result<DateTime<Utc>, String> {
self.pds.forecast_datetime()
}
pub fn parameter(&self) -> Option<(String, String, String)> {
let center = self.pds.center_id();
let table_version = self.pds.parameter_table_version();
let param_num = self.pds.parameter();
get_parameter(center, table_version, param_num).map(|p| {
(
p.abbreviation.to_string(),
p.name.to_string(),
p.units.to_string(),
)
})
}
pub fn level_info(&self) -> (String, f64, String) {
let level_type = self.pds.level_type();
let (type_name, units) = get_level_type_info(level_type);
let level_value = match level_type {
100 => {
self.pds.level_value() as f64
}
111 => {
self.pds.level_1() as f64
}
112 => {
self.pds.level_1() as f64
}
103 | 105 => {
self.pds.level_value() as f64
}
_ => {
if self.pds.level_1() != 0 || self.pds.level_2() != 0 {
(self.pds.level_1() as u16 * 256 + self.pds.level_2() as u16) as f64
} else {
0.0
}
}
};
(type_name.to_string(), level_value, units.to_string())
}
pub fn grid_shape(&self) -> (usize, usize) {
self.grid.as_ref().map(|g| g.dimensions()).unwrap_or((0, 0))
}
pub fn grid(&self) -> Option<&Grib1Grid> {
self.grid.as_ref()
}
pub fn latitudes(&self) -> Vec<f64> {
self.grid
.as_ref()
.map(|g| g.latitudes())
.unwrap_or_default()
}
pub fn longitudes(&self) -> Vec<f64> {
self.grid
.as_ref()
.map(|g| g.longitudes())
.unwrap_or_default()
}
pub fn data(&self) -> Result<Vec<f64>, String> {
let (ni, nj) = self.grid_shape();
let num_points = ni * nj;
if num_points == 0 {
return Err("Grid dimensions not available".to_string());
}
let bitmap_vec: Option<Vec<bool>> = if let Some(ref bms) = self.bitmap {
Some((0..num_points).map(|i| bms.is_valid(i)).collect())
} else {
None
};
let mut values = self.bds.unpack_data(num_points, bitmap_vec.as_deref())?;
let decimal_scale = self.pds.decimal_scale_factor();
if decimal_scale != 0 {
let scale_divisor = 10_f64.powi(decimal_scale as i32);
for value in values.iter_mut() {
if !value.is_nan() {
*value /= scale_divisor;
}
}
}
Ok(values)
}
pub fn center_id(&self) -> u8 {
self.pds.center_id()
}
pub fn generating_process_id(&self) -> u8 {
self.pds.generating_process_id()
}
pub fn is_ecmwf(&self) -> bool {
self.center_id() == 98
}
}
fn read_24bit(data: &[u8]) -> usize {
((data[0] as usize) << 16) | ((data[1] as usize) << 8) | (data[2] as usize)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_24bit() {
let data = [0x00, 0x01, 0x00];
assert_eq!(read_24bit(&data), 256);
let data = [0x01, 0x00, 0x00];
assert_eq!(read_24bit(&data), 65536);
}
}