1use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct MessageSummary {
11 pub message_index: usize,
12 pub key: String,
13 pub forecast_time: Option<i32>,
14 pub repr_template: Option<u16>,
15 pub payload_len: usize,
16 pub decode_ok: bool,
17 pub decoded_samples_len: Option<usize>,
18}
19
20#[derive(Debug, Clone)]
22pub struct FileSummary {
23 pub path: std::path::PathBuf,
24 pub n_messages: usize,
25 pub messages: Vec<MessageSummary>,
26}
27
28#[derive(Debug, Clone, Copy)]
30pub enum MatchReason {
31 Token,
32 NormParam,
33 Section4,
34}
35
36pub mod param_alias;
38
39#[allow(dead_code)]
41fn extract_tag_value_case_insensitive(desc: &str, tags: &[&str]) -> Option<String> {
42 for line in desc.lines() {
43 let s = line.trim();
44 for tag in tags {
45 let t = tag.to_lowercase();
46 let key = t.clone() + ":";
47 let key_eq = t.clone() + "=";
48 let s_l = s.to_lowercase();
49 if s_l.starts_with(&key) || s_l.starts_with(&key_eq) {
50 if let Some(idx) = s.find(':') {
51 let v = s[idx + 1..].trim();
52 return Some(v.to_string());
53 }
54 if let Some(idx) = s.find('=') {
55 let v = s[idx + 1..].trim();
56 return Some(v.to_string());
57 }
58 }
59 if s_l.starts_with(&format!("{} ", t)) {
61 let v = s[t.len()..].trim();
62 return Some(v.to_string());
63 }
64 let patterns = [format!("{} =", t.clone()), format!("{}:", t)];
66 for pat in patterns.iter() {
67 if let Some(pos) = s_l.find(pat) {
68 let v = s[pos + pat.len()..].trim();
69 return Some(v.to_string());
70 }
71 }
72 }
73 }
74 None
75}
76
77#[allow(dead_code)]
79fn parse_first_int_from_str(s: &str) -> Option<i32> {
80 let s = s.trim();
81 let mut num_str = String::new();
82 for c in s.chars() {
83 if c.is_ascii_digit() || (num_str.is_empty() && (c == '+' || c == '-')) {
84 num_str.push(c);
85 } else if !num_str.is_empty() {
86 break;
87 }
88 }
89 if num_str.is_empty() { None } else { num_str.parse::<i32>().ok() }
90}
91
92pub fn read_grib2_section7_payloads_by_message(path: &Path) -> Result<Vec<Vec<u8>>, String> {
95 use std::fs;
96 use memchr::memmem;
97
98 let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
99 let mut out: Vec<Vec<u8>> = Vec::new();
100
101 let mut pos = 0usize;
102 while pos + 16 <= data.len() {
103 if &data[pos..pos + 4] != b"GRIB" {
105 if let Some(next) = memmem::find(&data[pos + 1..], b"GRIB") {
106 pos = pos + 1 + next;
107 } else {
108 break;
109 }
110 continue;
111 }
112 if data[pos + 7] != 2 {
114 pos += 4;
115 continue;
116 }
117 let mut off = pos + 16;
119 let mut payload_found = false;
120 loop {
121 if off + 5 > data.len() {
122 break; }
124 let len = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
125 let sect_num = data[off + 4];
126 if len < 5 { break; }
127 let payload_len = len - 5;
128 let payload_off = off + 5;
129 if payload_off + payload_len > data.len() { break; }
130 if sect_num == 7 {
131 out.push(data[payload_off..payload_off + payload_len].to_vec());
132 payload_found = true;
133 }
134 off = payload_off + payload_len;
135 if off + 4 <= data.len() && &data[off..off + 4] == b"7777" {
137 off += 4;
138 break;
139 }
140 }
141 if !payload_found {
143 out.push(Vec::new());
144 }
145 pos = if off > pos { off } else { pos + 4 };
147 }
148
149 Ok(out)
150}
151
152pub struct Template42Params {
154 pub num_points: usize,
155 pub bits_per_sample: u8,
156 pub block_size: u32,
157 pub rsi: u32,
158 pub flags_mask: u32,
159 pub exp: i32,
160 pub dec: i32,
161 pub ref_val: f32,
162}
163
164pub fn read_grib2_section5_template42_params_by_message(path: &Path) -> Result<Vec<Option<Template42Params>>, String> {
168 #[cfg(feature = "grib-support")]
171 {
172 use std::fs::File;
173 use std::io::BufReader;
174 use grib::def::grib2::DataRepresentationTemplate;
175
176 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
177 let r = BufReader::new(f);
178 let grib2 = grib::from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
179
180 let mut out: Vec<Option<Template42Params>> = Vec::new();
181 for ((_msg_idx, _submsg_idx), submsg) in grib2.iter() {
182 match submsg.section5() {
183 Ok(s5) => match s5.payload.template {
184 DataRepresentationTemplate::_5_42(t) => {
185 let params = Template42Params {
186 num_points: s5.payload.num_encoded_points as usize,
187 bits_per_sample: t.simple.num_bits as u8,
188 block_size: t.block_size as u32,
189 rsi: t.ref_sample_interval as u32,
190 flags_mask: t.mask as u32,
191 exp: t.simple.exp as i32,
192 dec: t.simple.dec as i32,
193 ref_val: t.simple.ref_val as f32,
194 };
195 out.push(Some(params));
196 }
197 _ => out.push(None),
198 },
199 Err(_) => out.push(None),
200 }
201 }
202 Ok(out)
203 }
204 #[cfg(not(feature = "grib-support"))]
205 {
206 use std::fs;
207
208 let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
209 let mut out: Vec<Option<Template42Params>> = Vec::new();
210
211 let mut pos = 0usize;
212 while pos + 16 <= data.len() {
213 if &data[pos..pos + 4] != b"GRIB" {
214 if let Some(next) = memchr::memmem::find(&data[pos + 1..], b"GRIB") {
216 pos = pos + 1 + next;
217 } else {
218 break;
219 }
220 continue;
221 }
222 if data[pos + 7] != 2 {
223 pos += 4;
224 continue;
225 }
226 let mut off = pos + 16;
228 let mut found_s5 = false;
229 let mut s5_params: Option<Template42Params> = None;
230 loop {
231 if off + 5 > data.len() { break; }
232 let len = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
233 let sect_num = data[off + 4];
234 if len < 5 { break; }
235 let payload_len = len - 5;
236 let payload_off = off + 5;
237 if payload_off + payload_len > data.len() { break; }
238 if sect_num == 5 {
239 if payload_len >= 6 {
245 let _num_points = u32::from_be_bytes([
246 data[payload_off],
247 data[payload_off + 1],
248 data[payload_off + 2],
249 data[payload_off + 3],
250 ]) as usize;
251 let tmpl_num = u16::from_be_bytes([data[payload_off + 4], data[payload_off + 5]]);
252 if tmpl_num == 42 {
253 let mut cursor = payload_off + 6;
256 if cursor + 4 <= payload_off + payload_len {
257 let num_encoded_points = u32::from_be_bytes([
258 data[cursor],
259 data[cursor + 1],
260 data[cursor + 2],
261 data[cursor + 3],
262 ]) as usize;
263 cursor += 4;
264 if cursor + 1 > payload_off + payload_len { break; }
266 let num_bits = data[cursor] as u8; cursor += 1;
267 if cursor + 4 > payload_off + payload_len { break; }
269 let block_size = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
270 if cursor + 4 > payload_off + payload_len { break; }
272 let rsi = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
273 if cursor + 4 > payload_off + payload_len { break; }
275 let mask = u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]); cursor += 4;
276 if cursor + 2 + 2 + 4 > payload_off + payload_len { break; }
278 let exp = i16::from_be_bytes([data[cursor], data[cursor + 1]]) as i32; cursor += 2;
279 let dec = i16::from_be_bytes([data[cursor], data[cursor + 1]]) as i32; cursor += 2;
280 let ref_val = f32::from_bits(u32::from_be_bytes([data[cursor], data[cursor + 1], data[cursor + 2], data[cursor + 3]]));
281
282 s5_params = Some(Template42Params {
284 num_points: num_encoded_points,
285 bits_per_sample: num_bits,
286 block_size,
287 rsi,
288 flags_mask: mask,
289 exp,
290 dec,
291 ref_val,
292 });
293 }
294 }
295 }
296 found_s5 = true;
297 }
298 off = payload_off + payload_len;
299 if off + 4 <= data.len() && &data[off..off + 4] == b"7777" { off += 4; break; }
301 }
302 if found_s5 {
303 out.push(s5_params);
304 } else {
305 out.push(None);
306 }
307
308 pos = if off > pos { off } else { pos + 4 };
310 }
311
312 return Ok(out);
313 }
314}
315
316pub fn read_grib2_section4_parameter_ids_by_message(path: &Path) -> Result<Vec<Option<(u8,u8,u8)>>, String> {
319 #[cfg(feature = "grib-support")]
321 {
322 use std::fs::File;
323 use std::io::BufReader;
324 use grib::from_reader;
325
326 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
327 let r = BufReader::new(f);
328 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
329 let mut out: Vec<Option<(u8,u8,u8)>> = Vec::new();
330 for ((_msg_idx, _submsg_idx), submsg) in grib2.iter() {
331 let desc = submsg.describe();
333 let discipline = extract_tag_value_case_insensitive(&desc, &["discipline"]).and_then(|s| s.parse::<u8>().ok());
334 let category = extract_tag_value_case_insensitive(&desc, &["parametercategory", "parameter category", "paramcategory", "category"]).and_then(|s| s.parse::<u8>().ok());
335 let number = extract_tag_value_case_insensitive(&desc, &["parameternumber", "parameter number", "paramnumber", "number"]).and_then(|s| s.parse::<u8>().ok());
336 if discipline.is_some() || category.is_some() || number.is_some() {
337 let d = discipline.unwrap_or(0);
338 let c = category.unwrap_or(0);
339 let n = number.unwrap_or(0);
340 out.push(Some((d,c,n)));
341 } else {
342 out.push(None);
343 }
344 }
345 return Ok(out);
346 }
347
348 #[cfg(not(feature = "grib-support"))]
350 {
351 use std::fs;
352 let data = fs::read(path).map_err(|e| format!("打开文件失败:{e}"))?;
353 let mut out: Vec<Option<(u8,u8,u8)>> = Vec::new();
354 let mut pos = 0usize;
355 while pos + 16 <= data.len() {
356 if &data[pos..pos + 4] != b"GRIB" {
357 if let Some(next) = memchr::memmem::find(&data[pos + 1..], b"GRIB") {
358 pos = pos + 1 + next;
359 } else { break; }
360 continue;
361 }
362 if data[pos + 7] != 2 {
363 pos += 4;
364 continue;
365 }
366 let mut off = pos + 16;
367 let mut found_s4: Option<(u8,u8,u8)> = None;
368 loop {
369 if off + 5 > data.len() { break; }
370 let len = u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]) as usize;
371 let sect_num = data[off + 4];
372 if len < 5 { break; }
373 let payload_len = len - 5;
374 let payload_off = off + 5;
375 if payload_off + payload_len > data.len() { break; }
376 if sect_num == 4 {
377 if payload_len >= 10 {
380 let d = data[payload_off];
381 let c = data[payload_off + 8];
382 let n = data[payload_off + 9];
383 found_s4 = Some((d, c, n));
384 }
385 }
386 off = payload_off + payload_len;
387 if off + 4 <= data.len() && &data[off..off+4] == b"7777" { off += 4; break; }
388 }
389 out.push(found_s4);
390 pos = if off > pos { off } else { pos + 4 };
391 }
392 return Ok(out);
393 }
394}
395
396pub fn decode_template42_rust_from_params_with_payload(
398 payload: &[u8],
399 width: usize,
400 height: usize,
401 num_points: usize,
402 bits_per_sample: u8,
403 block_size: u32,
404 rsi: u32,
405 flags_mask: u32,
406 exp: i32,
407 dec: i32,
408 ref_val: f32,
409) -> Result<Vec<f32>, String> {
410 use rust_aec::{flags_from_grib2_ccsds_flags, params::AecParams};
411
412 let expected_points = width.saturating_mul(height);
413 if expected_points != 0 && num_points != expected_points {
414 return Err(format!(
415 "template42(rust-aec) only supports non-bitmap regular grids: num_points={} grid={}x{}={}",
416 num_points, width, height, expected_points
417 ));
418 }
419
420 let ccsds_flags: u8 = (flags_mask & 0xff) as u8;
421 let flags = flags_from_grib2_ccsds_flags(ccsds_flags);
422 let params = AecParams::new(bits_per_sample, block_size, rsi, flags);
423 let decoded_bytes = rust_aec::decode(payload, params, num_points)
424 .map_err(|e| format!("rust-aec decode failed: {e}"))?;
425
426 let bits_u32 = bits_per_sample as u32;
427 let bytes_per_sample = ((bits_u32 + 7) / 8) as usize;
428 let msb = flags.contains(rust_aec::params::AecFlags::MSB);
429 let mask: u32 = if bits_u32 >= 32 { u32::MAX } else { (1u32 << bits_u32) - 1 };
430 let bscale = 2.0f32.powi(exp);
431 let dscale = 10.0f32.powi(-dec);
432
433 let mut values = Vec::with_capacity(num_points);
434 for i in 0..num_points {
435 let start = i * bytes_per_sample;
436 let sample_bytes = &decoded_bytes[start..start + bytes_per_sample];
437 let raw = if msb {
438 sample_bytes.iter().fold(0u32, |acc, &b| (acc << 8) | (b as u32))
439 } else {
440 sample_bytes
441 .iter()
442 .enumerate()
443 .fold(0u32, |acc, (j, &b)| acc | ((b as u32) << (j * 8)))
444 } & mask;
445 let v = (ref_val + (raw as f32) * bscale) * dscale;
446 values.push(v);
447 }
448
449 Ok(values)
450}
451
452pub fn decode_template42_rust_from_params(
454 path: &Path,
455 width: usize,
456 height: usize,
457 num_points: usize,
458 bits_per_sample: u8,
459 block_size: u32,
460 rsi: u32,
461 flags_mask: u32,
462 exp: i32,
463 dec: i32,
464 ref_val: f32,
465) -> Result<Vec<f32>, String> {
466 let payload = read_first_grib2_section7_payload(path)?;
468 decode_template42_rust_from_params_with_payload(
469 &payload,
470 width,
471 height,
472 num_points,
473 bits_per_sample,
474 block_size,
475 rsi,
476 flags_mask,
477 exp,
478 dec,
479 ref_val,
480 )
481}
482
483pub fn read_first_grib2_section7_payload(path: &Path) -> Result<Vec<u8>, String> {
485 use std::fs::File;
486 use std::io::Read;
487
488 let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
489
490 let mut s0 = [0u8; 16];
492 f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
493 if &s0[0..4] != b"GRIB" {
494 return Err("not a GRIB file".to_string());
495 }
496 if s0[7] != 2 {
497 return Err(format!("not GRIB2 (edition={})", s0[7]));
498 }
499
500 loop {
501 let mut header = [0u8; 5];
502 f.read_exact(&mut header).map_err(|e| format!("read section header failed: {e}"))?;
503 let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
504 let sect_num = header[4];
505 if len < 5 {
506 return Err(format!("invalid section length: {len} (section {sect_num})"));
507 }
508 let payload_len = len - 5;
509 if sect_num == 7 {
510 let mut payload = vec![0u8; payload_len];
511 f.read_exact(&mut payload).map_err(|e| format!("read Section7 payload failed: {e}"))?;
512 return Ok(payload);
513 }
514 let mut skip = vec![0u8; payload_len];
515 f.read_exact(&mut skip).map_err(|e| format!("skip failed: {e}"))?;
516 }
517}
518
519pub fn detect_grib_edition(path: &Path) -> Result<u8, String> {
521 use std::fs::File;
522 use std::io::Read;
523
524 let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
525 let mut s0 = [0u8; 16];
526 f.read_exact(&mut s0).map_err(|e| format!("read header failed: {e}"))?;
527 if &s0[0..4] != b"GRIB" {
528 return Err("not a GRIB file".to_string());
529 }
530 Ok(s0[7])
531}
532
533pub fn has_grib_support() -> bool {
535 cfg!(feature = "grib-support")
536}
537
538#[derive(Debug, Clone)]
540pub struct Grib2LatLonGrid {
541 pub ni: usize,
542 pub nj: usize,
543 pub lat1_deg: f64,
544 pub lon1_deg: f64,
545 pub di_deg: f64,
546 pub dj_deg: f64,
547 pub scan_mode: u8,
548}
549
550pub fn read_first_grib2_latlon_grid(path: &Path) -> Result<Grib2LatLonGrid, String> {
552 use std::fs::File;
553 use std::io::Read;
554
555 let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
556
557 let mut s0 = [0u8; 16];
558 f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
559 if &s0[0..4] != b"GRIB" {
560 return Err("not a GRIB file".to_string());
561 }
562 if s0[7] != 2 {
563 return Err(format!("not GRIB2 (edition={})", s0[7]));
564 }
565
566 loop {
567 let mut header = [0u8; 5];
568 f.read_exact(&mut header).map_err(|e| format!("read section header failed: {e}"))?;
569 let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
570 let sect_num = header[4];
571 if len < 5 {
572 return Err(format!("invalid section length: {len} (section {sect_num})"));
573 }
574 let payload_len = len - 5;
575 let mut payload = vec![0u8; payload_len];
576 f.read_exact(&mut payload).map_err(|e| format!("read payload failed: {e}"))?;
577
578 if sect_num == 3 {
579 if payload.len() < 9 {
581 return Err("GRIB2: Section3 too short".to_string());
582 }
583 let grid_tmpl = u16::from_be_bytes([payload[7], payload[8]]);
584 if grid_tmpl != 0 {
585 return Err(format!("GRIB2: only grid template 3.0 (regular lat/lon) supported; got 3.{grid_tmpl}"));
586 }
587 let t = &payload[9..];
588 if t.len() < 58 {
589 return Err(format!("GRIB2: grid template 3.0 too short (need >=58 bytes, got {})", t.len()));
590 }
591 const MISSING_U32: u32 = 0xFFFF_FFFF;
592 let ni_u32 = u32::from_be_bytes([t[16], t[17], t[18], t[19]]);
593 let nj_u32 = u32::from_be_bytes([t[20], t[21], t[22], t[23]]);
594 if ni_u32 == MISSING_U32 || nj_u32 == MISSING_U32 {
595 return Err("GRIB2: template 3.0 has missing Ni/Nj or irregular grid".to_string());
596 }
597 let ni = ni_u32 as usize;
598 let nj = nj_u32 as usize;
599 let basic_angle = u32::from_be_bytes([t[24], t[25], t[26], t[27]]);
600 let subdivisions = u32::from_be_bytes([t[28], t[29], t[30], t[31]]);
601 let unit_deg: f64 = if (basic_angle == 0 && subdivisions == 0) || (basic_angle == MISSING_U32 && subdivisions == MISSING_U32) {
602 1e-6
603 } else {
604 (basic_angle as f64) / (subdivisions as f64)
605 };
606 let mut lat1 = i32::from_be_bytes([t[32], t[33], t[34], t[35]]) as f64 * unit_deg;
607 let mut lon1 = i32::from_be_bytes([t[36], t[37], t[38], t[39]]) as f64 * unit_deg;
608 let lat2 = i32::from_be_bytes([t[41], t[42], t[43], t[44]]) as f64 * unit_deg;
609 let lon2 = i32::from_be_bytes([t[45], t[46], t[47], t[48]]) as f64 * unit_deg;
610 let mut di = u32::from_be_bytes([t[49], t[50], t[51], t[52]]) as f64 * unit_deg;
611 let mut dj = u32::from_be_bytes([t[53], t[54], t[55], t[56]]) as f64 * unit_deg;
612 let scan_mode = t[57];
613
614 if (di == 0.0 || dj == 0.0) && (ni > 1 && nj > 1) {
616 if dj == 0.0 {
618 dj = (lat1 - lat2) / ((nj.saturating_sub(1)) as f64);
619 }
620 if di == 0.0 {
622 let mut dlon = lon2 - lon1;
623 while dlon <= -180.0 { dlon += 360.0; }
624 while dlon > 180.0 { dlon -= 360.0; }
625 di = dlon / ((ni.saturating_sub(1)) as f64);
626 }
627 }
628
629 if (di == 0.0 || dj == 0.0) && (ni > 1 && nj > 1) {
631 di = 360.0 / (ni as f64);
633 dj = 180.0 / (nj as f64);
634 if lat1 == 0.0 && lon1 == 0.0 {
636 lat1 = 90.0;
638 lon1 = -180.0;
639 }
640 }
641
642 let _suggested_lat = lat1 - ((nj.saturating_sub(1) as f64) * dj / 2.0);
644 let _suggested_lon = normalize_lon_deg(lon1 + ((ni.saturating_sub(1) as f64) * di / 2.0));
645 return Ok(Grib2LatLonGrid {
646 ni,
647 nj,
648 lat1_deg: lat1,
649 lon1_deg: normalize_lon_deg(lon1),
650 di_deg: di,
651 dj_deg: dj,
652 scan_mode,
653 });
654 }
655
656 }
658}
659
660fn normalize_lon_deg(mut lon: f64) -> f64 {
661 while lon <= -180.0 { lon += 360.0; }
662 while lon > 180.0 { lon -= 360.0; }
663 lon
664}
665
666pub fn read_all_grib2_section7_payloads(path: &Path) -> Result<Vec<Vec<u8>>, String> {
668 use std::fs::File;
669 use std::io::Read;
670
671 let mut f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
672
673 let mut s0 = [0u8; 16];
675 f.read_exact(&mut s0).map_err(|e| format!("read GRIB2 section0 failed: {e}"))?;
676 if &s0[0..4] != b"GRIB" {
677 return Err("not a GRIB file".to_string());
678 }
679 if s0[7] != 2 {
680 return Err(format!("not GRIB2 (edition={})", s0[7]));
681 }
682
683 let mut out = Vec::new();
684 loop {
685 let mut header = [0u8; 5];
686 match f.read_exact(&mut header) {
687 Ok(()) => {}
688 Err(e) => {
689 if e.kind() == std::io::ErrorKind::UnexpectedEof {
690 break;
691 }
692 return Err(format!("read section header failed: {e}"));
693 }
694 }
695 let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
696 let sect_num = header[4];
697 if len < 5 {
698 return Err(format!("invalid section length: {len} (section {sect_num})"));
699 }
700 let payload_len = len - 5;
701
702 if sect_num == 7 {
703 let mut payload = vec![0u8; payload_len];
704 match f.read_exact(&mut payload) {
705 Ok(()) => {
706 out.push(payload);
707 continue;
708 }
709 Err(e) => {
710 return Err(format!("read Section7 payload failed: {e}"));
711 }
712 }
713 }
714
715 let mut skip = vec![0u8; payload_len];
717 match f.read_exact(&mut skip) {
718 Ok(()) => {}
719 Err(_) => {
720 break;
722 }
723 }
724 }
725
726 Ok(out)
727}
728
729pub fn decode_template42_from_payload_list(
732 payloads: &[&[u8]],
733 params_list: &[Option<Template42Params>],
734 width: usize,
735 height: usize,
736) -> Result<Vec<Option<Vec<f32>>>, String> {
737 if payloads.len() != params_list.len() {
738 return Err("payloads and params_list length mismatch".to_string());
739 }
740 let mut out: Vec<Option<Vec<f32>>> = Vec::with_capacity(payloads.len());
741 for (payload, params_opt) in payloads.iter().zip(params_list.iter()) {
742 if let Some(params) = params_opt {
743 match decode_template42_rust_from_params_with_payload(
745 payload,
746 width,
747 height,
748 params.num_points,
749 params.bits_per_sample,
750 params.block_size,
751 params.rsi,
752 params.flags_mask,
753 params.exp,
754 params.dec,
755 params.ref_val,
756 ) {
757 Ok(v) => out.push(Some(v)),
758 Err(_) => out.push(None),
759 }
760 } else {
761 out.push(None);
762 }
763 }
764 Ok(out)
765}
766
767pub fn decode_template42_for_file_by_message(
770 path: &Path,
771 width: usize,
772 height: usize,
773) -> Result<Vec<Option<Vec<f32>>>, String> {
774 let payloads_vec = read_grib2_section7_payloads_by_message(path)?;
775 let params_vec = read_grib2_section5_template42_params_by_message(path)?;
776 let payload_slices: Vec<&[u8]> = payloads_vec.iter().map(|v| v.as_slice()).collect();
778 let params_ref: Vec<Option<Template42Params>> = params_vec.into_iter().collect();
779 decode_template42_from_payload_list(&payload_slices, ¶ms_ref, width, height)
780}
781
782pub fn decode_template42_first_message(
786 path: &Path,
787 width: usize,
788 height: usize,
789) -> Result<Option<Vec<f32>>, String> {
790 let payloads_vec = read_grib2_section7_payloads_by_message(path)?;
791 let params_vec = read_grib2_section5_template42_params_by_message(path)?;
792
793 let n = payloads_vec.len().min(params_vec.len());
794 for i in 0..n {
795 let payload = &payloads_vec[i];
796 if payload.is_empty() {
797 continue;
798 }
799 if let Some(ref params) = params_vec[i] {
800 match decode_template42_rust_from_params_with_payload(
801 payload.as_slice(),
802 width,
803 height,
804 params.num_points,
805 params.bits_per_sample,
806 params.block_size,
807 params.rsi,
808 params.flags_mask,
809 params.exp,
810 params.dec,
811 params.ref_val,
812 ) {
813 Ok(v) => return Ok(Some(v)),
814 Err(_) => continue, }
816 }
817 }
818 Ok(None)
819}
820
821pub fn decode_template42_try_message_payload(
825 path: &Path,
826 message_index: usize,
827 width: usize,
828 height: usize,
829 params: &Template42Params,
830) -> Result<Vec<f32>, String> {
831 match read_grib2_section7_payloads_by_message(path) {
833 Ok(payloads) => {
834 if let Some(p) = payloads.get(message_index) {
835 if !p.is_empty() {
836 return decode_template42_rust_from_params_with_payload(
837 p.as_slice(),
838 width,
839 height,
840 params.num_points,
841 params.bits_per_sample,
842 params.block_size,
843 params.rsi,
844 params.flags_mask,
845 params.exp,
846 params.dec,
847 params.ref_val,
848 );
849 }
850 }
851 }
852 Err(_) => {
853 }
855 }
856
857 let payload = read_first_grib2_section7_payload(path)?;
859 decode_template42_rust_from_params_with_payload(
860 &payload,
861 width,
862 height,
863 params.num_points,
864 params.bits_per_sample,
865 params.block_size,
866 params.rsi,
867 params.flags_mask,
868 params.exp,
869 params.dec,
870 params.ref_val,
871 )
872}
873
874pub fn probe_all_grib2_latlon(_path: &Path, _lat_deg: f64, _lon_deg: f64) -> Result<Vec<ProbeField>, String> {
877 #[cfg(not(feature = "grib-support"))]
878 {
879 return Err("probe_all_grib2_latlon requires the 'grib-support' feature to be enabled".to_string());
880 }
881
882 #[cfg(feature = "grib-support")]
883 {
884 use std::fs::File;
885 use std::io::BufReader;
886 use grib::{from_reader, Grib2SubmessageDecoder};
887
888 let path = _path;
889 let lat_deg = _lat_deg;
890 let lon_deg = _lon_deg;
891
892 let grid = read_first_grib2_latlon_grid(path)?;
893 if grid.scan_mode != 0 {
894 return Err(format!("unsupported scanning_mode=0x{:02x}", grid.scan_mode));
895 }
896
897 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
898 let r = BufReader::new(f);
899 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
900
901 let decoded_template42_by_message = match decode_template42_for_file_by_message(path, grid.ni, grid.nj) {
903 Ok(v) => v,
904 Err(_) => Vec::new(),
905 };
906
907 let mut out: Vec<ProbeField> = Vec::new();
908
909 for (idx, ((_msg_idx, _submsg_idx), submessage)) in grib2.iter().enumerate() {
910 let desc = submessage.describe();
911 let key = if let Some(sn) = crate::extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
912 sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
913 } else {
914 desc.lines().next().unwrap_or("<desc>").to_string()
915 };
916 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));
917
918 let (width, height) = match submessage.grid_shape() {
919 Ok((w,h)) => (w,h),
920 Err(_) => continue,
921 };
922 if width != grid.ni || height != grid.nj { continue; }
923
924 let values_opt = decoded_template42_by_message.get(idx).and_then(|o| o.clone());
926 let values: Vec<f32> = if let Some(v) = values_opt { v } else {
927 let template42_params_opt: Option<Template42Params> = match submessage.section5() {
929 Ok(s5) => match s5.payload.template {
930 grib::def::grib2::DataRepresentationTemplate::_5_42(t) => Some(Template42Params {
931 num_points: s5.payload.num_encoded_points as usize,
932 bits_per_sample: t.simple.num_bits as u8,
933 block_size: t.block_size as u32,
934 rsi: t.ref_sample_interval as u32,
935 flags_mask: t.mask as u32,
936 exp: t.simple.exp as i32,
937 dec: t.simple.dec as i32,
938 ref_val: t.simple.ref_val as f32,
939 }),
940 _ => None,
941 },
942 Err(_) => None,
943 };
944
945 let decoder = match Grib2SubmessageDecoder::from(submessage) {
946 Ok(d) => d,
947 Err(_) => continue,
948 };
949 match decoder.dispatch() {
950 Ok(decoded) => decoded.collect::<Vec<f32>>(),
951 Err(_) => {
952 if let Some(params) = template42_params_opt {
954 match decode_template42_try_message_payload(path, idx, width, height, ¶ms) {
955 Ok(vv) => vv,
956 Err(_) => continue,
957 }
958 } else {
959 continue;
960 }
961 }
962 }
963 };
964
965 let mut dlon = lon_deg - grid.lon1_deg;
967 while dlon < 0.0 { dlon += 360.0; }
968 while dlon >= 360.0 { dlon -= 360.0; }
969 let i_f = dlon / grid.di_deg;
970 let j_f = (grid.lat1_deg - lat_deg) / grid.dj_deg;
971 if !i_f.is_finite() || !j_f.is_finite() { continue; }
972 let ni = width as isize; let nj = height as isize;
973 let i0 = i_f.floor() as isize; let j0 = j_f.floor() as isize; let i1 = i0 + 1; let j1 = j0 + 1;
974 let dx = i_f - (i0 as f64); let dy = j_f - (j0 as f64);
975 let i0w = (((i0 % ni) + ni) % ni) as usize; let i1w = (((i1 % ni) + ni) % ni) as usize;
976 let j0c = j0.clamp(0, nj - 1) as usize; let j1c = j1.clamp(0, nj - 1) as usize;
977 let idx00 = j0c.saturating_mul(width).saturating_add(i0w);
978 let idx10 = j0c.saturating_mul(width).saturating_add(i1w);
979 let idx01 = j1c.saturating_mul(width).saturating_add(i0w);
980 let idx11 = j1c.saturating_mul(width).saturating_add(i1w);
981 let s00 = values.get(idx00).copied().unwrap_or(f32::NAN);
982 let s10 = values.get(idx10).copied().unwrap_or(f32::NAN);
983 let s01 = values.get(idx01).copied().unwrap_or(f32::NAN);
984 let s11 = values.get(idx11).copied().unwrap_or(f32::NAN);
985 if !s00.is_finite() || !s10.is_finite() || !s01.is_finite() || !s11.is_finite() { continue; }
986 let v0 = s00 * (1.0 - dx as f32) + s10 * (dx as f32);
987 let v1 = s01 * (1.0 - dx as f32) + s11 * (dx as f32);
988 let value = v0 * (1.0 - dy as f32) + v1 * (dy as f32);
989
990 let glat = grid.lat1_deg - j_f * grid.dj_deg;
992 let glon = normalize_lon_deg(grid.lon1_deg + i_f * grid.di_deg);
993
994 out.push(ProbeField { key, forecast_time: ft, i: i0w, j: j0c, lat: glat, lon: glon, value });
995 }
996
997 Ok(out)
998 }
999}
1000
1001pub fn probe_all_fields_at_lat_lon(path: &Path, lat_deg: f64, lon_deg: f64) -> Result<Vec<ProbeField>, String> {
1004 match detect_grib_edition(path)? {
1005 2 => probe_all_grib2_latlon(path, lat_deg, lon_deg),
1006 1 => {
1007 let msg = read_grib1_first_message(path)?;
1009 let (grid, _drt) = parse_grib1_latlon_grid(&msg)?;
1010 let decoded = decode_grib1_first_message(&msg)?;
1011 let (i, j, glat, glon) = nearest_latlon_index_for_grib1(lat_deg, lon_deg, &grid)?;
1012 let idx = j.saturating_mul(decoded.width).saturating_add(i);
1013 let val = decoded.values.get(idx).copied().unwrap_or(f32::NAN);
1014 let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
1015 Ok(vec![ProbeField { key, forecast_time: None, i, j, lat: glat, lon: glon, value: val }])
1016 }
1017 other => Err(format!("unsupported GRIB edition {} (only 1/2 supported)", other)),
1018 }
1019}
1020
1021pub fn summarize_file(path: &Path) -> Result<FileSummary, String> {
1023 match detect_grib_edition(path)? {
1024 2 => {
1025 #[cfg(not(feature = "grib-support"))]
1026 return Err("summarize_file requires the 'grib-support' feature".to_string());
1027
1028 #[cfg(feature = "grib-support")]
1029 {
1030 use std::fs::File;
1031 use std::io::BufReader;
1032 use grib::from_reader;
1033
1034 let f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
1035 let r = BufReader::new(f);
1036 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {}", e))?;
1037
1038 let payloads = read_grib2_section7_payloads_by_message(path).unwrap_or_default();
1040 let params_vec = read_grib2_section5_template42_params_by_message(path).unwrap_or_default();
1041
1042 let mut messages: Vec<MessageSummary> = Vec::new();
1043 for (idx, ((_msg_idx, _submsg_idx), sub)) in grib2.iter().enumerate() {
1044 let desc = sub.describe();
1045 let key = if let Some(sn) = extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
1046 sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
1047 } else {
1048 desc.lines().next().unwrap_or("<desc>").to_string()
1049 };
1050 let ft = extract_tag_value_case_insensitive(&desc, &["forecast time", "forecasttime", "forecast_time", "forecast"]).and_then(|s| parse_first_int_from_str(&s));
1051 let repr = sub.repr_def().repr_tmpl_num() as u16;
1052 let payload_len = payloads.get(idx).map(|v| v.len()).unwrap_or(0);
1053
1054 let (width, height) = match sub.grid_shape() {
1056 Ok((w,h)) => (w,h),
1057 Err(_) => continue,
1058 };
1059 let mut decode_ok = false;
1061 let mut decoded_samples_len: Option<usize> = None;
1062
1063 {
1065 use grib::Grib2SubmessageDecoder;
1066 if let Ok(decoder) = Grib2SubmessageDecoder::from(sub) {
1068 if let Ok(decoded_iter) = decoder.dispatch() {
1069 let collected: Vec<f32> = decoded_iter.collect();
1070 decoded_samples_len = Some(collected.len());
1071 decode_ok = !collected.is_empty();
1072 }
1073 }
1074 }
1075
1076 if !decode_ok {
1078 if let Some(params_opt) = params_vec.get(idx) {
1079 if let Some(params) = params_opt.as_ref() {
1080 if payload_len > 0 {
1081 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) {
1082 decode_ok = !vv.is_empty();
1083 decoded_samples_len = Some(vv.len());
1084 }
1085 }
1086 }
1087 }
1088 }
1089
1090 messages.push(MessageSummary {
1091 message_index: idx,
1092 key,
1093 forecast_time: ft,
1094 repr_template: Some(repr),
1095 payload_len,
1096 decode_ok,
1097 decoded_samples_len,
1098 });
1099 }
1100
1101 Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
1102 }
1103 }
1104 1 => {
1105 use std::fs::File;
1106 use std::io::Read;
1107 let mut f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
1108 let mut messages: Vec<MessageSummary> = Vec::new();
1109 let mut msg_idx = 0usize;
1110 loop {
1111 let mut header = [0u8; 8];
1112 match f.read_exact(&mut header) {
1113 Ok(()) => {}
1114 Err(e) => {
1115 if e.kind() == std::io::ErrorKind::UnexpectedEof { break; }
1116 return Err(format!("read GRIB1 header failed: {}", e));
1117 }
1118 }
1119 if &header[0..4] != b"GRIB" || header[7] != 1 { return Err("not a GRIB1 file or header error".to_string()); }
1120 let total_len = read_u24_be(&header[4..7]) as usize;
1121 if total_len < 8 { return Err(format!("invalid message length: {}", total_len)); }
1122 let mut msg = vec![0u8; total_len];
1123 msg[0..8].copy_from_slice(&header);
1124 f.read_exact(&mut msg[8..]).map_err(|e| format!("read GRIB1 message failed: {}", e))?;
1125 let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
1126 let payload_len = total_len;
1127 let mut decode_ok = false;
1128 let mut decoded_samples_len = None;
1129 if let Ok(d) = decode_grib1_first_message(&msg) {
1130 decode_ok = !d.values.is_empty();
1131 decoded_samples_len = Some(d.values.len());
1132 }
1133 messages.push(MessageSummary {
1134 message_index: msg_idx,
1135 key,
1136 forecast_time: None,
1137 repr_template: None,
1138 payload_len,
1139 decode_ok,
1140 decoded_samples_len,
1141 });
1142 msg_idx += 1;
1143 }
1144 Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
1145 }
1146 other => Err(format!("unsupported GRIB edition={} (only 1/2 supported)", other)),
1147 }
1148}
1149
1150pub fn find_candidates_for_param(path: &Path, param: &str) -> Result<Vec<(usize, MatchReason, String, Option<i32>)>, String> {
1152 let alias = crate::param_alias::ParamAlias::for_param(param);
1153 let fs = summarize_file(path)?;
1154 let mut out: Vec<(usize, MatchReason, String, Option<i32>)> = Vec::new();
1155 for m in fs.messages.into_iter() {
1156 let key_l = m.key.to_lowercase();
1157 for t in alias.tokens.iter() {
1159 if key_l.contains(t) {
1160 out.push((m.message_index, MatchReason::Token, m.key.clone(), m.forecast_time));
1161 continue;
1162 }
1163 }
1164 let norm = |s: &str| s.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>();
1166 let nparam = norm(param);
1167 let nkey = norm(&key_l);
1168 if !nparam.is_empty() && nkey.contains(&nparam) {
1169 out.push((m.message_index, MatchReason::NormParam, m.key.clone(), m.forecast_time));
1170 continue;
1171 }
1172 }
1173 Ok(out)
1174}
1175
1176fn nearest_latlon_index_for_grib1(lat_deg: f64, lon_deg: f64, grid: &Grib1LatLonGrid) -> Result<(usize, usize, f64, f64), String> {
1177 if grid.di_deg == 0.0 || grid.dj_deg == 0.0 {
1178 return Err("grid increments are zero".to_string());
1179 }
1180
1181 let lon = normalize_lon_deg(lon_deg);
1182 let lon1 = normalize_lon_deg(grid.lon1_deg);
1183
1184 let mut i = ((lon - lon1) / grid.di_deg).round() as isize;
1185 let mut j = ((grid.lat1_deg - lat_deg) / grid.dj_deg).round() as isize;
1186
1187 if grid.ni > 0 {
1188 let ni = grid.ni as isize;
1189 i = ((i % ni) + ni) % ni;
1190 }
1191 j = j.clamp(0, grid.nj.saturating_sub(1) as isize);
1192
1193 let i_usize = i as usize;
1194 let j_usize = j as usize;
1195 let glat = grid.lat1_deg - (j_usize as f64) * grid.dj_deg;
1196 let glon = normalize_lon_deg(lon1 + (i_usize as f64) * grid.di_deg);
1197 Ok((i_usize, j_usize, glat, glon))
1198}
1199
1200fn extract_grib1_parameter_key(msg: &[u8]) -> Option<String> {
1201 if msg.len() < 8 { return None; }
1202 let offset = 8;
1203 let pds_len = read_u24_be(msg.get(offset..offset + 3)? ) as usize;
1204 if msg.len() < offset + pds_len { return None; }
1205 let pds = &msg[offset..offset + pds_len];
1206 if pds.len() < 9 { return None; }
1207 let param = pds.get(8).copied()?;
1208 Some(format!("p{}", param))
1209}
1210
1211#[derive(Debug, Clone)]
1213pub struct DecodedField {
1214 pub width: usize,
1215 pub height: usize,
1216 pub values: Vec<f32>,
1217 pub aec_backend: Option<&'static str>,
1219}
1220
1221#[derive(Debug, Clone)]
1223pub struct ProbeField {
1224 pub key: String,
1226 pub forecast_time: Option<i32>,
1228 pub i: usize,
1230 pub j: usize,
1231 pub lat: f64,
1233 pub lon: f64,
1234 pub value: f32,
1236}
1237
1238pub fn decode_grib2_first_field(path: &Path) -> Result<DecodedField, String> {
1245 if let Ok(grid) = read_first_grib2_latlon_grid(path) {
1247 match decode_template42_first_message(path, grid.ni, grid.nj) {
1248 Ok(Some(values)) => return Ok(DecodedField { width: grid.ni, height: grid.nj, values, aec_backend: Some("rust-aec-first") }),
1249 Ok(None) | Err(_) => {}
1250 }
1251 }
1252
1253 #[cfg(feature = "grib-support")]
1255 {
1256 use grib::{from_reader, Grib2SubmessageDecoder, GribError, DecodeError};
1257 use std::fs::File;
1258 use std::io::BufReader;
1259
1260 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
1261 let r = BufReader::new(f);
1262 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
1263 let ((_msg_idx, _submsg_idx), submessage) = grib2.iter().next().ok_or_else(|| "No GRIB2 submessages found in file".to_string())?;
1265 let (width, height) = submessage.grid_shape().map_err(|e| format!("grid_shape read failed: {e}"))?;
1266
1267 let s5_opt = match submessage.section5() {
1269 Ok(s5) => Some(s5),
1270 Err(_) => None,
1271 };
1272
1273 let decoder = Grib2SubmessageDecoder::from(submessage).map_err(|e| format!("Decoder creation failed: {e}"))?;
1274
1275 match decoder.dispatch() {
1276 Ok(decoded) => return Ok(DecodedField { width, height, values: decoded.collect(), aec_backend: None }),
1277 Err(err) => {
1278 if matches!(err, GribError::DecodeError(DecodeError::NotSupported(
1280 "GRIB2 code table 5.0 (data representation template number)", 42
1281 ))) {
1282 if let Some(s5) = s5_opt {
1283 use grib::def::grib2::DataRepresentationTemplate;
1284 match s5.payload.template {
1285 DataRepresentationTemplate::_5_42(t) => {
1286 let params = Template42Params {
1287 num_points: s5.payload.num_encoded_points as usize,
1288 bits_per_sample: t.simple.num_bits as u8,
1289 block_size: t.block_size as u32,
1290 rsi: t.ref_sample_interval as u32,
1291 flags_mask: t.mask as u32,
1292 exp: t.simple.exp as i32,
1293 dec: t.simple.dec as i32,
1294 ref_val: t.simple.ref_val as f32,
1295 };
1296 match decode_template42_try_message_payload(path, 0, width, height, ¶ms) {
1297 Ok(values) => return Ok(DecodedField { width, height, values, aec_backend: Some("rust-aec") }),
1298 Err(e) => return Err(format!("rust-aec decode failed: {e}")),
1299 }
1300 }
1301 _ => return Err("template 42 params missing or unexpected template".to_string()),
1302 }
1303 } else {
1304 return Err("template 42 params missing (no Section5)".to_string());
1305 }
1306 } else {
1307 return Err(format!("decode error: {err}"));
1308 }
1309 }
1310 }
1311 }
1312
1313 #[cfg(not(feature = "grib-support"))]
1314 {
1315 return Err("grib-support feature disabled and fast-path did not decode first field".to_string());
1316 }
1317
1318}
1319#[derive(Debug, Clone)]
1323pub struct Grib1LatLonGrid {
1324 pub ni: usize,
1325 pub nj: usize,
1326 pub lat1_deg: f64,
1327 pub lon1_deg: f64,
1328 pub di_deg: f64,
1329 pub dj_deg: f64,
1330 pub scan_mode: u8,
1331}
1332
1333#[derive(Debug, Clone)]
1334pub struct Grib1Decoded {
1335 pub width: usize,
1336 pub height: usize,
1337 pub values: Vec<f32>,
1338}
1339
1340pub fn read_grib1_first_message(path: &Path) -> Result<Vec<u8>, String> {
1342 use std::fs::File;
1343 use std::io::Read;
1344
1345 let mut f = File::open(path).map_err(|e| format!("打开文件失败:{e}"))?;
1346 let mut header = [0u8; 8];
1347 f.read_exact(&mut header)
1348 .map_err(|e| format!("读取 GRIB1 头失败:{e}"))?;
1349
1350 if &header[0..4] != b"GRIB" {
1351 return Err("文件头不是 GRIB".to_string());
1352 }
1353 if header[7] != 1 {
1354 return Err(format!("不是 GRIB1(edition={})", header[7]));
1355 }
1356
1357 let msg_len = ((header[4] as u32) << 16 | (header[5] as u32) << 8 | (header[6] as u32)) as usize;
1358 if msg_len < 8 {
1359 return Err(format!("GRIB1 message length 异常:{msg_len}"));
1360 }
1361
1362 let mut msg = vec![0u8; msg_len];
1363 msg[0..8].copy_from_slice(&header);
1364 f.read_exact(&mut msg[8..])
1365 .map_err(|e| format!("读取 GRIB1 第一个 message 失败:{e}"))?;
1366 Ok(msg)
1367}
1368
1369pub fn parse_grib1_latlon_grid(msg: &[u8]) -> Result<(Grib1LatLonGrid, u8), String> {
1371 if msg.len() < 8 {
1372 return Err("GRIB1 message 太短".to_string());
1373 }
1374 let mut offset = 8;
1375
1376 let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
1377 if pds_len < 28 {
1378 return Err(format!("PDS length 太小:{pds_len}"));
1379 }
1380 let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
1381 let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
1382 let has_gds = (flags & 0x80) != 0;
1383 offset += pds_len;
1384 if !has_gds {
1385 return Err("GRIB1: 缺少 GDS(无法获取网格信息)".to_string());
1386 }
1387
1388 let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
1389 if gds_len < 28 {
1390 return Err(format!("GDS length 太小:{gds_len}"));
1391 }
1392 let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
1393
1394 let grid_type = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
1395 let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
1396 let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
1397
1398 let la1_md = read_i24_grib1(gds.get(10..13).ok_or_else(|| "GDS La1 越界".to_string())?);
1400 let lo1_md = read_i24_grib1(gds.get(13..16).ok_or_else(|| "GDS Lo1 越界".to_string())?);
1401 let di_md = read_u16_be(gds.get(23..25).ok_or_else(|| "GDS Di 越界".to_string())?) as i32;
1402 let dj_md = read_u16_be(gds.get(25..27).ok_or_else(|| "GDS Dj 越界".to_string())?) as i32;
1403 let scan_mode = gds.get(27).copied().ok_or_else(|| "GDS scanning_mode 越界".to_string())?;
1404
1405 let grid = Grib1LatLonGrid {
1406 ni: ni.max(1),
1407 nj: nj.max(1),
1408 lat1_deg: (la1_md as f64) / 1000.0,
1409 lon1_deg: (lo1_md as f64) / 1000.0,
1410 di_deg: (di_md as f64) / 1000.0,
1411 dj_deg: (dj_md as f64) / 1000.0,
1412 scan_mode,
1413 };
1414 Ok((grid, grid_type))
1415}
1416
1417fn read_i24_grib1(bytes: &[u8]) -> i32 {
1418 let b0 = bytes.get(0).copied().unwrap_or(0);
1419 let b1 = bytes.get(1).copied().unwrap_or(0);
1420 let b2 = bytes.get(2).copied().unwrap_or(0);
1421 let magnitude: i32 = ((b0 as i32 & 0x7f) << 16) | ((b1 as i32) << 8) | (b2 as i32);
1422 if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
1423}
1424
1425fn read_i16_grib1(bytes: &[u8]) -> i16 {
1426 let b0 = bytes.get(0).copied().unwrap_or(0);
1427 let b1 = bytes.get(1).copied().unwrap_or(0);
1428 let magnitude = (b1 as i16) + (((b0 & 0x7f) as i16) << 8);
1429 if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
1430}
1431
1432fn read_f32_ibm(bytes: &[u8]) -> f32 {
1433 if bytes.len() < 4 {
1434 return f32::NAN;
1435 }
1436 let sign = if (bytes[0] & 0x80) != 0 { -1.0 } else { 1.0 };
1437 let exponent = (bytes[0] & 0x7f) as i32;
1438 let mantissa = (((bytes[1] as i32) << 16) | ((bytes[2] as i32) << 8) | (bytes[3] as i32)) as f32;
1439 sign * 2.0f32.powi(-24) * mantissa * 16.0f32.powi(exponent - 64)
1440}
1441
1442pub fn decode_grib1_simple_packed_values(
1444 packed: &[u8],
1445 npoints: usize,
1446 bitmap: Option<&[u8]>,
1447 bits_per_value: usize,
1448 ref_value: f32,
1449 binary_scale: i16,
1450) -> Result<Vec<f32>, String> {
1451 let factor = 2.0f32.powi(binary_scale as i32);
1452
1453 let mut values = Vec::with_capacity(npoints);
1454
1455 let mut data_bit = BitCursor::new(packed);
1456 let mut bmp_bit = bitmap.map(BitCursor::new);
1457
1458 for _ in 0..npoints {
1459 if let Some(bmp) = bmp_bit.as_mut() {
1460 let present = bmp.read_bit().ok_or_else(|| "Bitmap 读取越界".to_string())?;
1461 if !present {
1462 values.push(f32::NAN);
1463 continue;
1464 }
1465 }
1466
1467 if bits_per_value == 0 {
1468 values.push(ref_value);
1469 continue;
1470 }
1471
1472 if bits_per_value > 32 {
1473 return Err(format!("bits_per_value 太大:{bits_per_value}(>32 暂不支持)"));
1474 }
1475
1476 let x = data_bit
1477 .read_bits(bits_per_value)
1478 .ok_or_else(|| "BDS packed data 读取越界".to_string())?;
1479
1480 values.push(ref_value + (x as f32) * factor);
1481 }
1482
1483 Ok(values)
1484}
1485
1486struct BitCursor<'a> {
1487 data: &'a [u8],
1488 bit_pos: usize,
1489}
1490
1491impl<'a> BitCursor<'a> {
1492 fn new(data: &'a [u8]) -> Self {
1493 Self { data, bit_pos: 0 }
1494 }
1495
1496 fn read_bit(&mut self) -> Option<bool> {
1497 let v = self.read_bits(1)?;
1498 Some(v != 0)
1499 }
1500
1501 fn read_bits(&mut self, nbits: usize) -> Option<u32> {
1502 if nbits == 0 {
1503 return Some(0);
1504 }
1505
1506 let mut out: u32 = 0;
1507 for _ in 0..nbits {
1508 let byte_idx = self.bit_pos / 8;
1509 let bit_in_byte = self.bit_pos % 8;
1510 let byte = *self.data.get(byte_idx)?;
1511 let bit = (byte >> (7 - bit_in_byte)) & 1;
1512 out = (out << 1) | (bit as u32);
1513 self.bit_pos += 1;
1514 }
1515 Some(out)
1516 }
1517}
1518
1519pub fn decode_grib1_first_message(msg: &[u8]) -> Result<Grib1Decoded, String> {
1521 if msg.len() < 8 {
1522 return Err("GRIB1 message 太短".to_string());
1523 }
1524 if &msg[0..4] != b"GRIB" {
1525 return Err("GRIB1 header 不正确".to_string());
1526 }
1527 if msg[7] != 1 {
1528 return Err(format!("不是 GRIB1(edition={})", msg[7]));
1529 }
1530
1531 let total_len = ((msg[4] as u32) << 16 | (msg[5] as u32) << 8 | (msg[6] as u32)) as usize;
1532 if total_len != msg.len() {
1533 if total_len > msg.len() {
1534 return Err(format!("GRIB1 指示的 message 长度={total_len},但实际读取到 {} 字节", msg.len()));
1535 }
1536 }
1537
1538 let mut offset = 8;
1539
1540 let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
1542 if pds_len < 28 {
1543 return Err(format!("PDS length 太小:{pds_len}"));
1544 }
1545 let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
1546 let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
1547 let has_gds = (flags & 0x80) != 0;
1548 let has_bms = (flags & 0x40) != 0;
1549 offset += pds_len;
1550
1551 let (width, height, data_representation_type) = if has_gds {
1553 let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
1554 if gds_len < 11 {
1555 return Err(format!("GDS length 太小:{gds_len}"));
1556 }
1557 let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
1558 let drt = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
1559 let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
1560 let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
1561 offset += gds_len;
1562 match drt {
1563 0 | 10 => (ni.max(1), nj.max(1), drt),
1564 other => return Err(format!("GRIB1: 暂不支持 data_representation_type={other}(目前支持 0=LatLon, 10=RotatedLatLon)")),
1565 }
1566 } else {
1567 return Err("GRIB1: 缺少 GDS(无法获取网格尺寸)".to_string());
1568 };
1569
1570 let bitmap: Option<Vec<u8>> = if has_bms {
1572 let bms_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BMS 长度越界".to_string())?) as usize;
1573 if bms_len < 6 {
1574 return Err(format!("BMS length 太小:{bms_len}"));
1575 }
1576 let bms = msg.get(offset..offset + bms_len).ok_or_else(|| "BMS 越界".to_string())?;
1577 let table_ref = read_u16_be(bms.get(4..6).ok_or_else(|| "BMS table_reference 越界".to_string())?);
1578 if table_ref != 0 {
1579 return Err(format!("GRIB1: BMS 使用了预定义 bitmap(table_reference={table_ref}),暂不支持"));
1580 }
1581 let bmp = bms.get(6..).unwrap_or(&[]).to_vec();
1582 offset += bms_len;
1583 Some(bmp)
1584 } else {
1585 None
1586 };
1587
1588 let bds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BDS 长度越界".to_string())?) as usize;
1590 if bds_len < 11 {
1591 return Err(format!("BDS length 太小:{bds_len}"));
1592 }
1593 let bds = msg.get(offset..offset + bds_len).ok_or_else(|| "BDS 越界".to_string())?;
1594 let bds_flags = bds.get(3).copied().ok_or_else(|| "BDS flags 越界".to_string())?;
1595
1596 let has_complex_packing = (bds_flags & 0b0100_0000) != 0;
1597 if has_complex_packing {
1598 return Err(format!("GRIB1: 暂不支持复杂 packing(BDS flags=0x{bds_flags:02x},grid_type={data_representation_type})"));
1599 }
1600
1601 let binary_scale = read_i16_grib1(bds.get(4..6).ok_or_else(|| "BDS binary_scale 越界".to_string())?);
1602 let ref_value = read_f32_ibm(bds.get(6..10).ok_or_else(|| "BDS ref_value 越界".to_string())?);
1603 let bits_per_value = bds.get(10).copied().ok_or_else(|| "BDS bits_per_value 越界".to_string())? as usize;
1604 let packed = bds.get(11..).unwrap_or(&[]);
1605
1606 let npoints = width
1607 .checked_mul(height)
1608 .ok_or_else(|| "width*height 溢出".to_string())?;
1609
1610 let values = decode_grib1_simple_packed_values(
1611 packed,
1612 npoints,
1613 bitmap.as_deref(),
1614 bits_per_value,
1615 ref_value,
1616 binary_scale,
1617 )?;
1618
1619 Ok(Grib1Decoded { width, height, values })
1620}
1621
1622fn read_u24_be(bytes: &[u8]) -> u32 {
1624 let b0 = bytes.get(0).copied().unwrap_or(0) as u32;
1625 let b1 = bytes.get(1).copied().unwrap_or(0) as u32;
1626 let b2 = bytes.get(2).copied().unwrap_or(0) as u32;
1627 (b0 << 16) | (b1 << 8) | b2
1628}
1629
1630fn read_u16_be(bytes: &[u8]) -> u16 {
1631 let b0 = bytes.get(0).copied().unwrap_or(0) as u16;
1632 let b1 = bytes.get(1).copied().unwrap_or(0) as u16;
1633 (b0 << 8) | b1
1634}
1635
1636
1637