@@ -70,14 +70,40 @@ impl Identification {
7070#[ derive( Debug , Clone , PartialEq ) ]
7171pub struct FixedSurface {
7272 pub surface_type : u8 ,
73+ /// The numeric level, or `None` when either WMO numeric component is
74+ /// encoded with its missing-value sentinel.
75+ pub value : Option < ScaledValue > ,
76+ }
77+
78+ /// The scale/value pair used to encode a fixed-surface numeric level.
79+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
80+ pub struct ScaledValue {
7381 pub scale_factor : i16 ,
7482 pub scaled_value : i32 ,
7583}
7684
7785impl FixedSurface {
78- pub fn scaled_value_f64 ( & self ) -> f64 {
79- let factor = 10.0_f64 . powi ( -( self . scale_factor as i32 ) ) ;
80- self . scaled_value as f64 * factor
86+ pub const fn with_value ( surface_type : u8 , scale_factor : i16 , scaled_value : i32 ) -> Self {
87+ Self {
88+ surface_type,
89+ value : Some ( ScaledValue {
90+ scale_factor,
91+ scaled_value,
92+ } ) ,
93+ }
94+ }
95+
96+ pub const fn without_value ( surface_type : u8 ) -> Self {
97+ Self {
98+ surface_type,
99+ value : None ,
100+ }
101+ }
102+
103+ pub fn scaled_value_f64 ( & self ) -> Option < f64 > {
104+ let value = self . value ?;
105+ let factor = 10.0_f64 . powi ( -i32:: from ( value. scale_factor ) ) ;
106+ Some ( f64:: from ( value. scaled_value ) * factor)
81107 }
82108}
83109
@@ -91,11 +117,19 @@ pub struct ProductDefinition {
91117
92118/// Typed GRIB2 Product Definition templates.
93119#[ derive( Debug , Clone , PartialEq ) ]
120+ #[ non_exhaustive]
94121pub enum ProductDefinitionTemplate {
95122 AnalysisOrForecast ( AnalysisOrForecastTemplate ) ,
96123 IndividualEnsembleForecast ( IndividualEnsembleForecastTemplate ) ,
97124 StatisticalProcess ( StatisticalProcessTemplate ) ,
98125 EnsembleStatisticalProcess ( EnsembleStatisticalProcessTemplate ) ,
126+ /// A well-framed Section 4 whose template is not interpreted by this
127+ /// version of the library. `raw` contains the template-specific bytes
128+ /// following the common parameter category and number.
129+ Unsupported {
130+ number : u16 ,
131+ raw : Vec < u8 > ,
132+ } ,
99133}
100134
101135/// Product Definition Template 4.0: analysis or forecast at a horizontal level.
@@ -186,23 +220,27 @@ impl ProductDefinition {
186220 }
187221
188222 pub fn generating_process ( & self ) -> Option < u8 > {
189- Some ( self . template . base ( ) . generating_process )
223+ self . template . base ( ) . map ( |base| base . generating_process )
190224 }
191225
192226 pub fn forecast_time_unit ( & self ) -> Option < u8 > {
193- Some ( self . template . base ( ) . forecast_time_unit )
227+ self . template . base ( ) . map ( |base| base . forecast_time_unit )
194228 }
195229
196230 pub fn forecast_time ( & self ) -> Option < u32 > {
197- Some ( self . template . base ( ) . forecast_time )
231+ self . template . base ( ) . map ( |base| base . forecast_time )
198232 }
199233
200234 pub fn first_surface ( & self ) -> Option < & FixedSurface > {
201- self . template . base ( ) . first_surface . as_ref ( )
235+ self . template
236+ . base ( )
237+ . and_then ( |base| base. first_surface . as_ref ( ) )
202238 }
203239
204240 pub fn second_surface ( & self ) -> Option < & FixedSurface > {
205- self . template . base ( ) . second_surface . as_ref ( )
241+ self . template
242+ . base ( )
243+ . and_then ( |base| base. second_surface . as_ref ( ) )
206244 }
207245
208246 pub fn end_of_overall_time_interval ( & self ) -> Option < ReferenceTime > {
@@ -225,7 +263,17 @@ impl ProductDefinitionTemplate {
225263 11 => Ok ( Self :: EnsembleStatisticalProcess (
226264 EnsembleStatisticalProcessTemplate :: parse ( section_bytes) ?,
227265 ) ) ,
228- other => Err ( Error :: UnsupportedProductTemplate ( other) ) ,
266+ number => {
267+ let raw_len = section_bytes. len ( ) - 11 ;
268+ let mut raw = Vec :: new ( ) ;
269+ raw. try_reserve ( raw_len) . map_err ( |err| {
270+ Error :: Other ( format ! (
271+ "failed to reserve {raw_len} unsupported product-template bytes: {err}"
272+ ) )
273+ } ) ?;
274+ raw. extend_from_slice ( & section_bytes[ 11 ..] ) ;
275+ Ok ( Self :: Unsupported { number, raw } )
276+ }
229277 }
230278 }
231279
@@ -235,16 +283,18 @@ impl ProductDefinitionTemplate {
235283 Self :: IndividualEnsembleForecast ( _) => 1 ,
236284 Self :: StatisticalProcess ( _) => 8 ,
237285 Self :: EnsembleStatisticalProcess ( _) => 11 ,
286+ Self :: Unsupported { number, .. } => * number,
238287 }
239288 }
240289
241- fn base ( & self ) -> & AnalysisOrForecastTemplate {
242- match self {
290+ fn base ( & self ) -> Option < & AnalysisOrForecastTemplate > {
291+ Some ( match self {
243292 Self :: AnalysisOrForecast ( template) => template,
244293 Self :: IndividualEnsembleForecast ( template) => & template. base ,
245294 Self :: StatisticalProcess ( template) => & template. base ,
246295 Self :: EnsembleStatisticalProcess ( template) => & template. ensemble . base ,
247- }
296+ Self :: Unsupported { .. } => return None ,
297+ } )
248298 }
249299
250300 fn end_of_overall_time_interval ( & self ) -> Option < ReferenceTime > {
@@ -254,6 +304,7 @@ impl ProductDefinitionTemplate {
254304 Some ( template. end_of_overall_time_interval )
255305 }
256306 Self :: AnalysisOrForecast ( _) | Self :: IndividualEnsembleForecast ( _) => None ,
307+ Self :: Unsupported { .. } => None ,
257308 }
258309 }
259310}
@@ -398,10 +449,18 @@ fn parse_surface(section_bytes: &[u8]) -> Option<FixedSurface> {
398449 return None ;
399450 }
400451
452+ let value = if section_bytes[ 1 ] == 0xff || section_bytes[ 2 ..6 ] == [ 0xff ; 4 ] {
453+ None
454+ } else {
455+ Some ( ScaledValue {
456+ scale_factor : grib_i8 ( section_bytes[ 1 ] ) ,
457+ scaled_value : grib_i32 ( & section_bytes[ 2 ..6 ] ) ?,
458+ } )
459+ } ;
460+
401461 Some ( FixedSurface {
402462 surface_type,
403- scale_factor : grib_i8 ( section_bytes[ 1 ] ) ,
404- scaled_value : grib_i32 ( & section_bytes[ 2 ..6 ] ) ?,
463+ value,
405464 } )
406465}
407466
@@ -447,7 +506,10 @@ mod tests {
447506 assert_eq ! ( product. parameter_number, 3 ) ;
448507 assert_eq ! ( product. template_number( ) , 0 ) ;
449508 assert_eq ! ( product. forecast_time( ) , Some ( 6 ) ) ;
450- assert_eq ! ( product. first_surface( ) . unwrap( ) . scaled_value_f64( ) , 850.0 ) ;
509+ assert_eq ! (
510+ product. first_surface( ) . unwrap( ) . scaled_value_f64( ) ,
511+ Some ( 850.0 )
512+ ) ;
451513 assert_eq ! (
452514 product. template,
453515 ProductDefinitionTemplate :: AnalysisOrForecast ( AnalysisOrForecastTemplate {
@@ -557,16 +619,38 @@ mod tests {
557619 }
558620
559621 #[ test]
560- fn rejects_unsupported_product_definition_templates ( ) {
622+ fn preserves_unsupported_product_definition_templates ( ) {
561623 let mut section = vec ! [ 0u8 ; 34 ] ;
562624 section[ ..4 ] . copy_from_slice ( & ( 34u32 ) . to_be_bytes ( ) ) ;
563625 section[ 4 ] = 4 ;
564626 section[ 7 ..9 ] . copy_from_slice ( & 99u16 . to_be_bytes ( ) ) ;
565627 section[ 9 ] = 2 ;
566628 section[ 10 ] = 3 ;
567629
568- let err = ProductDefinition :: parse ( & section) . unwrap_err ( ) ;
569- assert ! ( matches!( err, Error :: UnsupportedProductTemplate ( 99 ) ) ) ;
630+ section[ 11 ..] . copy_from_slice ( & [ 0x5a ; 23 ] ) ;
631+
632+ let product = ProductDefinition :: parse ( & section) . unwrap ( ) ;
633+ assert_eq ! ( product. template_number( ) , 99 ) ;
634+ assert_eq ! ( product. forecast_time( ) , None ) ;
635+ assert_eq ! ( product. first_surface( ) , None ) ;
636+ assert ! ( matches!(
637+ product. template,
638+ ProductDefinitionTemplate :: Unsupported { number: 99 , ref raw }
639+ if raw == & [ 0x5a ; 23 ]
640+ ) ) ;
641+ }
642+
643+ #[ test]
644+ fn preserves_surface_type_when_numeric_level_is_missing ( ) {
645+ let mut section = product_section_template_zero ( ) ;
646+ section[ 23 ] = 0xff ;
647+ section[ 24 ..28 ] . copy_from_slice ( & [ 0xff ; 4 ] ) ;
648+
649+ let product = ProductDefinition :: parse ( & section) . unwrap ( ) ;
650+ let surface = product. first_surface ( ) . unwrap ( ) ;
651+ assert_eq ! ( surface. surface_type, 103 ) ;
652+ assert_eq ! ( surface. value, None ) ;
653+ assert_eq ! ( surface. scaled_value_f64( ) , None ) ;
570654 }
571655
572656 #[ test]
0 commit comments