@@ -63,7 +63,7 @@ pub struct Grib1FieldBuilder {
6363 grid : Option < GridDefinition > ,
6464 packing : Option < PackingStrategy > ,
6565 values : Option < Vec < f64 > > ,
66- bitmap : Option < Vec < bool > > ,
66+ bitmap : Option < Grib1BitmapDefinition > ,
6767 value_order : ValueOrder ,
6868}
6969
@@ -96,7 +96,22 @@ impl Grib1FieldBuilder {
9696 }
9797
9898 pub fn bitmap ( mut self , bitmap : & [ bool ] ) -> Self {
99- self . bitmap = Some ( bitmap. to_vec ( ) ) ;
99+ self . bitmap = Some ( Grib1BitmapDefinition {
100+ present : bitmap. to_vec ( ) ,
101+ table_reference : None ,
102+ } ) ;
103+ self
104+ }
105+
106+ /// Use a GRIB1 predefined bitmap table reference.
107+ ///
108+ /// The bitmap is used to pack only present values. The written message
109+ /// contains the nonzero table reference, not the bitmap payload itself.
110+ pub fn predefined_bitmap ( mut self , table_reference : u16 , bitmap : & [ bool ] ) -> Self {
111+ self . bitmap = Some ( Grib1BitmapDefinition {
112+ present : bitmap. to_vec ( ) ,
113+ table_reference : Some ( table_reference) ,
114+ } ) ;
100115 self
101116 }
102117
@@ -130,22 +145,34 @@ impl Grib1FieldBuilder {
130145 } ) ;
131146 }
132147 if let Some ( bitmap) = & bitmap {
133- if bitmap. len ( ) != expected {
148+ if bitmap. present . len ( ) != expected {
134149 return Err ( Error :: DataLengthMismatch {
135150 expected,
136- actual : bitmap. len ( ) ,
151+ actual : bitmap. present . len ( ) ,
137152 } ) ;
138153 }
154+ if bitmap. table_reference == Some ( 0 ) {
155+ return Err ( Error :: Other (
156+ "GRIB1 predefined bitmap table reference must be nonzero" . into ( ) ,
157+ ) ) ;
158+ }
139159 }
140160
141161 if self . value_order == ValueOrder :: LogicalRowMajor {
142- reorder_field_to_grib_scan_order ( & grid, & mut values, bitmap. as_deref_mut ( ) ) ?;
162+ reorder_field_to_grib_scan_order (
163+ & grid,
164+ & mut values,
165+ bitmap. as_mut ( ) . map ( |bitmap| bitmap. present . as_mut_slice ( ) ) ,
166+ ) ?;
143167 }
144168
145- let packed = match packing {
169+ let bitmap_mask = bitmap. as_ref ( ) . map ( |bitmap| bitmap. present . as_slice ( ) ) ;
170+ let predefined_bitmap_reference = bitmap. as_ref ( ) . and_then ( |bitmap| bitmap. table_reference ) ;
171+
172+ let mut packed = match packing {
146173 PackingStrategy :: SimpleAuto { decimal_scale } => {
147174 product. decimal_scale = decimal_scale;
148- pack_simple_auto ( & values, bitmap . as_deref ( ) , decimal_scale) ?
175+ pack_simple_auto ( & values, bitmap_mask , decimal_scale) ?
149176 }
150177 PackingStrategy :: ComplexAuto { .. } => {
151178 return Err ( Error :: Other (
@@ -163,23 +190,35 @@ impl Grib1FieldBuilder {
163190 ) ) ;
164191 }
165192 } ;
193+ if predefined_bitmap_reference. is_some ( ) {
194+ packed. bitmap_payload = None ;
195+ }
166196 product. has_grid_definition = true ;
167- product. has_bitmap = packed. bitmap_payload . is_some ( ) ;
197+ product. has_bitmap =
198+ packed. bitmap_payload . is_some ( ) || predefined_bitmap_reference. is_some ( ) ;
168199
169200 Ok ( Grib1Field {
170201 product,
171202 grid,
172203 packed,
204+ predefined_bitmap_reference,
173205 } )
174206 }
175207}
176208
209+ #[ derive( Debug , Clone ) ]
210+ struct Grib1BitmapDefinition {
211+ present : Vec < bool > ,
212+ table_reference : Option < u16 > ,
213+ }
214+
177215/// A validated, packed GRIB1 field ready for message serialization.
178216#[ derive( Debug , Clone ) ]
179217pub struct Grib1Field {
180218 product : Grib1ProductDefinition ,
181219 grid : GridDefinition ,
182220 packed : PackedField ,
221+ predefined_bitmap_reference : Option < u16 > ,
183222}
184223
185224impl Grib1Field {
@@ -375,7 +414,9 @@ impl<'a, W: Write> GribWriter<'a, W> {
375414 let mut body = Vec :: new ( ) ;
376415 write_grib1_product_section ( & mut body, & field. product ) ?;
377416 write_grib1_grid_section ( & mut body, & field. grid ) ?;
378- if let Some ( bitmap) = & field. packed . bitmap_payload {
417+ if let Some ( table_reference) = field. predefined_bitmap_reference {
418+ write_grib1_predefined_bitmap_section ( & mut body, table_reference) ?;
419+ } else if let Some ( bitmap) = & field. packed . bitmap_payload {
379420 write_grib1_bitmap_section ( & mut body, bitmap, field. grid . num_points ( ) ) ?;
380421 }
381422 write_grib1_data_section ( & mut body, & field. packed , 0 ) ?;
@@ -1553,6 +1594,18 @@ fn write_grib1_bitmap_section(
15531594 Ok ( ( ) )
15541595}
15551596
1597+ fn write_grib1_predefined_bitmap_section ( out : & mut Vec < u8 > , table_reference : u16 ) -> Result < ( ) > {
1598+ if table_reference == 0 {
1599+ return Err ( Error :: Other (
1600+ "GRIB1 predefined bitmap table reference must be nonzero" . into ( ) ,
1601+ ) ) ;
1602+ }
1603+ write_u24_be ( out, 6 ) ?;
1604+ write_u8_be ( out, 0 ) ?;
1605+ write_u16_be ( out, table_reference) ?;
1606+ Ok ( ( ) )
1607+ }
1608+
15561609fn write_grib1_data_section ( out : & mut Vec < u8 > , packed : & PackedField , flags : u8 ) -> Result < ( ) > {
15571610 validate_grib1_binary_data_flags ( flags) ?;
15581611 let DataRepresentation :: SimplePacking ( params) = & packed. representation else {
@@ -2269,7 +2322,7 @@ mod tests {
22692322 PolarStereographicGrid , ProductDefinition , ProductDefinitionTemplate ,
22702323 } ;
22712324 use grib_reader:: sections:: scan_sections;
2272- use grib_reader:: GribFile ;
2325+ use grib_reader:: { GribFile , OpenOptions , PredefinedBitmap } ;
22732326 use serde:: Deserialize ;
22742327
22752328 fn identification ( ) -> Identification {
@@ -2589,6 +2642,70 @@ mod tests {
25892642 assert_eq ! ( decoded[ 3 ] , 8.0 ) ;
25902643 }
25912644
2645+ #[ test]
2646+ fn writes_grib1_predefined_bitmap_reference ( ) {
2647+ let bitmap = [ true , false , true , false ] ;
2648+ let field = Grib1FieldBuilder :: new ( )
2649+ . product ( grib1_product ( ) )
2650+ . grid ( grid ( ) )
2651+ . packing ( PackingStrategy :: SimpleAuto { decimal_scale : 0 } )
2652+ . values ( & [ 9.0 , 999.0 , 7.0 , 999.0 ] )
2653+ . predefined_bitmap ( 300 , & bitmap)
2654+ . build ( )
2655+ . unwrap ( ) ;
2656+ let bytes = write_grib1_message ( field) ;
2657+
2658+ let bitmap_offset = 8 + 28 + 32 ;
2659+ assert_eq ! (
2660+ & bytes[ bitmap_offset..bitmap_offset + 6 ] ,
2661+ & [ 0 , 0 , 6 , 0 , 1 , 44 ]
2662+ ) ;
2663+
2664+ let err = match GribFile :: from_bytes ( bytes. clone ( ) ) {
2665+ Ok ( _) => panic ! ( "expected unsupported predefined bitmap" ) ,
2666+ Err ( err) => err,
2667+ } ;
2668+ assert ! ( matches!(
2669+ err,
2670+ grib_core:: Error :: UnsupportedBitmapIndicator ( 300 )
2671+ ) ) ;
2672+
2673+ let bitmap_payload = [ 0b1010_0000 ] ;
2674+ let predefined = [ PredefinedBitmap {
2675+ center_id : 7 ,
2676+ subcenter_id : Some ( 0 ) ,
2677+ table_reference : 300 ,
2678+ bitmap : & bitmap_payload,
2679+ } ] ;
2680+ let file = GribFile :: from_bytes_with_grib1_predefined_bitmaps (
2681+ bytes,
2682+ OpenOptions :: default ( ) ,
2683+ & predefined,
2684+ )
2685+ . unwrap ( ) ;
2686+ let decoded = file. message ( 0 ) . unwrap ( ) . read_flat_data_as_f64 ( ) . unwrap ( ) ;
2687+ assert_eq ! ( decoded[ 0 ] , 9.0 ) ;
2688+ assert ! ( decoded[ 1 ] . is_nan( ) ) ;
2689+ assert_eq ! ( decoded[ 2 ] , 7.0 ) ;
2690+ assert ! ( decoded[ 3 ] . is_nan( ) ) ;
2691+ }
2692+
2693+ #[ test]
2694+ fn rejects_zero_grib1_predefined_bitmap_reference ( ) {
2695+ let err = Grib1FieldBuilder :: new ( )
2696+ . product ( grib1_product ( ) )
2697+ . grid ( grid ( ) )
2698+ . packing ( PackingStrategy :: SimpleAuto { decimal_scale : 0 } )
2699+ . values ( & [ 1.0 , 999.0 , 3.0 , 999.0 ] )
2700+ . predefined_bitmap ( 0 , & [ true , false , true , false ] )
2701+ . build ( )
2702+ . unwrap_err ( ) ;
2703+
2704+ assert ! (
2705+ matches!( err, grib_core:: Error :: Other ( message) if message. contains( "must be nonzero" ) )
2706+ ) ;
2707+ }
2708+
25922709 #[ test]
25932710 fn writes_grib1_ibm_float_reference_value ( ) {
25942711 let bytes = write_grib1_message ( grib1_simple_field ( & [ 10.0 , 11.0 , 12.0 , 13.0 ] ) ) ;
0 commit comments