1use std::slice::Iter;
2
3use chrono::{DateTime, LocalResult, TimeZone, Utc};
4
5use crate::{
6 codetables::SUPPORTED_PROD_DEF_TEMPLATE_NUMBERS,
7 datatypes::*,
8 error::*,
9 grid::{
10 GaussianGridDefinition, GridPointIterator, LambertGridDefinition, LatLonGridDefinition,
11 },
12 helpers::{read_as, GribInt},
13 GridPointIndexIterator, PolarStereographicGridDefinition,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Indicator {
18 pub discipline: u8,
20 pub total_length: u64,
22}
23
24impl Indicator {
25 pub(crate) fn from_slice(slice: &[u8]) -> Result<Self, ParseError> {
26 let discipline = slice[6];
27 let version = slice[7];
28 if version != 2 {
29 return Err(ParseError::GRIBVersionMismatch(version));
30 }
31
32 let total_length = read_as!(u64, slice, 8);
33
34 Ok(Self {
35 discipline,
36 total_length,
37 })
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Identification {
43 payload: Box<[u8]>,
44}
45
46impl Identification {
47 pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
48 let size = slice.len();
49 if size < 16 {
50 Err(BuildError::SectionSizeTooSmall(size))
51 } else {
52 Ok(Self { payload: slice })
53 }
54 }
55
56 pub fn iter(&self) -> Iter<'_, u8> {
57 self.payload.iter()
58 }
59
60 #[inline]
63 pub fn centre_id(&self) -> u16 {
64 let payload = &self.payload;
65 read_as!(u16, payload, 0)
66 }
67
68 #[inline]
71 pub fn subcentre_id(&self) -> u16 {
72 let payload = &self.payload;
73 read_as!(u16, payload, 2)
74 }
75
76 #[inline]
78 pub fn master_table_version(&self) -> u8 {
79 self.payload[4]
80 }
81
82 #[inline]
84 pub fn local_table_version(&self) -> u8 {
85 self.payload[5]
86 }
87
88 #[inline]
90 pub fn ref_time_significance(&self) -> u8 {
91 self.payload[6]
92 }
93
94 pub fn ref_time(&self) -> Result<DateTime<Utc>, GribError> {
96 let payload = &self.payload;
97 create_date_time(
98 read_as!(u16, payload, 7).into(),
99 self.payload[9].into(),
100 self.payload[10].into(),
101 self.payload[11].into(),
102 self.payload[12].into(),
103 self.payload[13].into(),
104 )
105 }
106
107 #[inline]
110 pub fn prod_status(&self) -> u8 {
111 self.payload[14]
112 }
113
114 #[inline]
116 pub fn data_type(&self) -> u8 {
117 self.payload[15]
118 }
119}
120
121#[inline]
122fn create_date_time(
123 year: i32,
124 month: u32,
125 day: u32,
126 hour: u32,
127 minute: u32,
128 second: u32,
129) -> Result<DateTime<Utc>, GribError> {
130 let result = Utc.with_ymd_and_hms(year, month, day, hour, minute, second);
131 if let LocalResult::None = result {
132 Err(GribError::InvalidValueError(format!(
133 "invalid date time: {year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}"
134 )))
135 } else {
136 Ok(result.unwrap())
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct LocalUse {
142 payload: Box<[u8]>,
143}
144
145impl LocalUse {
146 pub fn from_payload(slice: Box<[u8]>) -> Self {
147 Self { payload: slice }
148 }
149
150 pub fn iter(&self) -> Iter<'_, u8> {
151 self.payload.iter()
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Hash)]
156pub struct GridDefinition {
157 payload: Box<[u8]>,
158}
159
160impl GridDefinition {
161 pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
162 let size = slice.len();
163 if size < 9 {
164 Err(BuildError::SectionSizeTooSmall(size))
165 } else {
166 Ok(Self { payload: slice })
167 }
168 }
169
170 pub fn iter(&self) -> Iter<'_, u8> {
171 self.payload.iter()
172 }
173
174 pub fn num_points(&self) -> u32 {
176 let payload = &self.payload;
177 read_as!(u32, payload, 1)
178 }
179
180 pub fn grid_tmpl_num(&self) -> u16 {
182 let payload = &self.payload;
183 read_as!(u16, payload, 7)
184 }
185}
186
187#[derive(Debug, PartialEq, Eq)]
188pub enum GridDefinitionTemplateValues {
189 Template0(LatLonGridDefinition),
190 Template20(PolarStereographicGridDefinition),
191 Template30(LambertGridDefinition),
192 Template40(GaussianGridDefinition),
193}
194
195impl GridDefinitionTemplateValues {
196 pub fn grid_shape(&self) -> (usize, usize) {
199 match self {
200 Self::Template0(def) => def.grid_shape(),
201 Self::Template20(def) => def.grid_shape(),
202 Self::Template30(def) => def.grid_shape(),
203 Self::Template40(def) => def.grid_shape(),
204 }
205 }
206
207 pub fn short_name(&self) -> &'static str {
216 match self {
217 Self::Template0(def) => def.short_name(),
218 Self::Template20(def) => def.short_name(),
219 Self::Template30(def) => def.short_name(),
220 Self::Template40(def) => def.short_name(),
221 }
222 }
223
224 pub fn ij(&self) -> Result<GridPointIndexIterator, GribError> {
230 match self {
231 Self::Template0(def) => def.ij(),
232 Self::Template20(def) => def.ij(),
233 Self::Template30(def) => def.ij(),
234 Self::Template40(def) => def.ij(),
235 }
236 }
237
238 pub fn latlons(&self) -> Result<GridPointIterator, GribError> {
245 let iter = match self {
246 Self::Template0(def) => GridPointIterator::LatLon(def.latlons()?),
247 #[cfg(feature = "gridpoints-proj")]
248 Self::Template20(def) => GridPointIterator::Lambert(def.latlons()?),
249 #[cfg(feature = "gridpoints-proj")]
250 Self::Template30(def) => GridPointIterator::Lambert(def.latlons()?),
251 Self::Template40(def) => GridPointIterator::LatLon(def.latlons()?),
252 #[cfg(not(feature = "gridpoints-proj"))]
253 _ => {
254 return Err(GribError::NotSupported(
255 "lat/lon computation support for the template is dropped in this build"
256 .to_owned(),
257 ))
258 }
259 };
260 Ok(iter)
261 }
262}
263
264impl TryFrom<&GridDefinition> for GridDefinitionTemplateValues {
265 type Error = GribError;
266
267 fn try_from(value: &GridDefinition) -> Result<Self, Self::Error> {
268 let num = value.grid_tmpl_num();
269 match num {
270 0 => {
271 let buf = &value.payload;
272 if buf.len() > 67 {
273 return Err(GribError::NotSupported(format!(
274 "template {num} with list of number of points"
275 )));
276 }
277 Ok(GridDefinitionTemplateValues::Template0(
278 LatLonGridDefinition::from_buf(&buf[25..]),
279 ))
280 }
281 20 => {
282 let buf = &value.payload;
283 Ok(GridDefinitionTemplateValues::Template20(
284 PolarStereographicGridDefinition::from_buf(&buf[9..]),
285 ))
286 }
287 30 => {
288 let buf = &value.payload;
289 Ok(GridDefinitionTemplateValues::Template30(
290 LambertGridDefinition::from_buf(&buf[9..]),
291 ))
292 }
293 40 => {
294 let buf = &value.payload;
295 if buf.len() > 67 {
296 return Err(GribError::NotSupported(format!(
297 "template {num} with list of number of points"
298 )));
299 }
300 Ok(GridDefinitionTemplateValues::Template40(
301 GaussianGridDefinition::from_buf(&buf[25..]),
302 ))
303 }
304 _ => Err(GribError::NotSupported(format!("template {num}"))),
305 }
306 }
307}
308
309const START_OF_PROD_TEMPLATE: usize = 4;
310
311#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub struct ProdDefinition {
313 payload: Box<[u8]>,
314}
315
316impl ProdDefinition {
317 pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
318 let size = slice.len();
319 if size < START_OF_PROD_TEMPLATE {
320 Err(BuildError::SectionSizeTooSmall(size))
321 } else {
322 Ok(Self { payload: slice })
323 }
324 }
325
326 pub fn iter(&self) -> Iter<'_, u8> {
327 self.payload.iter()
328 }
329
330 pub fn num_coordinates(&self) -> u16 {
332 let payload = &self.payload;
333 read_as!(u16, payload, 0)
334 }
335
336 pub fn prod_tmpl_num(&self) -> u16 {
338 let payload = &self.payload;
339 read_as!(u16, payload, 2)
340 }
341
342 pub(crate) fn template_supported(&self) -> bool {
347 SUPPORTED_PROD_DEF_TEMPLATE_NUMBERS.contains(&self.prod_tmpl_num())
348 }
349
350 pub fn parameter_category(&self) -> Option<u8> {
353 if self.template_supported() {
354 self.payload.get(START_OF_PROD_TEMPLATE).copied()
355 } else {
356 None
357 }
358 }
359
360 pub fn parameter_number(&self) -> Option<u8> {
363 if self.template_supported() {
364 self.payload.get(START_OF_PROD_TEMPLATE + 1).copied()
365 } else {
366 None
367 }
368 }
369
370 pub fn generating_process(&self) -> Option<u8> {
373 if self.template_supported() {
374 let index = match self.prod_tmpl_num() {
375 0..=39 => Some(2),
376 40..=43 => Some(4),
377 44..=46 => Some(15),
378 47 => Some(2),
379 48..=49 => Some(26),
380 51 => Some(2),
381 55..=56 => Some(8),
383 59 => Some(8),
385 60..=61 => Some(2),
386 62..=63 => Some(8),
387 70..=73 => Some(7),
389 76..=79 => Some(5),
390 80..=81 => Some(27),
391 82 => Some(16),
392 83 => Some(2),
393 84 => Some(16),
394 85 => Some(15),
395 86..=91 => Some(2),
396 254 => Some(2),
397 1000..=1101 => Some(2),
398 _ => None,
399 }?;
400 self.payload.get(START_OF_PROD_TEMPLATE + index).copied()
401 } else {
402 None
403 }
404 }
405
406 pub fn forecast_time(&self) -> Option<ForecastTime> {
410 if self.template_supported() {
411 let unit_index = match self.prod_tmpl_num() {
412 0..=15 => Some(8),
413 32..=34 => Some(8),
414 40..=43 => Some(10),
415 44..=47 => Some(21),
416 48..=49 => Some(32),
417 51 => Some(8),
418 55..=56 => Some(14),
420 59 => Some(14),
422 60..=61 => Some(8),
423 62..=63 => Some(14),
424 70..=73 => Some(13),
426 76..=79 => Some(11),
427 80..=81 => Some(33),
428 82..=84 => Some(22),
429 85 => Some(21),
430 86..=87 => Some(8),
431 88 => Some(26),
432 91 => Some(8),
433 1000..=1101 => Some(8),
434 _ => None,
435 }?;
436 let unit_index = START_OF_PROD_TEMPLATE + unit_index;
437 let unit = self.payload.get(unit_index).copied();
438 let start = unit_index + 1;
439 let end = unit_index + 5;
440 let time = u32::from_be_bytes(self.payload[start..end].try_into().unwrap());
441 unit.map(|v| ForecastTime::from_numbers(v, time))
442 } else {
443 None
444 }
445 }
446
447 pub fn fixed_surfaces(&self) -> Option<(FixedSurface, FixedSurface)> {
449 if self.template_supported() {
450 let index = match self.prod_tmpl_num() {
451 0..=15 => Some(13),
452 40..=43 => Some(15),
453 44 => Some(24),
454 45..=47 => Some(26),
455 48..=49 => Some(37),
456 51 => Some(13),
457 55..=56 => Some(19),
459 59 => Some(19),
461 60..=61 => Some(13),
462 62..=63 => Some(19),
463 70..=73 => Some(18),
465 76..=79 => Some(16),
466 80..=81 => Some(38),
467 82..=84 => Some(27),
468 85 => Some(26),
469 86..=87 => Some(13),
470 88 => Some(5),
471 91 => Some(13),
472 1100..=1101 => Some(13),
473 _ => None,
474 }?;
475
476 let first_surface = self.read_surface_from(index);
477 let second_surface = self.read_surface_from(index + 6);
478 first_surface.zip(second_surface)
479 } else {
480 None
481 }
482 }
483
484 fn read_surface_from(&self, index: usize) -> Option<FixedSurface> {
485 let index = START_OF_PROD_TEMPLATE + index;
486 let surface_type = self.payload.get(index).copied();
487 let scale_factor = self.payload.get(index + 1).map(|v| (*v).as_grib_int());
488 let start = index + 2;
489 let end = index + 6;
490 let scaled_value =
491 u32::from_be_bytes(self.payload[start..end].try_into().unwrap()).as_grib_int();
492 surface_type
493 .zip(scale_factor)
494 .map(|(stype, factor)| FixedSurface::new(stype, factor, scaled_value))
495 }
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Hash)]
499pub struct ReprDefinition {
500 payload: Box<[u8]>,
501}
502
503impl ReprDefinition {
504 pub fn from_payload(slice: Box<[u8]>) -> Result<Self, BuildError> {
505 let size = slice.len();
506 if size < 6 {
507 Err(BuildError::SectionSizeTooSmall(size))
508 } else {
509 Ok(Self { payload: slice })
510 }
511 }
512
513 pub fn iter(&self) -> Iter<'_, u8> {
514 self.payload.iter()
515 }
516
517 pub fn num_points(&self) -> u32 {
521 let payload = &self.payload;
522 read_as!(u32, payload, 0)
523 }
524
525 pub fn repr_tmpl_num(&self) -> u16 {
527 let payload = &self.payload;
528 read_as!(u16, payload, 4)
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash)]
533pub struct BitMap {
534 pub bitmap_indicator: u8,
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541
542 macro_rules! test_date_time_creation {
543 ($((
544 $name:ident,
545 $year:expr,
546 $month:expr,
547 $day:expr,
548 $hour:expr,
549 $minute:expr,
550 $second:expr,
551 $ok_expected:expr
552 ),)*) => ($(
553 #[test]
554 fn $name() {
555 let result = create_date_time($year, $month, $day, $hour, $minute, $second);
556 assert_eq!(result.is_ok(), $ok_expected);
557 }
558 )*);
559 }
560
561 test_date_time_creation! {
562 (date_time_creation_for_valid_date_time, 2022, 1, 1, 0, 0, 0, true),
563 (date_time_creation_for_invalid_date, 2022, 11, 31, 0, 0, 0, false),
564 (date_time_creation_for_invalid_time, 2022, 1, 1, 0, 61, 0, false),
565 }
566
567 #[test]
568 fn error_in_date_time_creation() {
569 let result = create_date_time(2022, 11, 31, 0, 0, 0);
570 assert_eq!(
571 result,
572 Err(GribError::InvalidValueError(
573 "invalid date time: 2022-11-31 00:00:00".to_owned()
574 ))
575 );
576 }
577
578 #[test]
579 fn grid_definition_template_0() {
580 let data = GridDefinition::from_payload(
584 vec![
585 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff,
586 0xff, 0x01, 0x03, 0xcd, 0x39, 0xfa, 0x01, 0x03, 0xc9, 0xf6, 0xa3, 0x00, 0x00, 0x01,
587 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x02,
588 0xdb, 0xc9, 0x3d, 0x07, 0x09, 0x7d, 0xa4, 0x30, 0x01, 0x31, 0xcf, 0xc3, 0x08, 0xef,
589 0xdd, 0x5c, 0x00, 0x01, 0xe8, 0x48, 0x00, 0x01, 0x45, 0x85, 0x00,
590 ]
591 .into_boxed_slice(),
592 )
593 .unwrap();
594
595 let actual = GridDefinitionTemplateValues::try_from(&data).unwrap();
596 let expected = GridDefinitionTemplateValues::Template0(LatLonGridDefinition {
597 ni: 256,
598 nj: 336,
599 first_point_lat: 47958333,
600 first_point_lon: 118062500,
601 last_point_lat: 20041667,
602 last_point_lon: 149937500,
603 scanning_mode: crate::grid::ScanningMode(0b00000000),
604 });
605 assert_eq!(actual, expected);
606 }
607
608 #[test]
609 fn prod_definition_parameters() {
610 let data = ProdDefinition::from_payload(
611 vec![
612 0, 0, 0, 0, 193, 0, 2, 153, 255, 0, 0, 0, 0, 0, 0, 0, 40, 1, 255, 255, 255, 255,
613 255, 255, 255, 255, 255, 255, 255,
614 ]
615 .into_boxed_slice(),
616 )
617 .unwrap();
618
619 assert_eq!(data.parameter_category(), Some(193));
620 assert_eq!(data.parameter_number(), Some(0));
621 assert_eq!(
622 data.forecast_time(),
623 Some(ForecastTime::from_numbers(0, 40))
624 );
625 assert_eq!(
626 data.fixed_surfaces(),
627 Some((
628 FixedSurface::new(1, -127, -2147483647),
629 FixedSurface::new(255, -127, -2147483647)
630 ))
631 );
632 }
633}