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 grid = read_first_grib2_latlon_grid(path)?;
889 if grid.scan_mode != 0 {
890 return Err(format!("unsupported scanning_mode=0x{:02x}", grid.scan_mode));
891 }
892
893 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
894 let r = BufReader::new(f);
895 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
896
897 let decoded_template42_by_message = match decode_template42_for_file_by_message(path, grid.ni, grid.nj) {
899 Ok(v) => v,
900 Err(_) => Vec::new(),
901 };
902
903 let mut out: Vec<ProbeField> = Vec::new();
904
905 for (idx, ((_msg_idx, _submsg_idx), submessage)) in grib2.iter().enumerate() {
906 let desc = submessage.describe();
907 let key = if let Some(sn) = crate::extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
908 sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
909 } else {
910 desc.lines().next().unwrap_or("<desc>").to_string()
911 };
912 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));
913
914 let (width, height) = match submessage.grid_shape() {
915 Ok((w,h)) => (w,h),
916 Err(_) => continue,
917 };
918 if width != grid.ni || height != grid.nj { continue; }
919
920 let values_opt = decoded_template42_by_message.get(idx).and_then(|o| o.clone());
922 let values: Vec<f32> = if let Some(v) = values_opt { v } else {
923 let template42_params_opt: Option<Template42Params> = match submessage.section5() {
925 Ok(s5) => match s5.payload.template {
926 grib::def::grib2::DataRepresentationTemplate::_5_42(t) => Some(Template42Params {
927 num_points: s5.payload.num_encoded_points as usize,
928 bits_per_sample: t.simple.num_bits as u8,
929 block_size: t.block_size as u32,
930 rsi: t.ref_sample_interval as u32,
931 flags_mask: t.mask as u32,
932 exp: t.simple.exp as i32,
933 dec: t.simple.dec as i32,
934 ref_val: t.simple.ref_val as f32,
935 }),
936 _ => None,
937 },
938 Err(_) => None,
939 };
940
941 let decoder = match Grib2SubmessageDecoder::from(submessage) {
942 Ok(d) => d,
943 Err(_) => continue,
944 };
945 match decoder.dispatch() {
946 Ok(decoded) => decoded.collect::<Vec<f32>>(),
947 Err(_) => {
948 if let Some(params) = template42_params_opt {
950 match decode_template42_try_message_payload(path, idx, width, height, ¶ms) {
951 Ok(vv) => vv,
952 Err(_) => continue,
953 }
954 } else {
955 continue;
956 }
957 }
958 }
959 };
960
961 let mut dlon = lon_deg - grid.lon1_deg;
963 while dlon < 0.0 { dlon += 360.0; }
964 while dlon >= 360.0 { dlon -= 360.0; }
965 let i_f = dlon / grid.di_deg;
966 let j_f = (grid.lat1_deg - lat_deg) / grid.dj_deg;
967 if !i_f.is_finite() || !j_f.is_finite() { continue; }
968 let ni = width as isize; let nj = height as isize;
969 let i0 = i_f.floor() as isize; let j0 = j_f.floor() as isize; let i1 = i0 + 1; let j1 = j0 + 1;
970 let dx = i_f - (i0 as f64); let dy = j_f - (j0 as f64);
971 let i0w = (((i0 % ni) + ni) % ni) as usize; let i1w = (((i1 % ni) + ni) % ni) as usize;
972 let j0c = j0.clamp(0, nj - 1) as usize; let j1c = j1.clamp(0, nj - 1) as usize;
973 let idx00 = j0c.saturating_mul(width).saturating_add(i0w);
974 let idx10 = j0c.saturating_mul(width).saturating_add(i1w);
975 let idx01 = j1c.saturating_mul(width).saturating_add(i0w);
976 let idx11 = j1c.saturating_mul(width).saturating_add(i1w);
977 let s00 = values.get(idx00).copied().unwrap_or(f32::NAN);
978 let s10 = values.get(idx10).copied().unwrap_or(f32::NAN);
979 let s01 = values.get(idx01).copied().unwrap_or(f32::NAN);
980 let s11 = values.get(idx11).copied().unwrap_or(f32::NAN);
981 if !s00.is_finite() || !s10.is_finite() || !s01.is_finite() || !s11.is_finite() { continue; }
982 let v0 = s00 * (1.0 - dx as f32) + s10 * (dx as f32);
983 let v1 = s01 * (1.0 - dx as f32) + s11 * (dx as f32);
984 let value = v0 * (1.0 - dy as f32) + v1 * (dy as f32);
985
986 let glat = grid.lat1_deg - j_f * grid.dj_deg;
988 let glon = normalize_lon_deg(grid.lon1_deg + i_f * grid.di_deg);
989
990 out.push(ProbeField { key, forecast_time: ft, i: i0w, j: j0c, lat: glat, lon: glon, value });
991 }
992
993 Ok(out)
994 }
995}
996
997pub fn probe_all_fields_at_lat_lon(path: &Path, lat_deg: f64, lon_deg: f64) -> Result<Vec<ProbeField>, String> {
1000 match detect_grib_edition(path)? {
1001 2 => probe_all_grib2_latlon(path, lat_deg, lon_deg),
1002 1 => {
1003 let msg = read_grib1_first_message(path)?;
1005 let (grid, _drt) = parse_grib1_latlon_grid(&msg)?;
1006 let decoded = decode_grib1_first_message(&msg)?;
1007 let (i, j, glat, glon) = nearest_latlon_index_for_grib1(lat_deg, lon_deg, &grid)?;
1008 let idx = j.saturating_mul(decoded.width).saturating_add(i);
1009 let val = decoded.values.get(idx).copied().unwrap_or(f32::NAN);
1010 let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
1011 Ok(vec![ProbeField { key, forecast_time: None, i, j, lat: glat, lon: glon, value: val }])
1012 }
1013 other => Err(format!("unsupported GRIB edition {} (only 1/2 supported)", other)),
1014 }
1015}
1016
1017pub fn summarize_file(path: &Path) -> Result<FileSummary, String> {
1019 match detect_grib_edition(path)? {
1020 2 => {
1021 #[cfg(not(feature = "grib-support"))]
1022 return Err("summarize_file requires the 'grib-support' feature".to_string());
1023
1024 #[cfg(feature = "grib-support")]
1025 {
1026 use std::fs::File;
1027 use std::io::BufReader;
1028 use grib::from_reader;
1029
1030 let f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
1031 let r = BufReader::new(f);
1032 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {}", e))?;
1033
1034 let payloads = read_grib2_section7_payloads_by_message(path).unwrap_or_default();
1036 let params_vec = read_grib2_section5_template42_params_by_message(path).unwrap_or_default();
1037
1038 let mut messages: Vec<MessageSummary> = Vec::new();
1039 for (idx, ((_msg_idx, _submsg_idx), sub)) in grib2.iter().enumerate() {
1040 let desc = sub.describe();
1041 let key = if let Some(sn) = extract_tag_value_case_insensitive(&desc, &["shortname", "short_name", "short name"]) {
1042 sn.trim_matches(|c: char| c == '"' || c == '\'' || c.is_whitespace()).to_string()
1043 } else {
1044 desc.lines().next().unwrap_or("<desc>").to_string()
1045 };
1046 let ft = extract_tag_value_case_insensitive(&desc, &["forecast time", "forecasttime", "forecast_time", "forecast"]).and_then(|s| parse_first_int_from_str(&s));
1047 let repr = sub.repr_def().repr_tmpl_num() as u16;
1048 let payload_len = payloads.get(idx).map(|v| v.len()).unwrap_or(0);
1049
1050 let (width, height) = match sub.grid_shape() {
1052 Ok((w,h)) => (w,h),
1053 Err(_) => continue,
1054 };
1055 let mut decode_ok = false;
1057 let mut decoded_samples_len: Option<usize> = None;
1058
1059 {
1061 use grib::Grib2SubmessageDecoder;
1062 if let Ok(decoder) = Grib2SubmessageDecoder::from(sub) {
1064 if let Ok(decoded_iter) = decoder.dispatch() {
1065 let collected: Vec<f32> = decoded_iter.collect();
1066 decoded_samples_len = Some(collected.len());
1067 decode_ok = !collected.is_empty();
1068 }
1069 }
1070 }
1071
1072 if !decode_ok {
1074 if let Some(params_opt) = params_vec.get(idx) {
1075 if let Some(params) = params_opt.as_ref() {
1076 if payload_len > 0 {
1077 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) {
1078 decode_ok = !vv.is_empty();
1079 decoded_samples_len = Some(vv.len());
1080 }
1081 }
1082 }
1083 }
1084 }
1085
1086 messages.push(MessageSummary {
1087 message_index: idx,
1088 key,
1089 forecast_time: ft,
1090 repr_template: Some(repr),
1091 payload_len,
1092 decode_ok,
1093 decoded_samples_len,
1094 });
1095 }
1096
1097 Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
1098 }
1099 }
1100 1 => {
1101 use std::fs::File;
1102 use std::io::Read;
1103 let mut f = File::open(path).map_err(|e| format!("open file failed: {}", e))?;
1104 let mut messages: Vec<MessageSummary> = Vec::new();
1105 let mut msg_idx = 0usize;
1106 loop {
1107 let mut header = [0u8; 8];
1108 match f.read_exact(&mut header) {
1109 Ok(()) => {}
1110 Err(e) => {
1111 if e.kind() == std::io::ErrorKind::UnexpectedEof { break; }
1112 return Err(format!("read GRIB1 header failed: {}", e));
1113 }
1114 }
1115 if &header[0..4] != b"GRIB" || header[7] != 1 { return Err("not a GRIB1 file or header error".to_string()); }
1116 let total_len = read_u24_be(&header[4..7]) as usize;
1117 if total_len < 8 { return Err(format!("invalid message length: {}", total_len)); }
1118 let mut msg = vec![0u8; total_len];
1119 msg[0..8].copy_from_slice(&header);
1120 f.read_exact(&mut msg[8..]).map_err(|e| format!("read GRIB1 message failed: {}", e))?;
1121 let key = extract_grib1_parameter_key(&msg).unwrap_or_else(|| "<unknown>".to_string());
1122 let payload_len = total_len;
1123 let mut decode_ok = false;
1124 let mut decoded_samples_len = None;
1125 if let Ok(d) = decode_grib1_first_message(&msg) {
1126 decode_ok = !d.values.is_empty();
1127 decoded_samples_len = Some(d.values.len());
1128 }
1129 messages.push(MessageSummary {
1130 message_index: msg_idx,
1131 key,
1132 forecast_time: None,
1133 repr_template: None,
1134 payload_len,
1135 decode_ok,
1136 decoded_samples_len,
1137 });
1138 msg_idx += 1;
1139 }
1140 Ok(FileSummary { path: path.to_path_buf(), n_messages: messages.len(), messages })
1141 }
1142 other => Err(format!("unsupported GRIB edition={} (only 1/2 supported)", other)),
1143 }
1144}
1145
1146pub fn find_candidates_for_param(path: &Path, param: &str) -> Result<Vec<(usize, MatchReason, String, Option<i32>)>, String> {
1148 let alias = crate::param_alias::ParamAlias::for_param(param);
1149 let fs = summarize_file(path)?;
1150 let mut out: Vec<(usize, MatchReason, String, Option<i32>)> = Vec::new();
1151 for m in fs.messages.into_iter() {
1152 let key_l = m.key.to_lowercase();
1153 for t in alias.tokens.iter() {
1155 if key_l.contains(t) {
1156 out.push((m.message_index, MatchReason::Token, m.key.clone(), m.forecast_time));
1157 continue;
1158 }
1159 }
1160 let norm = |s: &str| s.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>();
1162 let nparam = norm(param);
1163 let nkey = norm(&key_l);
1164 if !nparam.is_empty() && nkey.contains(&nparam) {
1165 out.push((m.message_index, MatchReason::NormParam, m.key.clone(), m.forecast_time));
1166 continue;
1167 }
1168 }
1169 Ok(out)
1170}
1171
1172fn nearest_latlon_index_for_grib1(lat_deg: f64, lon_deg: f64, grid: &Grib1LatLonGrid) -> Result<(usize, usize, f64, f64), String> {
1173 if grid.di_deg == 0.0 || grid.dj_deg == 0.0 {
1174 return Err("grid increments are zero".to_string());
1175 }
1176
1177 let lon = normalize_lon_deg(lon_deg);
1178 let lon1 = normalize_lon_deg(grid.lon1_deg);
1179
1180 let mut i = ((lon - lon1) / grid.di_deg).round() as isize;
1181 let mut j = ((grid.lat1_deg - lat_deg) / grid.dj_deg).round() as isize;
1182
1183 if grid.ni > 0 {
1184 let ni = grid.ni as isize;
1185 i = ((i % ni) + ni) % ni;
1186 }
1187 j = j.clamp(0, grid.nj.saturating_sub(1) as isize);
1188
1189 let i_usize = i as usize;
1190 let j_usize = j as usize;
1191 let glat = grid.lat1_deg - (j_usize as f64) * grid.dj_deg;
1192 let glon = normalize_lon_deg(lon1 + (i_usize as f64) * grid.di_deg);
1193 Ok((i_usize, j_usize, glat, glon))
1194}
1195
1196fn extract_grib1_parameter_key(msg: &[u8]) -> Option<String> {
1197 if msg.len() < 8 { return None; }
1198 let offset = 8;
1199 let pds_len = read_u24_be(msg.get(offset..offset + 3)? ) as usize;
1200 if msg.len() < offset + pds_len { return None; }
1201 let pds = &msg[offset..offset + pds_len];
1202 if pds.len() < 9 { return None; }
1203 let param = pds.get(8).copied()?;
1204 Some(format!("p{}", param))
1205}
1206
1207#[derive(Debug, Clone)]
1209pub struct DecodedField {
1210 pub width: usize,
1211 pub height: usize,
1212 pub values: Vec<f32>,
1213 pub aec_backend: Option<&'static str>,
1215}
1216
1217#[derive(Debug, Clone)]
1219pub struct ProbeField {
1220 pub key: String,
1222 pub forecast_time: Option<i32>,
1224 pub i: usize,
1226 pub j: usize,
1227 pub lat: f64,
1229 pub lon: f64,
1230 pub value: f32,
1232}
1233
1234pub fn decode_grib2_first_field(path: &Path) -> Result<DecodedField, String> {
1241 if let Ok(grid) = read_first_grib2_latlon_grid(path) {
1243 match decode_template42_first_message(path, grid.ni, grid.nj) {
1244 Ok(Some(values)) => return Ok(DecodedField { width: grid.ni, height: grid.nj, values, aec_backend: Some("rust-aec-first") }),
1245 Ok(None) | Err(_) => {}
1246 }
1247 }
1248
1249 #[cfg(feature = "grib-support")]
1251 {
1252 use grib::{from_reader, Grib2SubmessageDecoder, GribError, DecodeError};
1253 use std::fs::File;
1254 use std::io::BufReader;
1255
1256 let f = File::open(path).map_err(|e| format!("open file failed: {e}"))?;
1257 let r = BufReader::new(f);
1258 let grib2 = from_reader(r).map_err(|e| format!("GRIB parse failed: {e}"))?;
1259 let ((_msg_idx, _submsg_idx), submessage) = grib2.iter().next().ok_or_else(|| "No GRIB2 submessages found in file".to_string())?;
1261 let (width, height) = submessage.grid_shape().map_err(|e| format!("grid_shape read failed: {e}"))?;
1262
1263 let s5_opt = match submessage.section5() {
1265 Ok(s5) => Some(s5),
1266 Err(_) => None,
1267 };
1268
1269 let decoder = Grib2SubmessageDecoder::from(submessage).map_err(|e| format!("Decoder creation failed: {e}"))?;
1270
1271 match decoder.dispatch() {
1272 Ok(decoded) => return Ok(DecodedField { width, height, values: decoded.collect(), aec_backend: None }),
1273 Err(err) => {
1274 if matches!(err, GribError::DecodeError(DecodeError::NotSupported(
1276 "GRIB2 code table 5.0 (data representation template number)", 42
1277 ))) {
1278 if let Some(s5) = s5_opt {
1279 use grib::def::grib2::DataRepresentationTemplate;
1280 match s5.payload.template {
1281 DataRepresentationTemplate::_5_42(t) => {
1282 let params = Template42Params {
1283 num_points: s5.payload.num_encoded_points as usize,
1284 bits_per_sample: t.simple.num_bits as u8,
1285 block_size: t.block_size as u32,
1286 rsi: t.ref_sample_interval as u32,
1287 flags_mask: t.mask as u32,
1288 exp: t.simple.exp as i32,
1289 dec: t.simple.dec as i32,
1290 ref_val: t.simple.ref_val as f32,
1291 };
1292 match decode_template42_try_message_payload(path, 0, width, height, ¶ms) {
1293 Ok(values) => return Ok(DecodedField { width, height, values, aec_backend: Some("rust-aec") }),
1294 Err(e) => return Err(format!("rust-aec decode failed: {e}")),
1295 }
1296 }
1297 _ => return Err("template 42 params missing or unexpected template".to_string()),
1298 }
1299 } else {
1300 return Err("template 42 params missing (no Section5)".to_string());
1301 }
1302 } else {
1303 return Err(format!("decode error: {err}"));
1304 }
1305 }
1306 }
1307 }
1308
1309 #[cfg(not(feature = "grib-support"))]
1310 {
1311 return Err("grib-support feature disabled and fast-path did not decode first field".to_string());
1312 }
1313
1314}
1315#[derive(Debug, Clone)]
1319pub struct Grib1LatLonGrid {
1320 pub ni: usize,
1321 pub nj: usize,
1322 pub lat1_deg: f64,
1323 pub lon1_deg: f64,
1324 pub di_deg: f64,
1325 pub dj_deg: f64,
1326 pub scan_mode: u8,
1327}
1328
1329#[derive(Debug, Clone)]
1330pub struct Grib1Decoded {
1331 pub width: usize,
1332 pub height: usize,
1333 pub values: Vec<f32>,
1334}
1335
1336pub fn read_grib1_first_message(path: &Path) -> Result<Vec<u8>, String> {
1338 use std::fs::File;
1339 use std::io::Read;
1340
1341 let mut f = File::open(path).map_err(|e| format!("打开文件失败:{e}"))?;
1342 let mut header = [0u8; 8];
1343 f.read_exact(&mut header)
1344 .map_err(|e| format!("读取 GRIB1 头失败:{e}"))?;
1345
1346 if &header[0..4] != b"GRIB" {
1347 return Err("文件头不是 GRIB".to_string());
1348 }
1349 if header[7] != 1 {
1350 return Err(format!("不是 GRIB1(edition={})", header[7]));
1351 }
1352
1353 let msg_len = ((header[4] as u32) << 16 | (header[5] as u32) << 8 | (header[6] as u32)) as usize;
1354 if msg_len < 8 {
1355 return Err(format!("GRIB1 message length 异常:{msg_len}"));
1356 }
1357
1358 let mut msg = vec![0u8; msg_len];
1359 msg[0..8].copy_from_slice(&header);
1360 f.read_exact(&mut msg[8..])
1361 .map_err(|e| format!("读取 GRIB1 第一个 message 失败:{e}"))?;
1362 Ok(msg)
1363}
1364
1365pub fn parse_grib1_latlon_grid(msg: &[u8]) -> Result<(Grib1LatLonGrid, u8), String> {
1367 if msg.len() < 8 {
1368 return Err("GRIB1 message 太短".to_string());
1369 }
1370 let mut offset = 8;
1371
1372 let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
1373 if pds_len < 28 {
1374 return Err(format!("PDS length 太小:{pds_len}"));
1375 }
1376 let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
1377 let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
1378 let has_gds = (flags & 0x80) != 0;
1379 offset += pds_len;
1380 if !has_gds {
1381 return Err("GRIB1: 缺少 GDS(无法获取网格信息)".to_string());
1382 }
1383
1384 let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
1385 if gds_len < 28 {
1386 return Err(format!("GDS length 太小:{gds_len}"));
1387 }
1388 let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
1389
1390 let grid_type = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
1391 let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
1392 let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
1393
1394 let la1_md = read_i24_grib1(gds.get(10..13).ok_or_else(|| "GDS La1 越界".to_string())?);
1396 let lo1_md = read_i24_grib1(gds.get(13..16).ok_or_else(|| "GDS Lo1 越界".to_string())?);
1397 let di_md = read_u16_be(gds.get(23..25).ok_or_else(|| "GDS Di 越界".to_string())?) as i32;
1398 let dj_md = read_u16_be(gds.get(25..27).ok_or_else(|| "GDS Dj 越界".to_string())?) as i32;
1399 let scan_mode = gds.get(27).copied().ok_or_else(|| "GDS scanning_mode 越界".to_string())?;
1400
1401 let grid = Grib1LatLonGrid {
1402 ni: ni.max(1),
1403 nj: nj.max(1),
1404 lat1_deg: (la1_md as f64) / 1000.0,
1405 lon1_deg: (lo1_md as f64) / 1000.0,
1406 di_deg: (di_md as f64) / 1000.0,
1407 dj_deg: (dj_md as f64) / 1000.0,
1408 scan_mode,
1409 };
1410 Ok((grid, grid_type))
1411}
1412
1413fn read_i24_grib1(bytes: &[u8]) -> i32 {
1414 let b0 = bytes.get(0).copied().unwrap_or(0);
1415 let b1 = bytes.get(1).copied().unwrap_or(0);
1416 let b2 = bytes.get(2).copied().unwrap_or(0);
1417 let magnitude: i32 = ((b0 as i32 & 0x7f) << 16) | ((b1 as i32) << 8) | (b2 as i32);
1418 if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
1419}
1420
1421fn read_i16_grib1(bytes: &[u8]) -> i16 {
1422 let b0 = bytes.get(0).copied().unwrap_or(0);
1423 let b1 = bytes.get(1).copied().unwrap_or(0);
1424 let magnitude = (b1 as i16) + (((b0 & 0x7f) as i16) << 8);
1425 if (b0 & 0x80) != 0 { -magnitude } else { magnitude }
1426}
1427
1428fn read_f32_ibm(bytes: &[u8]) -> f32 {
1429 if bytes.len() < 4 {
1430 return f32::NAN;
1431 }
1432 let sign = if (bytes[0] & 0x80) != 0 { -1.0 } else { 1.0 };
1433 let exponent = (bytes[0] & 0x7f) as i32;
1434 let mantissa = (((bytes[1] as i32) << 16) | ((bytes[2] as i32) << 8) | (bytes[3] as i32)) as f32;
1435 sign * 2.0f32.powi(-24) * mantissa * 16.0f32.powi(exponent - 64)
1436}
1437
1438pub fn decode_grib1_simple_packed_values(
1440 packed: &[u8],
1441 npoints: usize,
1442 bitmap: Option<&[u8]>,
1443 bits_per_value: usize,
1444 ref_value: f32,
1445 binary_scale: i16,
1446) -> Result<Vec<f32>, String> {
1447 let factor = 2.0f32.powi(binary_scale as i32);
1448
1449 let mut values = Vec::with_capacity(npoints);
1450
1451 let mut data_bit = BitCursor::new(packed);
1452 let mut bmp_bit = bitmap.map(BitCursor::new);
1453
1454 for _ in 0..npoints {
1455 if let Some(bmp) = bmp_bit.as_mut() {
1456 let present = bmp.read_bit().ok_or_else(|| "Bitmap 读取越界".to_string())?;
1457 if !present {
1458 values.push(f32::NAN);
1459 continue;
1460 }
1461 }
1462
1463 if bits_per_value == 0 {
1464 values.push(ref_value);
1465 continue;
1466 }
1467
1468 if bits_per_value > 32 {
1469 return Err(format!("bits_per_value 太大:{bits_per_value}(>32 暂不支持)"));
1470 }
1471
1472 let x = data_bit
1473 .read_bits(bits_per_value)
1474 .ok_or_else(|| "BDS packed data 读取越界".to_string())?;
1475
1476 values.push(ref_value + (x as f32) * factor);
1477 }
1478
1479 Ok(values)
1480}
1481
1482struct BitCursor<'a> {
1483 data: &'a [u8],
1484 bit_pos: usize,
1485}
1486
1487impl<'a> BitCursor<'a> {
1488 fn new(data: &'a [u8]) -> Self {
1489 Self { data, bit_pos: 0 }
1490 }
1491
1492 fn read_bit(&mut self) -> Option<bool> {
1493 let v = self.read_bits(1)?;
1494 Some(v != 0)
1495 }
1496
1497 fn read_bits(&mut self, nbits: usize) -> Option<u32> {
1498 if nbits == 0 {
1499 return Some(0);
1500 }
1501
1502 let mut out: u32 = 0;
1503 for _ in 0..nbits {
1504 let byte_idx = self.bit_pos / 8;
1505 let bit_in_byte = self.bit_pos % 8;
1506 let byte = *self.data.get(byte_idx)?;
1507 let bit = (byte >> (7 - bit_in_byte)) & 1;
1508 out = (out << 1) | (bit as u32);
1509 self.bit_pos += 1;
1510 }
1511 Some(out)
1512 }
1513}
1514
1515pub fn decode_grib1_first_message(msg: &[u8]) -> Result<Grib1Decoded, String> {
1517 if msg.len() < 8 {
1518 return Err("GRIB1 message 太短".to_string());
1519 }
1520 if &msg[0..4] != b"GRIB" {
1521 return Err("GRIB1 header 不正确".to_string());
1522 }
1523 if msg[7] != 1 {
1524 return Err(format!("不是 GRIB1(edition={})", msg[7]));
1525 }
1526
1527 let total_len = ((msg[4] as u32) << 16 | (msg[5] as u32) << 8 | (msg[6] as u32)) as usize;
1528 if total_len != msg.len() {
1529 if total_len > msg.len() {
1530 return Err(format!("GRIB1 指示的 message 长度={total_len},但实际读取到 {} 字节", msg.len()));
1531 }
1532 }
1533
1534 let mut offset = 8;
1535
1536 let pds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "PDS 长度越界".to_string())?) as usize;
1538 if pds_len < 28 {
1539 return Err(format!("PDS length 太小:{pds_len}"));
1540 }
1541 let pds = msg.get(offset..offset + pds_len).ok_or_else(|| "PDS 越界".to_string())?;
1542 let flags = pds.get(7).copied().ok_or_else(|| "PDS flags 越界".to_string())?;
1543 let has_gds = (flags & 0x80) != 0;
1544 let has_bms = (flags & 0x40) != 0;
1545 offset += pds_len;
1546
1547 let (width, height, data_representation_type) = if has_gds {
1549 let gds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "GDS 长度越界".to_string())?) as usize;
1550 if gds_len < 11 {
1551 return Err(format!("GDS length 太小:{gds_len}"));
1552 }
1553 let gds = msg.get(offset..offset + gds_len).ok_or_else(|| "GDS 越界".to_string())?;
1554 let drt = gds.get(5).copied().ok_or_else(|| "GDS data_representation_type 越界".to_string())?;
1555 let ni = read_u16_be(gds.get(6..8).ok_or_else(|| "GDS Ni 越界".to_string())?) as usize;
1556 let nj = read_u16_be(gds.get(8..10).ok_or_else(|| "GDS Nj 越界".to_string())?) as usize;
1557 offset += gds_len;
1558 match drt {
1559 0 | 10 => (ni.max(1), nj.max(1), drt),
1560 other => return Err(format!("GRIB1: 暂不支持 data_representation_type={other}(目前支持 0=LatLon, 10=RotatedLatLon)")),
1561 }
1562 } else {
1563 return Err("GRIB1: 缺少 GDS(无法获取网格尺寸)".to_string());
1564 };
1565
1566 let bitmap: Option<Vec<u8>> = if has_bms {
1568 let bms_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BMS 长度越界".to_string())?) as usize;
1569 if bms_len < 6 {
1570 return Err(format!("BMS length 太小:{bms_len}"));
1571 }
1572 let bms = msg.get(offset..offset + bms_len).ok_or_else(|| "BMS 越界".to_string())?;
1573 let table_ref = read_u16_be(bms.get(4..6).ok_or_else(|| "BMS table_reference 越界".to_string())?);
1574 if table_ref != 0 {
1575 return Err(format!("GRIB1: BMS 使用了预定义 bitmap(table_reference={table_ref}),暂不支持"));
1576 }
1577 let bmp = bms.get(6..).unwrap_or(&[]).to_vec();
1578 offset += bms_len;
1579 Some(bmp)
1580 } else {
1581 None
1582 };
1583
1584 let bds_len = read_u24_be(msg.get(offset..offset + 3).ok_or_else(|| "BDS 长度越界".to_string())?) as usize;
1586 if bds_len < 11 {
1587 return Err(format!("BDS length 太小:{bds_len}"));
1588 }
1589 let bds = msg.get(offset..offset + bds_len).ok_or_else(|| "BDS 越界".to_string())?;
1590 let bds_flags = bds.get(3).copied().ok_or_else(|| "BDS flags 越界".to_string())?;
1591
1592 let has_complex_packing = (bds_flags & 0b0100_0000) != 0;
1593 if has_complex_packing {
1594 return Err(format!("GRIB1: 暂不支持复杂 packing(BDS flags=0x{bds_flags:02x},grid_type={data_representation_type})"));
1595 }
1596
1597 let binary_scale = read_i16_grib1(bds.get(4..6).ok_or_else(|| "BDS binary_scale 越界".to_string())?);
1598 let ref_value = read_f32_ibm(bds.get(6..10).ok_or_else(|| "BDS ref_value 越界".to_string())?);
1599 let bits_per_value = bds.get(10).copied().ok_or_else(|| "BDS bits_per_value 越界".to_string())? as usize;
1600 let packed = bds.get(11..).unwrap_or(&[]);
1601
1602 let npoints = width
1603 .checked_mul(height)
1604 .ok_or_else(|| "width*height 溢出".to_string())?;
1605
1606 let values = decode_grib1_simple_packed_values(
1607 packed,
1608 npoints,
1609 bitmap.as_deref(),
1610 bits_per_value,
1611 ref_value,
1612 binary_scale,
1613 )?;
1614
1615 Ok(Grib1Decoded { width, height, values })
1616}
1617
1618fn read_u24_be(bytes: &[u8]) -> u32 {
1620 let b0 = bytes.get(0).copied().unwrap_or(0) as u32;
1621 let b1 = bytes.get(1).copied().unwrap_or(0) as u32;
1622 let b2 = bytes.get(2).copied().unwrap_or(0) as u32;
1623 (b0 << 16) | (b1 << 8) | b2
1624}
1625
1626fn read_u16_be(bytes: &[u8]) -> u16 {
1627 let b0 = bytes.get(0).copied().unwrap_or(0) as u16;
1628 let b1 = bytes.get(1).copied().unwrap_or(0) as u16;
1629 (b0 << 8) | b1
1630}
1631
1632
1633