use std::path::Path;
#[derive(Debug, Clone)]
pub struct MessageSummary {
pub message_index: usize,
pub key: String,
pub forecast_time: Option<i32>,
pub repr_template: Option<u16>,
pub payload_len: usize,
pub decode_ok: bool,
pub decoded_samples_len: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct FileSummary {
pub path: std::path::PathBuf,
pub n_messages: usize,
pub messages: Vec<MessageSummary>,
}
#[derive(Debug, Clone, Copy)]
pub enum MatchReason {
Token,
NormParam,
Section4,
}
pub mod param_alias;
#[allow(dead_code)]
fn extract_tag_value_case_insensitive(desc: &str, tags: &[&str]) -> Option<String> {
for line in desc.lines() {
let s = line.trim();
for tag in tags {
let t = tag.to_lowercase();
let key = t.clone() + ":";
let key_eq = t.clone() + "=";
let s_l = s.to_lowercase();
if s_l.starts_with(&key) || s_l.starts_with(&key_eq) {
if let Some(idx) = s.find(':') {
let v = s[idx + 1..].trim();
return Some(v.to_string());
}
if let Some(idx) = s.find('=') {
let v = s[idx + 1..].trim();
return Some(v.to_string());
}
}
if s_l.starts_with(&format!("{} ", t)) {
let v = s[t.len()..].trim();
return Some(v.to_string());
}
let patterns = [format!("{} =", t.clone()), format!("{}:", t)];
for pat in patterns.iter() {
if let Some(pos) = s_l.find(pat) {
let v = s[pos + pat.len()..].trim();
return Some(v.to_string());
}
}
}
}
None
}
#[allow(dead_code)]
fn parse_first_int_from_str(s: &str) -> Option<i32> {
let s = s.trim();
let mut num_str = String::new();
for c in s.chars() {
if c.is_ascii_digit() || (num_str.is_empty() && (c == '+' || c == '-')) {
num_str.push(c);
} else if !num_str.is_empty() {
break;
}
}
if num_str.is_empty() { None } else { num_str.parse::<i32>().ok() }
}
pub fn read_grib2_section7_payloads_by_message(path: &Path) -> Result<Vec<Vec<u8>>, String> {
use std::fs;
use memchr::memmem;
let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
let mut out: Vec<Vec<u8>> = Vec::new();
let mut pos = 0usize;
while pos + 16 <= data.len() {
if &data[pos..pos + 4] != b"GRIB" {
if let Some(next) = memmem::find(&data[pos + 1..], b"GRIB") {
pos = pos + 1 + next;
} else {
break;
}
continue;
}
if data[pos + 7] != 2 {
pos += 4;
continue;
}
let mut off = pos + 16;
let mut payload_found = false;
loop {
if off + 5 > data.len() {
break; }
let len = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
let sect_num = data[off + 4];
if len < 5 { break; }
let payload_len = len - 5;
let payload_off = off + 5;
if payload_off + payload_len > data.len() { break; }
if sect_num == 7 {
out.push(data[payload_off..payload_off + payload_len].to_vec());
payload_found = true;
}
off = payload_off + payload_len;
if off + 4 <= data.len() && &data[off..off + 4] == b"7777" {
off += 4;
break;
}
}
if !payload_found {
out.push(Vec::new());
}
pos = if off > pos { off } else { pos + 4 };
}
Ok(out)
}
pub struct Template42Params {
pub num_points: usize,
pub bits_per_sample: u8,
pub block_size: u32,
pub rsi: u32,
pub flags_mask: u32,
pub exp: i32,
pub dec: i32,
pub ref_val: f32,
}
pub fn read_grib2_section5_template42_params_by_message(path: &Path) -> Result<Vec<Option<Template42Params>>, String> {
#[cfg(feature = "grib-support")]
{
use std::fs::File;
use std::io::BufReader;
use grib::def::grib2::DataRepresentationTemplate;
let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let r = BufReader::new(f);
let grib2 = grib::from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
let mut out: Vec<Option<Template42Params>> = Vec::new();
for ((_msg_idx, _submsg_idx), submsg) in grib2.iter() {
match submsg.section5() {
Ok(s5) => match s5.payload.template {
DataRepresentationTemplate::_5_42(t) => {
let params = Template42Params {
num_points: s5.payload.num_encoded_points as usize,
bits_per_sample: t.simple.num_bits as u8,
block_size: t.block_size as u32,
rsi: t.ref_sample_interval as u32,
flags_mask: t.mask as u32,
exp: t.simple.exp as i32,
dec: t.simple.dec as i32,
ref_val: t.simple.ref_val as f32,
};
out.push(Some(params));
}
_ => out.push(None),
},
Err(_) => out.push(None),
}
}
Ok(out)
}
#[cfg(not(feature = "grib-support"))]
{
use std::fs;
let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
let mut out: Vec<Option<Template42Params>> = Vec::new();
let mut pos = 0usize;
while pos + 16 <= data.len() {
if &data[pos..pos + 4] != b"GRIB" {
if let Some(next) = memchr::memmem::find(&data[pos + 1..], b"GRIB") {
pos = pos + 1 + next;
} else {
break;
}
continue;
}
if data[pos + 7] != 2 {
pos += 4;
continue;
}
let mut off = pos + 16;
let mut found_s5 = false;
let mut s5_params: Option<Template42Params> = None;
loop {
if off + 5 > data.len() { break; }
let len = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
let sect_num = data[off + 4];
if len < 5 { break; }
let payload_len = len - 5;
let payload_off = off + 5;
if payload_off + payload_len > data.len() { break; }
if sect_num == 5 {
if payload_len >= 6 {
let _num_points = u32::from_be_bytes([
data[payload_off],
data[payload_off + 1],
data[payload_off + 2],
data[payload_off + 3],
]) as usize;
let tmpl_num = u16::from_be_bytes([data[payload_off + 4], data[payload_off + 5]]);
if tmpl_num == 42 {
let mut cursor = payload_off + 6;
if cursor + 4 <= payload_off + payload_len {
let num_encoded_points = u32::from_be_bytes([
data[cursor],
data[cursor + 1],
data[cursor + 2],
data[cursor + 3],
]) as usize;
cursor += 4;
if cursor + 1 > payload_off + payload_len { break; }
let num_bits = data[cursor] as u8; cursor += 1;
if cursor + 4 > payload_off + payload_len { break; }
let block_size = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
if cursor + 4 > payload_off + payload_len { break; }
let rsi = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
if cursor + 4 > payload_off + payload_len { break; }
let mask = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
if cursor + 2 + 2 + 4 > payload_off + payload_len { break; }
let exp = i16::from_be_bytes([data[cursor], data[cursor + 1]]) as i32; cursor += 2;
let dec = i16::from_be_bytes([data[cursor], data[cursor + 1]]) as i32; cursor += 2;
let ref_val = f32::from_bits(u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]));
s5_params = Some(Template42Params {
num_points: num_encoded_points,
bits_per_sample: num_bits,
block_size,
rsi,
flags_mask: mask,
exp,
dec,
ref_val,
});
}
}
}
found_s5 = true;
}
off = payload_off + payload_len;
if off + 4 <= data.len() && &data[off..off + 4] == b"7777" { off += 4; break; }
}
if found_s5 {
out.push(s5_params);
} else {
out.push(None);
}
pos = if off > pos { off } else { pos + 4 };
}
return Ok(out);
}
}
pub fn read_grib2_section4_parameter_ids_by_message(path: &Path) -> Result<Vec<Option<(u8,u8,u8)>>, String> {
#[cfg(feature = "grib-support")]
{
use std::fs::File;
use std::io::BufReader;
use grib::from_reader;
let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let r = BufReader::new(f);
let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
let mut out: Vec<Option<(u8,u8,u8)>> = Vec::new();
for ((_msg_idx, _submsg_idx), submsg) in grib2.iter() {
let desc = submsg.describe();
let discipline = extract_tag_value_case_insensitive(&desc, &["discipline"]).and_then(|s| s.parse::<u8>().ok());
let category = extract_tag_value_case_insensitive(&desc, &["parametercategory", "parameter category", "paramcategory", "category"]).and_then(|s| s.parse::<u8>().ok());
let number = extract_tag_value_case_insensitive(&desc, &["parameternumber", "parameter number", "paramnumber", "number"]).and_then(|s| s.parse::<u8>().ok());
if discipline.is_some() || category.is_some() || number.is_some() {
let d = discipline.unwrap_or(0);
let c = category.unwrap_or(0);
let n = number.unwrap_or(0);
out.push(Some((d,c,n)));
} else {
out.push(None);
}
}
return Ok(out);
}
#[cfg(not(feature = "grib-support"))]
{
use std::fs;
let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
let mut out: Vec<Option<(u8,u8,u8)>> = Vec::new();
let mut pos = 0usize;
while pos + 16 <= data.len() {
if &data[pos..pos + 4] != b"GRIB" {
if let Some(next) = memchr::memmem::find(&data[pos + 1..], b"GRIB") {
pos = pos + 1 + next;
} else { break; }
continue;
}
if data[pos + 7] != 2 {
pos += 4;
continue;
}
let mut off = pos + 16;
let mut found_s4: Option<(u8,u8,u8)> = None;
loop {
if off + 5 > data.len() { break; }
let len = u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]) as usize;
let sect_num = data[off + 4];
if len < 5 { break; }
let payload_len = len - 5;
let payload_off = off + 5;
if payload_off + payload_len > data.len() { break; }
if sect_num == 4 {
if payload_len >= 10 {
let d = data[payload_off];
let c = data[payload_off + 8];
let n = data[payload_off + 9];
found_s4 = Some((d, c, n));
}
}
off = payload_off + payload_len;
if off + 4 <= data.len() && &data[off..off+4] == b"7777" { off += 4; break; }
}
out.push(found_s4);
pos = if off > pos { off } else { pos + 4 };
}
return Ok(out);
}
}
pub fn decode_template42_rust_from_params_with_payload(
payload: &[u8],
width: usize,
height: usize,
num_points: usize,
bits_per_sample: u8,
block_size: u32,
rsi: u32,
flags_mask: u32,
exp: i32,
dec: i32,
ref_val: f32,
) -> Result<Vec<f32>, String> {
use rust_aec::{flags_from_grib2_ccsds_flags, params::AecParams};
let expected_points = width.saturating_mul(height);
if expected_points != 0 && num_points != expected_points {
return Err(format!(
"template42(rust-aec) only supports non-bitmap regular grids: num_points={} grid={}x{}={}",
num_points, width, height, expected_points
));
}
let ccsds_flags: u8 = (flags_mask & 0xff) as u8;
let flags = flags_from_grib2_ccsds_flags(ccsds_flags);
let params = AecParams::new(bits_per_sample, block_size, rsi, flags);
let decoded_bytes = rust_aec::decode(payload, params, num_points)
.map_err(|e| format!("rust-aec decode failed: {e}"))?;
let bits_u32 = bits_per_sample as u32;
let bytes_per_sample = ((bits_u32 + 7) / 8) as usize;
let msb = flags.contains(rust_aec::params::AecFlags::MSB);
let mask: u32 = if bits_u32 >= 32 { u32::MAX } else { (1u32 << bits_u32) - 1 };
let bscale = 2.0f32.powi(exp);
let dscale = 10.0f32.powi(-dec);
let mut values = Vec::with_capacity(num_points);
for i in 0..num_points {
let start = i * bytes_per_sample;
let sample_bytes = &decoded_bytes[start..start + bytes_per_sample];
let raw = if msb {
sample_bytes.iter().fold(0u32, |acc, &b| (acc << 8) | (b as u32))
} else {
sample_bytes
.iter()
.enumerate()
.fold(0u32, |acc, (j, &b)| acc | ((b as u32) << (j * 8)))
} & mask;
let v = (ref_val + (raw as f32) * bscale) * dscale;
values.push(v);
}
Ok(values)
}
pub fn decode_template42_rust_from_params(
path: &Path,
width: usize,
height: usize,
num_points: usize,
bits_per_sample: u8,
block_size: u32,
rsi: u32,
flags_mask: u32,
exp: i32,
dec: i32,
ref_val: f32,
) -> Result<Vec<f32>, String> {
let payload = read_first_grib2_section7_payload(path)?;
decode_template42_rust_from_params_with_payload(
&payload,
width,
height,
num_points,
bits_per_sample,
block_size,
rsi,
flags_mask,
exp,
dec,
ref_val,
)
}
pub fn read_first_grib2_section7_payload(path: &Path) -> Result<Vec<u8>, String> {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let mut s0 = [0u8; 16];
f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
if &s0[0..4] != b"GRIB" {
return Err("not a GRIB file".to_string());
}
if s0[7] != 2 {
return Err(format!("not GRIB2 (edition={})", s0[7]));
}
loop {
let mut header = [0u8; 5];
f.read_exact(&mut header).map_err(|e| format!("read section header failed: {e}"))?;
let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
let sect_num = header[4];
if len < 5 {
return Err(format!("invalid section length: {len} (section {sect_num})"));
}
let payload_len = len - 5;
if sect_num == 7 {
let mut payload = vec![0u8; payload_len];
f.read_exact(&mut payload).map_err(|e| format!("read Section7 payload failed: {e}"))?;
return Ok(payload);
}
let mut skip = vec![0u8; payload_len];
f.read_exact(&mut skip).map_err(|e| format!("skip failed: {e}"))?;
}
}
pub fn detect_grib_edition(path: &Path) -> Result<u8, String> {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let mut s0 = [0u8; 16];
f.read_exact(&mut s0).map_err(|e| format!("read header failed: {e}"))?;
if &s0[0..4] != b"GRIB" {
return Err("not a GRIB file".to_string());
}
Ok(s0[7])
}
pub fn has_grib_support() -> bool {
cfg!(feature = "grib-support")
}
#[derive(Debug, Clone)]
pub struct Grib2LatLonGrid {
pub ni: usize,
pub nj: usize,
pub lat1_deg: f64,
pub lon1_deg: f64,
pub di_deg: f64,
pub dj_deg: f64,
pub scan_mode: u8,
}
pub fn read_first_grib2_latlon_grid(path: &Path) -> Result<Grib2LatLonGrid, String> {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let mut s0 = [0u8; 16];
f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
if &s0[0..4] != b"GRIB" {
return Err("not a GRIB file".to_string());
}
if s0[7] != 2 {
return Err(format!("not GRIB2 (edition={})", s0[7]));
}
loop {
let mut header = [0u8; 5];
f.read_exact(&mut header).map_err(|e| format!("read section header failed: {e}"))?;
let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
let sect_num = header[4];
if len < 5 {
return Err(format!("invalid section length: {len} (section {sect_num})"));
}
let payload_len = len - 5;
let mut payload = vec![0u8; payload_len];
f.read_exact(&mut payload).map_err(|e| format!("read payload failed: {e}"))?;
if sect_num == 3 {
if payload.len() < 9 {
return Err("GRIB2: Section3 too short".to_string());
}
let grid_tmpl = u16::from_be_bytes([payload[7], payload[8]]);
if grid_tmpl != 0 {
return Err(format!("GRIB2: only grid template 3.0 (regular lat/lon) supported; got 3.{grid_tmpl}"));
}
let t = &payload[9..];
if t.len() < 58 {
return Err(format!("GRIB2: grid template 3.0 too short (need >=58 bytes, got {})", t.len()));
}
const MISSING_U32: u32 = 0xFFFF_FFFF;
let ni_u32 = u32::from_be_bytes([t[16], t[17], t[18], t[19]]);
let nj_u32 = u32::from_be_bytes([t[20], t[21], t[22], t[23]]);
if ni_u32 == MISSING_U32 || nj_u32 == MISSING_U32 {
return Err("GRIB2: template 3.0 has missing Ni/Nj or irregular grid".to_string());
}
let ni = ni_u32 as usize;
let nj = nj_u32 as usize;
let basic_angle = u32::from_be_bytes([t[24], t[25], t[26], t[27]]);
let subdivisions = u32::from_be_bytes([t[28], t[29], t[30], t[31]]);
let unit_deg: f64 = if (basic_angle == 0 && subdivisions == 0) || (basic_angle == MISSING_U32 && subdivisions == MISSING_U32) {
1e-6
} else {
(basic_angle as f64) / (subdivisions as f64)
};
let mut lat1 = i32::from_be_bytes([t[32], t[33], t[34], t[35]]) as f64 * unit_deg;
let mut lon1 = i32::from_be_bytes([t[36], t[37], t[38], t[39]]) as f64 * unit_deg;
let lat2 = i32::from_be_bytes([t[41], t[42], t[43], t[44]]) as f64 * unit_deg;
let lon2 = i32::from_be_bytes([t[45], t[46], t[47], t[48]]) as f64 * unit_deg;
let mut di = u32::from_be_bytes([t[49], t[50], t[51], t[52]]) as f64 * unit_deg;
let mut dj = u32::from_be_bytes([t[53], t[54], t[55], t[56]]) as f64 * unit_deg;
let scan_mode = t[57];
if (di == 0.0 || dj == 0.0) && (ni > 1 && nj > 1) {
if dj == 0.0 {
dj = (lat1 - lat2) / ((nj.saturating_sub(1)) as f64);
}
if di == 0.0 {
let mut dlon = lon2 - lon1;
while dlon <= -180.0 { dlon += 360.0; }
while dlon > 180.0 { dlon -= 360.0; }
di = dlon / ((ni.saturating_sub(1)) as f64);
}
}
if (di == 0.0 || dj == 0.0) && (ni > 1 && nj > 1) {
di = 360.0 / (ni as f64);
dj = 180.0 / (nj as f64);
if lat1 == 0.0 && lon1 == 0.0 {
lat1 = 90.0;
lon1 = -180.0;
}
}
let _suggested_lat = lat1 - ((nj.saturating_sub(1) as f64) * dj / 2.0);
let _suggested_lon = normalize_lon_deg(lon1 + ((ni.saturating_sub(1) as f64) * di / 2.0));
return Ok(Grib2LatLonGrid {
ni,
nj,
lat1_deg: lat1,
lon1_deg: normalize_lon_deg(lon1),
di_deg: di,
dj_deg: dj,
scan_mode,
});
}
}
}
fn normalize_lon_deg(mut lon: f64) -> f64 {
while lon <= -180.0 { lon += 360.0; }
while lon > 180.0 { lon -= 360.0; }
lon
}
pub fn read_all_grib2_section7_payloads(path: &Path) -> Result<Vec<Vec<u8>>, String> {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let mut s0 = [0u8; 16];
f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
if &s0[0..4] != b"GRIB" {
return Err("not a GRIB file".to_string());
}
if s0[7] != 2 {
return Err(format!("not GRIB2 (edition={})", s0[7]));
}
let mut out = Vec::new();
loop {
let mut header = [0u8; 5];
match f.read_exact(&mut header) {
Ok(()) => {}
Err(e) => {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
break;
}
return Err(format!("read section header failed: {e}"));
}
}
let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
let sect_num = header[4];
if len < 5 {
return Err(format!("invalid section length: {len} (section {sect_num})"));
}
let payload_len = len - 5;
if sect_num == 7 {
let mut payload = vec![0u8; payload_len];
match f.read_exact(&mut payload) {
Ok(()) => {
out.push(payload);
continue;
}
Err(e) => {
return Err(format!("read Section7 payload failed: {e}"));
}
}
}
let mut skip = vec![0u8; payload_len];
match f.read_exact(&mut skip) {
Ok(()) => {}
Err(_) => {
break;
}
}
}
Ok(out)
}
pub fn decode_template42_from_payload_list(
payloads: &[&[u8]],
params_list: &[Option<Template42Params>],
width: usize,
height: usize,
) -> Result<Vec<Option<Vec<f32>>>, String> {
if payloads.len() != params_list.len() {
return Err("payloads and params_list length mismatch".to_string());
}
let mut out: Vec<Option<Vec<f32>>> = Vec::with_capacity(payloads.len());
for (payload, params_opt) in payloads.iter().zip(params_list.iter()) {
if let Some(params) = params_opt {
match decode_template42_rust_from_params_with_payload(
payload,
width,
height,
params.num_points,
params.bits_per_sample,
params.block_size,
params.rsi,
params.flags_mask,
params.exp,
params.dec,
params.ref_val,
) {
Ok(v) => out.push(Some(v)),
Err(_) => out.push(None),
}
} else {
out.push(None);
}
}
Ok(out)
}
pub fn decode_template42_for_file_by_message(
path: &Path,
width: usize,
height: usize,
) -> Result<Vec<Option<Vec<f32>>>, String> {
let payloads_vec = read_grib2_section7_payloads_by_message(path)?;
let params_vec = read_grib2_section5_template42_params_by_message(path)?;
let payload_slices: Vec<&[u8]> = payloads_vec.iter().map(|v| v.as_slice()).collect();
let params_ref: Vec<Option<Template42Params>> = params_vec.into_iter().collect();
decode_template42_from_payload_list(&payload_slices, ¶ms_ref, width, height)
}
pub fn decode_template42_first_message(
path: &Path,
width: usize,
height: usize,
) -> Result<Option<Vec<f32>>, String> {
let payloads_vec = read_grib2_section7_payloads_by_message(path)?;
let params_vec = read_grib2_section5_template42_params_by_message(path)?;
let n = payloads_vec.len().min(params_vec.len());
for i in 0..n {
let payload = &payloads_vec[i];
if payload.is_empty() {
continue;
}
if let Some(ref params) = params_vec[i] {
match decode_template42_rust_from_params_with_payload(
payload.as_slice(),
width,
height,
params.num_points,
params.bits_per_sample,
params.block_size,
params.rsi,
params.flags_mask,
params.exp,
params.dec,
params.ref_val,
) {
Ok(v) => return Ok(Some(v)),
Err(_) => continue, }
}
}
Ok(None)
}
pub fn decode_template42_try_message_payload(
path: &Path,
message_index: usize,
width: usize,
height: usize,
params: &Template42Params,
) -> Result<Vec<f32>, String> {
match read_grib2_section7_payloads_by_message(path) {
Ok(payloads) => {
if let Some(p) = payloads.get(message_index) {
if !p.is_empty() {
return decode_template42_rust_from_params_with_payload(
p.as_slice(),
width,
height,
params.num_points,
params.bits_per_sample,
params.block_size,
params.rsi,
params.flags_mask,
params.exp,
params.dec,
params.ref_val,
);
}
}
}
Err(_) => {
}
}
let payload = read_first_grib2_section7_payload(path)?;
decode_template42_rust_from_params_with_payload(
&payload,
width,
height,
params.num_points,
params.bits_per_sample,
params.block_size,
params.rsi,
params.flags_mask,
params.exp,
params.dec,
params.ref_val,
)
}
pub fn probe_all_grib2_latlon(_path: &Path, _lat_deg: f64, _lon_deg: f64) -> Result<Vec<ProbeField>, String> {
#[cfg(not(feature = "grib-support"))]
{
return Err("probe_all_grib2_latlon requires the 'grib-support' feature to be enabled".to_string());
}
#[cfg(feature = "grib-support")]
{
use std::fs::File;
use std::io::BufReader;
use grib::{from_reader, Grib2SubmessageDecoder};
let path = _path;
let lat_deg = _lat_deg;
let lon_deg = _lon_deg;
let grid = read_first_grib2_latlon_grid(path)?;
if grid.scan_mode != 0 {
return Err(format!("unsupported scanning_mode=0x{:02x}", grid.scan_mode));
}
let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let r = BufReader::new(f);
let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
let decoded_template42_by_message = match decode_template42_for_file_by_message(path, grid.ni, grid.nj) {
Ok(v) => v,
Err(_) => Vec::new(),
};
let mut out: Vec<ProbeField> = Vec::new();
for (idx, ((_msg_idx, _submsg_idx), submessage)) in grib2.iter().enumerate() {
let desc = submessage.describe();
let key = if let Some(sn) = crate::extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
} else {
desc.lines().next().unwrap_or("<desc>").to_string()
};
let ft = crate::extract_tag_value_case_insensitive(&desc, &["forecast time", "forecasttime", "forecast_time", "forecast"]).and_then(|s| crate::parse_first_int_from_str(&s));
let (width, height) = match submessage.grid_shape() {
Ok((w,h)) => (w,h),
Err(_) => continue,
};
if width != grid.ni || height != grid.nj { continue; }
let values_opt = decoded_template42_by_message.get(idx).and_then(|o| o.clone());
let values: Vec<f32> = if let Some(v) = values_opt { v } else {
let template42_params_opt: Option<Template42Params> = match submessage.section5() {
Ok(s5) => match s5.payload.template {
grib::def::grib2::DataRepresentationTemplate::_5_42(t) => Some(Template42Params {
num_points: s5.payload.num_encoded_points as usize,
bits_per_sample: t.simple.num_bits as u8,
block_size: t.block_size as u32,
rsi: t.ref_sample_interval as u32,
flags_mask: t.mask as u32,
exp: t.simple.exp as i32,
dec: t.simple.dec as i32,
ref_val: t.simple.ref_val as f32,
}),
_ => None,
},
Err(_) => None,
};
let decoder = match Grib2SubmessageDecoder::from(submessage) {
Ok(d) => d,
Err(_) => continue,
};
match decoder.dispatch() {
Ok(decoded) => decoded.collect::<Vec<f32>>(),
Err(_) => {
if let Some(params) = template42_params_opt {
match decode_template42_try_message_payload(path, idx, width, height, ¶ms) {
Ok(vv) => vv,
Err(_) => continue,
}
} else {
continue;
}
}
}
};
let mut dlon = lon_deg - grid.lon1_deg;
while dlon < 0.0 { dlon += 360.0; }
while dlon >= 360.0 { dlon -= 360.0; }
let i_f = dlon / grid.di_deg;
let j_f = (grid.lat1_deg - lat_deg) / grid.dj_deg;
if !i_f.is_finite() || !j_f.is_finite() { continue; }
let ni = width as isize; let nj = height as isize;
let i0 = i_f.floor() as isize; let j0 = j_f.floor() as isize; let i1 = i0 + 1; let j1 = j0 + 1;
let dx = i_f - (i0 as f64); let dy = j_f - (j0 as f64);
let i0w = (((i0 % ni) + ni) % ni) as usize; let i1w = (((i1 % ni) + ni) % ni) as usize;
let j0c = j0.clamp(0, nj - 1) as usize; let j1c = j1.clamp(0, nj - 1) as usize;
let idx00 = j0c.saturating_mul(width).saturating_add(i0w);
let idx10 = j0c.saturating_mul(width).saturating_add(i1w);
let idx01 = j1c.saturating_mul(width).saturating_add(i0w);
let idx11 = j1c.saturating_mul(width).saturating_add(i1w);
let s00 = values.get(idx00).copied().unwrap_or(f32::NAN);
let s10 = values.get(idx10).copied().unwrap_or(f32::NAN);
let s01 = values.get(idx01).copied().unwrap_or(f32::NAN);
let s11 = values.get(idx11).copied().unwrap_or(f32::NAN);
if !s00.is_finite() || !s10.is_finite() || !s01.is_finite() || !s11.is_finite() { continue; }
let v0 = s00 * (1.0 - dx as f32) + s10 * (dx as f32);
let v1 = s01 * (1.0 - dx as f32) + s11 * (dx as f32);
let value = v0 * (1.0 - dy as f32) + v1 * (dy as f32);
let glat = grid.lat1_deg - j_f * grid.dj_deg;
let glon = normalize_lon_deg(grid.lon1_deg + i_f * grid.di_deg);
out.push(ProbeField { key, forecast_time: ft, i: i0w, j: j0c, lat: glat, lon: glon, value });
}
Ok(out)
}
}
pub fn probe_all_fields_at_lat_lon(path: &Path, lat_deg: f64, lon_deg: f64) -> Result<Vec<ProbeField>, String> {
match detect_grib_edition(path)? {
2 => probe_all_grib2_latlon(path, lat_deg, lon_deg),
1 => {
let msg = read_grib1_first_message(path)?;
let (grid, _drt) = parse_grib1_latlon_grid(&msg)?;
let decoded = decode_grib1_first_message(&msg)?;
let (i, j, glat, glon) = nearest_latlon_index_for_grib1(lat_deg, lon_deg, &grid)?;
let idx = j.saturating_mul(decoded.width).saturating_add(i);
let val = decoded.values.get(idx).copied().unwrap_or(f32::NAN);
let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
Ok(vec![ProbeField { key, forecast_time: None, i, j, lat: glat, lon: glon, value: val }])
}
other => Err(format!("unsupported GRIB edition {} (only 1/2 supported)", other)),
}
}
pub fn summarize_file(path: &Path) -> Result<FileSummary, String> {
match detect_grib_edition(path)? {
2 => {
#[cfg(not(feature = "grib-support"))]
return Err("summarize_file requires the 'grib-support' feature".to_string());
#[cfg(feature = "grib-support")]
{
use std::fs::File;
use std::io::BufReader;
use grib::from_reader;
let f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
let r = BufReader::new(f);
let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {}", e))?;
let payloads = read_grib2_section7_payloads_by_message(path).unwrap_or_default();
let params_vec = read_grib2_section5_template42_params_by_message(path).unwrap_or_default();
let mut messages: Vec<MessageSummary> = Vec::new();
for (idx, ((_msg_idx, _submsg_idx), sub)) in grib2.iter().enumerate() {
let desc = sub.describe();
let key = if let Some(sn) = extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
} else {
desc.lines().next().unwrap_or("<desc>").to_string()
};
let ft = extract_tag_value_case_insensitive(&desc, &["forecast time", "forecasttime", "forecast_time", "forecast"]).and_then(|s| parse_first_int_from_str(&s));
let repr = sub.repr_def().repr_tmpl_num() as u16;
let payload_len = payloads.get(idx).map(|v| v.len()).unwrap_or(0);
let (width, height) = match sub.grid_shape() {
Ok((w,h)) => (w,h),
Err(_) => continue,
};
let mut decode_ok = false;
let mut decoded_samples_len: Option<usize> = None;
{
use grib::Grib2SubmessageDecoder;
if let Ok(decoder) = Grib2SubmessageDecoder::from(sub) {
if let Ok(decoded_iter) = decoder.dispatch() {
let collected: Vec<f32> = decoded_iter.collect();
decoded_samples_len = Some(collected.len());
decode_ok = !collected.is_empty();
}
}
}
if !decode_ok {
if let Some(params_opt) = params_vec.get(idx) {
if let Some(params) = params_opt.as_ref() {
if payload_len > 0 {
if let Ok(vv) = decode_template42_rust_from_params_with_payload(payloads.get(idx).map(|v| v.as_slice()).unwrap_or(&[]), width, height, params.num_points, params.bits_per_sample, params.block_size, params.rsi, params.flags_mask, params.exp, params.dec, params.ref_val) {
decode_ok = !vv.is_empty();
decoded_samples_len = Some(vv.len());
}
}
}
}
}
messages.push(MessageSummary {
message_index: idx,
key,
forecast_time: ft,
repr_template: Some(repr),
payload_len,
decode_ok,
decoded_samples_len,
});
}
Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
}
}
1 => {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
let mut messages: Vec<MessageSummary> = Vec::new();
let mut msg_idx = 0usize;
loop {
let mut header = [0u8; 8];
match f.read_exact(&mut header) {
Ok(()) => {}
Err(e) => {
if e.kind() == std::io::ErrorKind::UnexpectedEof { break; }
return Err(format!("read GRIB1 header failed: {}", e));
}
}
if &header[0..4] != b"GRIB" || header[7] != 1 { return Err("not a GRIB1 file or header error".to_string()); }
let total_len = read_u24_be(&header[4..7]) as usize;
if total_len < 8 { return Err(format!("invalid message length: {}", total_len)); }
let mut msg = vec![0u8; total_len];
msg[0..8].copy_from_slice(&header);
f.read_exact(&mut msg[8..]).map_err(|e| format!("read GRIB1 message failed: {}", e))?;
let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
let payload_len = total_len;
let mut decode_ok = false;
let mut decoded_samples_len = None;
if let Ok(d) = decode_grib1_first_message(&msg) {
decode_ok = !d.values.is_empty();
decoded_samples_len = Some(d.values.len());
}
messages.push(MessageSummary {
message_index: msg_idx,
key,
forecast_time: None,
repr_template: None,
payload_len,
decode_ok,
decoded_samples_len,
});
msg_idx += 1;
}
Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
}
other => Err(format!("unsupported GRIB edition={} (only 1/2 supported)", other)),
}
}
pub fn find_candidates_for_param(path: &Path, param: &str) -> Result<Vec<(usize, MatchReason, String, Option<i32>)>, String> {
let alias = crate::param_alias::ParamAlias::for_param(param);
let fs = summarize_file(path)?;
let mut out: Vec<(usize, MatchReason, String, Option<i32>)> = Vec::new();
for m in fs.messages.into_iter() {
let key_l = m.key.to_lowercase();
for t in alias.tokens.iter() {
if key_l.contains(t) {
out.push((m.message_index, MatchReason::Token, m.key.clone(), m.forecast_time));
continue;
}
}
let norm = |s: &str| s.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>();
let nparam = norm(param);
let nkey = norm(&key_l);
if !nparam.is_empty() && nkey.contains(&nparam) {
out.push((m.message_index, MatchReason::NormParam, m.key.clone(), m.forecast_time));
continue;
}
}
Ok(out)
}
fn nearest_latlon_index_for_grib1(lat_deg: f64, lon_deg: f64, grid: &Grib1LatLonGrid) -> Result<(usize, usize, f64, f64), String> {
if grid.di_deg == 0.0 || grid.dj_deg == 0.0 {
return Err("grid increments are zero".to_string());
}
let lon = normalize_lon_deg(lon_deg);
let lon1 = normalize_lon_deg(grid.lon1_deg);
let mut i = ((lon - lon1) / grid.di_deg).round() as isize;
let mut j = ((grid.lat1_deg - lat_deg) / grid.dj_deg).round() as isize;
if grid.ni > 0 {
let ni = grid.ni as isize;
i = ((i % ni) + ni) % ni;
}
j = j.clamp(0, grid.nj.saturating_sub(1) as isize);
let i_usize = i as usize;
let j_usize = j as usize;
let glat = grid.lat1_deg - (j_usize as f64) * grid.dj_deg;
let glon = normalize_lon_deg(lon1 + (i_usize as f64) * grid.di_deg);
Ok((i_usize, j_usize, glat, glon))
}
fn extract_grib1_parameter_key(msg: &[u8]) -> Option<String> {
if msg.len() < 8 { return None; }
let offset = 8;
let pds_len = read_u24_be(msg.get(offset..offset + 3)? ) as usize;
if msg.len() < offset + pds_len { return None; }
let pds = &msg[offset..offset + pds_len];
if pds.len() < 9 { return None; }
let param = pds.get(8).copied()?;
Some(format!("p{}", param))
}
#[derive(Debug, Clone)]
pub struct DecodedField {
pub width: usize,
pub height: usize,
pub values: Vec<f32>,
pub aec_backend: Option<&'static str>,
}
#[derive(Debug, Clone)]
pub struct ProbeField {
pub key: String,
pub forecast_time: Option<i32>,
pub i: usize,
pub j: usize,
pub lat: f64,
pub lon: f64,
pub value: f32,
}
pub fn decode_grib2_first_field(path: &Path) -> Result<DecodedField, String> {
if let Ok(grid) = read_first_grib2_latlon_grid(path) {
match decode_template42_first_message(path, grid.ni, grid.nj) {
Ok(Some(values)) => return Ok(DecodedField { width: grid.ni, height: grid.nj, values, aec_backend: Some("rust-aec-first") }),
Ok(None) | Err(_) => {}
}
}
#[cfg(feature = "grib-support")]
{
use grib::{from_reader, Grib2SubmessageDecoder, GribError, DecodeError};
use std::fs::File;
use std::io::BufReader;
let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
let r = BufReader::new(f);
let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
let ((_msg_idx, _submsg_idx), submessage) = grib2.iter().next().ok_or_else(|| "No GRIB2 submessages found in file".to_string())?;
let (width, height) = submessage.grid_shape().map_err(|e| format!("grid_shape read failed: {e}"))?;
let s5_opt = match submessage.section5() {
Ok(s5) => Some(s5),
Err(_) => None,
};
let decoder = Grib2SubmessageDecoder::from(submessage).map_err(|e| format!("Decoder creation failed: {e}"))?;
match decoder.dispatch() {
Ok(decoded) => return Ok(DecodedField { width, height, values: decoded.collect(), aec_backend: None }),
Err(err) => {
if matches!(err, GribError::DecodeError(DecodeError::NotSupported(
"GRIB2 code table 5.0 (data representation template number)", 42
))) {
if let Some(s5) = s5_opt {
use grib::def::grib2::DataRepresentationTemplate;
match s5.payload.template {
DataRepresentationTemplate::_5_42(t) => {
let params = Template42Params {
num_points: s5.payload.num_encoded_points as usize,
bits_per_sample: t.simple.num_bits as u8,
block_size: t.block_size as u32,
rsi: t.ref_sample_interval as u32,
flags_mask: t.mask as u32,
exp: t.simple.exp as i32,
dec: t.simple.dec as i32,
ref_val: t.simple.ref_val as f32,
};
match decode_template42_try_message_payload(path, 0, width, height, ¶ms) {
Ok(values) => return Ok(DecodedField { width, height, values, aec_backend: Some("rust-aec") }),
Err(e) => return Err(format!("rust-aec decode failed: {e}")),
}
}
_ => return Err("template 42 params missing or unexpected template".to_string()),
}
} else {
return Err("template 42 params missing (no Section5)".to_string());
}
} else {
return Err(format!("decode error: {err}"));
}
}
}
}
#[cfg(not(feature = "grib-support"))]
{
return Err("grib-support feature disabled and fast-path did not decode first field".to_string());
}
}
#[derive(Debug, Clone)]
pub struct Grib1LatLonGrid {
pub ni: usize,
pub nj: usize,
pub lat1_deg: f64,
pub lon1_deg: f64,
pub di_deg: f64,
pub dj_deg: f64,
pub scan_mode: u8,
}
#[derive(Debug, Clone)]
pub struct Grib1Decoded {
pub width: usize,
pub height: usize,
pub values: Vec<f32>,
}
pub fn read_grib1_first_message(path: &Path) -> Result<Vec<u8>, String> {
use std::fs::File;
use std::io::Read;
let mut f = File::open(path).map_err(|e| format!("打开文件失败:{e}"))?;
let mut header = [0u8; 8];
f.read_exact(&mut header)
.map_err(|e| format!("读取 GRIB1 头失败:{e}"))?;
if &header[0..4] != b"GRIB" {
return Err("文件头不是 GRIB".to_string());
}
if header[7] != 1 {
return Err(format!("不是 GRIB1(edition={})", header[7]));
}
let msg_len = ((header[4] as u32) << 16 | (header[5] as u32) << 8 | (header[6] as u32)) as usize;
if msg_len < 8 {
return Err(format!("GRIB1 message length 异常:{msg_len}"));
}
let mut msg = vec![0u8; msg_len];
msg[0..8].copy_from_slice(&header);
f.read_exact(&mut msg[8..])
.map_err(|e| format!("读取 GRIB1 第一个 message 失败:{e}"))?;
Ok(msg)
}
pub fn parse_grib1_latlon_grid(msg: &[u8]) -> Result<(Grib1LatLonGrid, u8), String> {
if msg.len() < 8 {
return Err("GRIB1 message 太短".to_string());
}
let mut offset = 8;
let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
if pds_len < 28 {
return Err(format!("PDS length 太小:{pds_len}"));
}
let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
let has_gds = (flags & 0x80) != 0;
offset += pds_len;
if !has_gds {
return Err("GRIB1: 缺少 GDS(无法获取网格信息)".to_string());
}
let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
if gds_len < 28 {
return Err(format!("GDS length 太小:{gds_len}"));
}
let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
let grid_type = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
let la1_md = read_i24_grib1(gds.get(10..13).ok_or_else(|| "GDS La1 越界".to_string())?);
let lo1_md = read_i24_grib1(gds.get(13..16).ok_or_else(|| "GDS Lo1 越界".to_string())?);
let di_md = read_u16_be(gds.get(23..25).ok_or_else(|| "GDS Di 越界".to_string())?) as i32;
let dj_md = read_u16_be(gds.get(25..27).ok_or_else(|| "GDS Dj 越界".to_string())?) as i32;
let scan_mode = gds.get(27).copied().ok_or_else(|| "GDS scanning_mode 越界".to_string())?;
let grid = Grib1LatLonGrid {
ni: ni.max(1),
nj: nj.max(1),
lat1_deg: (la1_md as f64) / 1000.0,
lon1_deg: (lo1_md as f64) / 1000.0,
di_deg: (di_md as f64) / 1000.0,
dj_deg: (dj_md as f64) / 1000.0,
scan_mode,
};
Ok((grid, grid_type))
}
fn read_i24_grib1(bytes: &[u8]) -> i32 {
let b0 = bytes.get(0).copied().unwrap_or(0);
let b1 = bytes.get(1).copied().unwrap_or(0);
let b2 = bytes.get(2).copied().unwrap_or(0);
let magnitude: i32 = ((b0 as i32 & 0x7f) << 16) | ((b1 as i32) << 8) | (b2 as i32);
if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
}
fn read_i16_grib1(bytes: &[u8]) -> i16 {
let b0 = bytes.get(0).copied().unwrap_or(0);
let b1 = bytes.get(1).copied().unwrap_or(0);
let magnitude = (b1 as i16) + (((b0 & 0x7f) as i16) << 8);
if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
}
fn read_f32_ibm(bytes: &[u8]) -> f32 {
if bytes.len() < 4 {
return f32::NAN;
}
let sign = if (bytes[0] & 0x80) != 0 { -1.0 } else { 1.0 };
let exponent = (bytes[0] & 0x7f) as i32;
let mantissa = (((bytes[1] as i32) << 16) | ((bytes[2] as i32) << 8) | (bytes[3] as i32)) as f32;
sign * 2.0f32.powi(-24) * mantissa * 16.0f32.powi(exponent - 64)
}
pub fn decode_grib1_simple_packed_values(
packed: &[u8],
npoints: usize,
bitmap: Option<&[u8]>,
bits_per_value: usize,
ref_value: f32,
binary_scale: i16,
) -> Result<Vec<f32>, String> {
let factor = 2.0f32.powi(binary_scale as i32);
let mut values = Vec::with_capacity(npoints);
let mut data_bit = BitCursor::new(packed);
let mut bmp_bit = bitmap.map(BitCursor::new);
for _ in 0..npoints {
if let Some(bmp) = bmp_bit.as_mut() {
let present = bmp.read_bit().ok_or_else(|| "Bitmap 读取越界".to_string())?;
if !present {
values.push(f32::NAN);
continue;
}
}
if bits_per_value == 0 {
values.push(ref_value);
continue;
}
if bits_per_value > 32 {
return Err(format!("bits_per_value 太大:{bits_per_value}(>32 暂不支持)"));
}
let x = data_bit
.read_bits(bits_per_value)
.ok_or_else(|| "BDS packed data 读取越界".to_string())?;
values.push(ref_value + (x as f32) * factor);
}
Ok(values)
}
struct BitCursor<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> BitCursor<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn read_bit(&mut self) -> Option<bool> {
let v = self.read_bits(1)?;
Some(v != 0)
}
fn read_bits(&mut self, nbits: usize) -> Option<u32> {
if nbits == 0 {
return Some(0);
}
let mut out: u32 = 0;
for _ in 0..nbits {
let byte_idx = self.bit_pos / 8;
let bit_in_byte = self.bit_pos % 8;
let byte = *self.data.get(byte_idx)?;
let bit = (byte >> (7 - bit_in_byte)) & 1;
out = (out << 1) | (bit as u32);
self.bit_pos += 1;
}
Some(out)
}
}
pub fn decode_grib1_first_message(msg: &[u8]) -> Result<Grib1Decoded, String> {
if msg.len() < 8 {
return Err("GRIB1 message 太短".to_string());
}
if &msg[0..4] != b"GRIB" {
return Err("GRIB1 header 不正确".to_string());
}
if msg[7] != 1 {
return Err(format!("不是 GRIB1(edition={})", msg[7]));
}
let total_len = ((msg[4] as u32) << 16 | (msg[5] as u32) << 8 | (msg[6] as u32)) as usize;
if total_len != msg.len() {
if total_len > msg.len() {
return Err(format!("GRIB1 指示的 message 长度={total_len},但实际读取到 {} 字节", msg.len()));
}
}
let mut offset = 8;
let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
if pds_len < 28 {
return Err(format!("PDS length 太小:{pds_len}"));
}
let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
let has_gds = (flags & 0x80) != 0;
let has_bms = (flags & 0x40) != 0;
offset += pds_len;
let (width, height, data_representation_type) = if has_gds {
let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
if gds_len < 11 {
return Err(format!("GDS length 太小:{gds_len}"));
}
let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
let drt = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
offset += gds_len;
match drt {
0 | 10 => (ni.max(1), nj.max(1), drt),
other => return Err(format!("GRIB1: 暂不支持 data_representation_type={other}(目前支持 0=LatLon, 10=RotatedLatLon)")),
}
} else {
return Err("GRIB1: 缺少 GDS(无法获取网格尺寸)".to_string());
};
let bitmap: Option<Vec<u8>> = if has_bms {
let bms_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BMS 长度越界".to_string())?) as usize;
if bms_len < 6 {
return Err(format!("BMS length 太小:{bms_len}"));
}
let bms = msg.get(offset..offset + bms_len).ok_or_else(|| "BMS 越界".to_string())?;
let table_ref = read_u16_be(bms.get(4..6).ok_or_else(|| "BMS table_reference 越界".to_string())?);
if table_ref != 0 {
return Err(format!("GRIB1: BMS 使用了预定义 bitmap(table_reference={table_ref}),暂不支持"));
}
let bmp = bms.get(6..).unwrap_or(&[]).to_vec();
offset += bms_len;
Some(bmp)
} else {
None
};
let bds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BDS 长度越界".to_string())?) as usize;
if bds_len < 11 {
return Err(format!("BDS length 太小:{bds_len}"));
}
let bds = msg.get(offset..offset + bds_len).ok_or_else(|| "BDS 越界".to_string())?;
let bds_flags = bds.get(3).copied().ok_or_else(|| "BDS flags 越界".to_string())?;
let has_complex_packing = (bds_flags & 0b0100_0000) != 0;
if has_complex_packing {
return Err(format!("GRIB1: 暂不支持复杂 packing(BDS flags=0x{bds_flags:02x},grid_type={data_representation_type})"));
}
let binary_scale = read_i16_grib1(bds.get(4..6).ok_or_else(|| "BDS binary_scale 越界".to_string())?);
let ref_value = read_f32_ibm(bds.get(6..10).ok_or_else(|| "BDS ref_value 越界".to_string())?);
let bits_per_value = bds.get(10).copied().ok_or_else(|| "BDS bits_per_value 越界".to_string())? as usize;
let packed = bds.get(11..).unwrap_or(&[]);
let npoints = width
.checked_mul(height)
.ok_or_else(|| "width*height 溢出".to_string())?;
let values = decode_grib1_simple_packed_values(
packed,
npoints,
bitmap.as_deref(),
bits_per_value,
ref_value,
binary_scale,
)?;
Ok(Grib1Decoded { width, height, values })
}
fn read_u24_be(bytes: &[u8]) -> u32 {
let b0 = bytes.get(0).copied().unwrap_or(0) as u32;
let b1 = bytes.get(1).copied().unwrap_or(0) as u32;
let b2 = bytes.get(2).copied().unwrap_or(0) as u32;
(b0 << 16) | (b1 << 8) | b2
}
fn read_u16_be(bytes: &[u8]) -> u16 {
let b0 = bytes.get(0).copied().unwrap_or(0) as u16;
let b1 = bytes.get(1).copied().unwrap_or(0) as u16;
(b0 << 8) | b1
}