grib2io.utils
Collection of utility functions to assist in the encoding and decoding of GRIB2 Messages.
1""" 2Collection of utility functions to assist in the encoding and decoding 3of GRIB2 Messages. 4""" 5 6import datetime 7import struct 8from decimal import Decimal, localcontext 9from typing import Dict, List, Optional, Tuple, Type, Union 10 11import numpy as np 12from numpy.typing import ArrayLike 13 14from .. import tables 15from .. import templates 16 17 18def decimal_to_scaled_int( 19 value: Union[float, str, int], 20 scale_factor: Optional[int] = None, 21) -> Tuple[int, int]: 22 """ 23 Convert a float-like value to a scaled integer using the minimal decimal scaling factor. 24 25 The input value is internally converted to a `Decimal` to ensure precise scaling. 26 27 Parameters 28 ---------- 29 value : float, str, or int 30 The numeric value to scale. 31 scaled_value : int 32 The integer result of scaling the original value by `10**scale_factor`. 33 34 Returns 35 ------- 36 scale_factor : int 37 The smallest power of 10 such that `value * 10**scale_factor` is an exact integer. 38 scaled_value : int 39 The integer result of scaling the original value by `10**scale_factor`. 40 """ 41 dec_value = Decimal(str(value)) # Preserve exact decimal representation 42 43 with localcontext() as ctx: 44 ctx.prec = 28 45 46 if scale_factor is not None: 47 scaled = dec_value * (10 ** scale_factor) 48 if scaled != scaled.to_integral_value(): 49 raise ValueError( 50 f"Value {value} cannot be exactly scaled by 10^{scale_factor}" 51 ) 52 return scale_factor, int(scaled) 53 else: 54 scale_factor = 0 55 while dec_value != dec_value.to_integral_value(): 56 dec_value *= 10 57 scale_factor += 1 58 if scale_factor > 20: 59 raise ValueError( 60 f"Could not find exact scale factor for value {value} within bounds." 61 ) 62 return scale_factor, int(dec_value) 63 64 65def int2bin(i: int, nbits: int=8, output: Union[Type[str], Type[List]]=str): 66 """ 67 Convert integer to binary string or list 68 69 The struct module unpack using ">i" will unpack a 32-bit integer from a 70 binary string. 71 72 Parameters 73 ---------- 74 i 75 Integer value to convert to binary representation. 76 nbits : default=8 77 Number of bits to return. Valid values are 8 [DEFAULT], 16, 32, and 78 64. 79 output : default=str 80 Return data as `str` [DEFAULT] or `list` (list of ints). 81 82 Returns 83 ------- 84 int2bin 85 `str` or `list` (list of ints) of binary representation of the integer 86 value. 87 """ 88 i = int(i) if not isinstance(i,int) else i 89 assert nbits in [8,16,32,64] 90 bitstr = "{0:b}".format(i).zfill(nbits) 91 if output is str: 92 return bitstr 93 elif output is list: 94 return [int(b) for b in bitstr] 95 96 97def ieee_float_to_int(f): 98 """ 99 Convert an IEEE 754 32-bit float to a 32-bit integer. 100 101 Parameters 102 ---------- 103 f : float 104 Floating-point value. 105 106 Returns 107 ------- 108 ieee_float_to_int 109 `numpy.int32` representation of an IEEE 32-bit float. 110 """ 111 i = struct.unpack('>i',struct.pack('>f',np.float32(f)))[0] 112 return np.int32(i) 113 114 115def ieee_int_to_float(i): 116 """ 117 Convert a 32-bit integer to an IEEE 32-bit float. 118 119 Parameters 120 ---------- 121 i : int 122 Integer value. 123 124 Returns 125 ------- 126 ieee_int_to_float 127 `numpy.float32` representation of a 32-bit int. 128 """ 129 f = struct.unpack('>f',struct.pack('>i',np.int32(i)))[0] 130 return np.float32(f) 131 132 133def get_leadtime(pdtn: int, pdt: ArrayLike) -> datetime.timedelta: 134 """ 135 Compute lead time as a datetime.timedelta object. 136 137 Using information from GRIB2 Product Definition Template 138 Number, and Product Definition Template (Section 4). 139 140 Parameters 141 ---------- 142 pdtn 143 GRIB2 Product Definition Template Number 144 pdt 145 Sequence containing GRIB2 Product Definition Template (Section 4). 146 147 Returns 148 ------- 149 leadTime 150 datetime.timedelta object representing the lead time of the GRIB2 message. 151 """ 152 lt = tables.get_value_from_table(pdt[templates.UnitOfForecastTime._key[pdtn]], 'scale_time_seconds') 153 lt *= pdt[templates.ValueOfForecastTime._key[pdtn]] 154 return datetime.timedelta(seconds=int(lt)) 155 156 157def get_duration(pdtn: int, pdt: ArrayLike) -> datetime.timedelta: 158 """ 159 Compute a time duration as a datetime.timedelta. 160 161 Uses information from Product Definition Template Number, and Product 162 Definition Template (Section 4). 163 164 Parameters 165 ---------- 166 pdtn 167 GRIB2 Product Definition Template Number 168 pdt 169 Sequence containing GRIB2 Product Definition Template (Section 4). 170 171 Returns 172 ------- 173 get_duration 174 datetime.timedelta object representing the time duration of the GRIB2 175 message. 176 """ 177 if pdtn in templates._timeinterval_pdtns: 178 ntime = pdt[templates.NumberOfTimeRanges._key[pdtn]] 179 duration_unit = tables.get_value_from_table( 180 pdt[templates.UnitOfTimeRangeOfStatisticalProcess._key[pdtn]], 181 'scale_time_seconds') 182 d = ntime * duration_unit * pdt[ 183 templates.TimeRangeOfStatisticalProcess._key[pdtn]] 184 else: 185 d = 0 186 return datetime.timedelta(seconds=int(d)) 187 188 189def decode_wx_strings(lus: bytes) -> Dict[int, str]: 190 """ 191 Decode GRIB2 Local Use Section to obtain NDFD/MDL Weather Strings. 192 193 The decode procedure is defined 194 [here](https://vlab.noaa.gov/web/mdl/nbm-gmos-grib2-wx-info). 195 196 Parameters 197 ---------- 198 lus 199 GRIB2 Local Use Section containing NDFD weather strings. 200 201 Returns 202 ------- 203 decode_wx_strings 204 Dict of NDFD/MDL weather strings. Keys are an integer value that 205 represent the sequential order of the key in the packed local use 206 section and the value is the weather key. 207 """ 208 assert lus[0] == 1 209 # Unpack information related to the simple packing method 210 # the packed weather string data. 211 ngroups = struct.unpack('>H',lus[1:3])[0] 212 nvalues = struct.unpack('>i',lus[3:7])[0] 213 refvalue = struct.unpack('>i',lus[7:11])[0] 214 dsf = struct.unpack('>h',lus[11:13])[0] 215 nbits = lus[13] 216 datatype = lus[14] 217 if datatype == 0: # Floating point 218 refvalue = np.float32(ieee_int_to_float(refvalue)*10**-dsf) 219 elif datatype == 1: # Integer 220 refvalue = np.int32(ieee_int_to_float(refvalue)*10**-dsf) 221 # Upack each byte starting at byte 15 to end of the local use 222 # section, create a binary string and append to the full 223 # binary string. 224 b = '' 225 for i in range(15,len(lus)): 226 iword = struct.unpack('>B',lus[i:i+1])[0] 227 b += bin(iword).split('b')[1].zfill(8) 228 # Iterate over the binary string (b). For each nbits 229 # chunk, convert to an integer, including the refvalue, 230 # and then convert the int to an ASCII character, then 231 # concatenate to wxstring. 232 wxstring = '' 233 for i in range(0,len(b),nbits): 234 wxstring += chr(int(b[i:i+nbits],2)+refvalue) 235 # Return string as list, split by null character. 236 #return list(filter(None,wxstring.split('\0'))) 237 return {n:k for n,k in enumerate(list(filter(None,wxstring.split('\0'))))} 238 239 240def get_wgrib2_prob_string( 241 probtype: int, 242 sfacl: int, 243 svall: int, 244 sfacu: int, 245 svalu: int, 246) -> str: 247 """ 248 Return a wgrib2-styled string of probabilistic threshold information. 249 250 Logic from wgrib2 source, 251 [Prob.c](https://github.com/NOAA-EMC/NCEPLIBS-wgrib2/blob/develop/wgrib2/Prob.c), 252 is replicated here. 253 254 Parameters 255 ---------- 256 probtype 257 Type of probability (Code Table 4.9). 258 sfacl 259 Scale factor of lower limit. 260 svall 261 Scaled value of lower limit. 262 sfacu 263 Scale factor of upper limit. 264 svalu 265 Scaled value of upper limit. 266 267 Returns 268 ------- 269 get_wgrib2_prob_string 270 wgrib2-formatted string of probability threshold. 271 """ 272 probstr = '' 273 if sfacl == -127: sfacl = 0 274 if sfacu == -127: sfacu = 0 275 lower = svall/(10**sfacl) 276 upper = svalu/(10**sfacu) 277 if probtype == 0: 278 probstr = 'prob <%g' % (lower) 279 elif probtype == 1: 280 probstr = 'prob >%g' % (upper) 281 elif probtype == 2: 282 if lower == upper: 283 probstr = 'prob =%g' % (lower) 284 else: 285 probstr = 'prob >=%g <%g' % (lower,upper) 286 elif probtype == 3: 287 probstr = 'prob >%g' % (lower) 288 elif probtype == 4: 289 probstr = 'prob <%g' % (upper) 290 else: 291 probstr = '' 292 return probstr
19def decimal_to_scaled_int( 20 value: Union[float, str, int], 21 scale_factor: Optional[int] = None, 22) -> Tuple[int, int]: 23 """ 24 Convert a float-like value to a scaled integer using the minimal decimal scaling factor. 25 26 The input value is internally converted to a `Decimal` to ensure precise scaling. 27 28 Parameters 29 ---------- 30 value : float, str, or int 31 The numeric value to scale. 32 scaled_value : int 33 The integer result of scaling the original value by `10**scale_factor`. 34 35 Returns 36 ------- 37 scale_factor : int 38 The smallest power of 10 such that `value * 10**scale_factor` is an exact integer. 39 scaled_value : int 40 The integer result of scaling the original value by `10**scale_factor`. 41 """ 42 dec_value = Decimal(str(value)) # Preserve exact decimal representation 43 44 with localcontext() as ctx: 45 ctx.prec = 28 46 47 if scale_factor is not None: 48 scaled = dec_value * (10 ** scale_factor) 49 if scaled != scaled.to_integral_value(): 50 raise ValueError( 51 f"Value {value} cannot be exactly scaled by 10^{scale_factor}" 52 ) 53 return scale_factor, int(scaled) 54 else: 55 scale_factor = 0 56 while dec_value != dec_value.to_integral_value(): 57 dec_value *= 10 58 scale_factor += 1 59 if scale_factor > 20: 60 raise ValueError( 61 f"Could not find exact scale factor for value {value} within bounds." 62 ) 63 return scale_factor, int(dec_value)
Convert a float-like value to a scaled integer using the minimal decimal scaling factor.
The input value is internally converted to a Decimal to ensure precise scaling.
Parameters
- value (float, str, or int): The numeric value to scale.
- scaled_value (int):
The integer result of scaling the original value by
10**scale_factor.
Returns
- scale_factor (int):
The smallest power of 10 such that
value * 10**scale_factoris an exact integer. - scaled_value (int):
The integer result of scaling the original value by
10**scale_factor.
66def int2bin(i: int, nbits: int=8, output: Union[Type[str], Type[List]]=str): 67 """ 68 Convert integer to binary string or list 69 70 The struct module unpack using ">i" will unpack a 32-bit integer from a 71 binary string. 72 73 Parameters 74 ---------- 75 i 76 Integer value to convert to binary representation. 77 nbits : default=8 78 Number of bits to return. Valid values are 8 [DEFAULT], 16, 32, and 79 64. 80 output : default=str 81 Return data as `str` [DEFAULT] or `list` (list of ints). 82 83 Returns 84 ------- 85 int2bin 86 `str` or `list` (list of ints) of binary representation of the integer 87 value. 88 """ 89 i = int(i) if not isinstance(i,int) else i 90 assert nbits in [8,16,32,64] 91 bitstr = "{0:b}".format(i).zfill(nbits) 92 if output is str: 93 return bitstr 94 elif output is list: 95 return [int(b) for b in bitstr]
Convert integer to binary string or list
The struct module unpack using ">i" will unpack a 32-bit integer from a binary string.
Parameters
- i: Integer value to convert to binary representation.
- nbits (default=8): Number of bits to return. Valid values are 8 [DEFAULT], 16, 32, and 64.
- output (default=str):
Return data as
str[DEFAULT] orlist(list of ints).
Returns
- int2bin:
strorlist(list of ints) of binary representation of the integer value.
98def ieee_float_to_int(f): 99 """ 100 Convert an IEEE 754 32-bit float to a 32-bit integer. 101 102 Parameters 103 ---------- 104 f : float 105 Floating-point value. 106 107 Returns 108 ------- 109 ieee_float_to_int 110 `numpy.int32` representation of an IEEE 32-bit float. 111 """ 112 i = struct.unpack('>i',struct.pack('>f',np.float32(f)))[0] 113 return np.int32(i)
Convert an IEEE 754 32-bit float to a 32-bit integer.
Parameters
- f (float): Floating-point value.
Returns
- ieee_float_to_int:
numpy.int32representation of an IEEE 32-bit float.
116def ieee_int_to_float(i): 117 """ 118 Convert a 32-bit integer to an IEEE 32-bit float. 119 120 Parameters 121 ---------- 122 i : int 123 Integer value. 124 125 Returns 126 ------- 127 ieee_int_to_float 128 `numpy.float32` representation of a 32-bit int. 129 """ 130 f = struct.unpack('>f',struct.pack('>i',np.int32(i)))[0] 131 return np.float32(f)
Convert a 32-bit integer to an IEEE 32-bit float.
Parameters
- i (int): Integer value.
Returns
- ieee_int_to_float:
numpy.float32representation of a 32-bit int.
134def get_leadtime(pdtn: int, pdt: ArrayLike) -> datetime.timedelta: 135 """ 136 Compute lead time as a datetime.timedelta object. 137 138 Using information from GRIB2 Product Definition Template 139 Number, and Product Definition Template (Section 4). 140 141 Parameters 142 ---------- 143 pdtn 144 GRIB2 Product Definition Template Number 145 pdt 146 Sequence containing GRIB2 Product Definition Template (Section 4). 147 148 Returns 149 ------- 150 leadTime 151 datetime.timedelta object representing the lead time of the GRIB2 message. 152 """ 153 lt = tables.get_value_from_table(pdt[templates.UnitOfForecastTime._key[pdtn]], 'scale_time_seconds') 154 lt *= pdt[templates.ValueOfForecastTime._key[pdtn]] 155 return datetime.timedelta(seconds=int(lt))
Compute lead time as a datetime.timedelta object.
Using information from GRIB2 Product Definition Template Number, and Product Definition Template (Section 4).
Parameters
- pdtn: GRIB2 Product Definition Template Number
- pdt: Sequence containing GRIB2 Product Definition Template (Section 4).
Returns
- leadTime: datetime.timedelta object representing the lead time of the GRIB2 message.
158def get_duration(pdtn: int, pdt: ArrayLike) -> datetime.timedelta: 159 """ 160 Compute a time duration as a datetime.timedelta. 161 162 Uses information from Product Definition Template Number, and Product 163 Definition Template (Section 4). 164 165 Parameters 166 ---------- 167 pdtn 168 GRIB2 Product Definition Template Number 169 pdt 170 Sequence containing GRIB2 Product Definition Template (Section 4). 171 172 Returns 173 ------- 174 get_duration 175 datetime.timedelta object representing the time duration of the GRIB2 176 message. 177 """ 178 if pdtn in templates._timeinterval_pdtns: 179 ntime = pdt[templates.NumberOfTimeRanges._key[pdtn]] 180 duration_unit = tables.get_value_from_table( 181 pdt[templates.UnitOfTimeRangeOfStatisticalProcess._key[pdtn]], 182 'scale_time_seconds') 183 d = ntime * duration_unit * pdt[ 184 templates.TimeRangeOfStatisticalProcess._key[pdtn]] 185 else: 186 d = 0 187 return datetime.timedelta(seconds=int(d))
Compute a time duration as a datetime.timedelta.
Uses information from Product Definition Template Number, and Product Definition Template (Section 4).
Parameters
- pdtn: GRIB2 Product Definition Template Number
- pdt: Sequence containing GRIB2 Product Definition Template (Section 4).
Returns
- get_duration: datetime.timedelta object representing the time duration of the GRIB2 message.
190def decode_wx_strings(lus: bytes) -> Dict[int, str]: 191 """ 192 Decode GRIB2 Local Use Section to obtain NDFD/MDL Weather Strings. 193 194 The decode procedure is defined 195 [here](https://vlab.noaa.gov/web/mdl/nbm-gmos-grib2-wx-info). 196 197 Parameters 198 ---------- 199 lus 200 GRIB2 Local Use Section containing NDFD weather strings. 201 202 Returns 203 ------- 204 decode_wx_strings 205 Dict of NDFD/MDL weather strings. Keys are an integer value that 206 represent the sequential order of the key in the packed local use 207 section and the value is the weather key. 208 """ 209 assert lus[0] == 1 210 # Unpack information related to the simple packing method 211 # the packed weather string data. 212 ngroups = struct.unpack('>H',lus[1:3])[0] 213 nvalues = struct.unpack('>i',lus[3:7])[0] 214 refvalue = struct.unpack('>i',lus[7:11])[0] 215 dsf = struct.unpack('>h',lus[11:13])[0] 216 nbits = lus[13] 217 datatype = lus[14] 218 if datatype == 0: # Floating point 219 refvalue = np.float32(ieee_int_to_float(refvalue)*10**-dsf) 220 elif datatype == 1: # Integer 221 refvalue = np.int32(ieee_int_to_float(refvalue)*10**-dsf) 222 # Upack each byte starting at byte 15 to end of the local use 223 # section, create a binary string and append to the full 224 # binary string. 225 b = '' 226 for i in range(15,len(lus)): 227 iword = struct.unpack('>B',lus[i:i+1])[0] 228 b += bin(iword).split('b')[1].zfill(8) 229 # Iterate over the binary string (b). For each nbits 230 # chunk, convert to an integer, including the refvalue, 231 # and then convert the int to an ASCII character, then 232 # concatenate to wxstring. 233 wxstring = '' 234 for i in range(0,len(b),nbits): 235 wxstring += chr(int(b[i:i+nbits],2)+refvalue) 236 # Return string as list, split by null character. 237 #return list(filter(None,wxstring.split('\0'))) 238 return {n:k for n,k in enumerate(list(filter(None,wxstring.split('\0'))))}
Decode GRIB2 Local Use Section to obtain NDFD/MDL Weather Strings.
The decode procedure is defined here.
Parameters
- lus: GRIB2 Local Use Section containing NDFD weather strings.
Returns
- decode_wx_strings: Dict of NDFD/MDL weather strings. Keys are an integer value that represent the sequential order of the key in the packed local use section and the value is the weather key.
241def get_wgrib2_prob_string( 242 probtype: int, 243 sfacl: int, 244 svall: int, 245 sfacu: int, 246 svalu: int, 247) -> str: 248 """ 249 Return a wgrib2-styled string of probabilistic threshold information. 250 251 Logic from wgrib2 source, 252 [Prob.c](https://github.com/NOAA-EMC/NCEPLIBS-wgrib2/blob/develop/wgrib2/Prob.c), 253 is replicated here. 254 255 Parameters 256 ---------- 257 probtype 258 Type of probability (Code Table 4.9). 259 sfacl 260 Scale factor of lower limit. 261 svall 262 Scaled value of lower limit. 263 sfacu 264 Scale factor of upper limit. 265 svalu 266 Scaled value of upper limit. 267 268 Returns 269 ------- 270 get_wgrib2_prob_string 271 wgrib2-formatted string of probability threshold. 272 """ 273 probstr = '' 274 if sfacl == -127: sfacl = 0 275 if sfacu == -127: sfacu = 0 276 lower = svall/(10**sfacl) 277 upper = svalu/(10**sfacu) 278 if probtype == 0: 279 probstr = 'prob <%g' % (lower) 280 elif probtype == 1: 281 probstr = 'prob >%g' % (upper) 282 elif probtype == 2: 283 if lower == upper: 284 probstr = 'prob =%g' % (lower) 285 else: 286 probstr = 'prob >=%g <%g' % (lower,upper) 287 elif probtype == 3: 288 probstr = 'prob >%g' % (lower) 289 elif probtype == 4: 290 probstr = 'prob <%g' % (upper) 291 else: 292 probstr = '' 293 return probstr
Return a wgrib2-styled string of probabilistic threshold information.
Logic from wgrib2 source, Prob.c, is replicated here.
Parameters
- probtype: Type of probability (Code Table 4.9).
- sfacl: Scale factor of lower limit.
- svall: Scaled value of lower limit.
- sfacu: Scale factor of upper limit.
- svalu: Scaled value of upper limit.
Returns
- get_wgrib2_prob_string: wgrib2-formatted string of probability threshold.