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 14try: 15 from .. import iplib 16except ImportError: 17 pass 18from .. import tables 19from .. import templates 20 21 22def decimal_to_scaled_int( 23 value: Union[float, str, int], 24 scale_factor: Optional[int] = None, 25) -> Tuple[int, int]: 26 """ 27 Convert a float-like value to a scaled integer using the minimal decimal scaling factor. 28 29 The input value is internally converted to a `Decimal` to ensure precise scaling. 30 31 Parameters 32 ---------- 33 value : float, str, or int 34 The numeric value to scale. 35 scaled_value : int 36 The integer result of scaling the original value by `10**scale_factor`. 37 38 Returns 39 ------- 40 scale_factor : int 41 The smallest power of 10 such that `value * 10**scale_factor` is an exact integer. 42 scaled_value : int 43 The integer result of scaling the original value by `10**scale_factor`. 44 """ 45 dec_value = Decimal(str(value)) # Preserve exact decimal representation 46 47 with localcontext() as ctx: 48 ctx.prec = 28 49 50 if scale_factor is not None: 51 scaled = dec_value * (10**scale_factor) 52 if scaled != scaled.to_integral_value(): 53 raise ValueError(f"Value {value} cannot be exactly scaled by 10^{scale_factor}") 54 return scale_factor, int(scaled) 55 else: 56 scale_factor = 0 57 while dec_value != dec_value.to_integral_value(): 58 dec_value *= 10 59 scale_factor += 1 60 if scale_factor > 20: 61 raise ValueError(f"Could not find exact scale factor for value {value} within bounds.") 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 ) 183 d = min(ntime, 1) * duration_unit * pdt[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 struct.unpack(">H", lus[1:3])[0] 212 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 < 0: 274 sfacl = 0 275 if sfacu < 0: 276 sfacu = 0 277 lower = svall / (10**sfacl) 278 upper = svalu / (10**sfacu) 279 if probtype == 0: 280 probstr = "prob <%g" % (lower) 281 elif probtype == 1: 282 probstr = "prob >%g" % (upper) 283 elif probtype == 2: 284 if lower == upper: 285 probstr = "prob =%g" % (lower) 286 else: 287 probstr = "prob >=%g <%g" % (lower, upper) 288 elif probtype == 3: 289 probstr = "prob >%g" % (lower) 290 elif probtype == 4: 291 probstr = "prob <%g" % (upper) 292 else: 293 probstr = "" 294 return probstr 295 296 297def latlon_to_ij( 298 gdtn, 299 gdt, 300 lats, 301 lons, 302 missing_value=np.nan, 303): 304 """ 305 Convert latitude/longitude coordinates to grid (i, j) indices using the 306 GRIB2 Grid Definition Section (GDS). 307 308 This function calls the grib2io iplib Cython extension module function, 309 `grib2io.iplib.latlon_to_ij`. 310 311 Parameters 312 ---------- 313 gdtn : int 314 GRIB2 grid definition template number. 315 gdt : ndarray of int32 316 GRIB2 grid definition template values. 317 lats : numpy.ndarray or list 318 Latitude coordinates in degrees. 319 lons : numpy.ndarray or list 320 Longitude coordinates in degrees. 321 missing_value : float, optional 322 Missing value to represent when latitude/longitude coordinate is 323 outside the grid domain. 324 325 Returns 326 ------- 327 xpts : ndarray of float32 328 Grid x-coordinates (i-indices) corresponding to the input 329 latitude/longitude points. 330 ypts : ndarray of float32 331 Grid y-coordinates (j-indices) corresponding to the input 332 latitude/longitude points. 333 """ 334 # Check lats and lons 335 if isinstance(lats, list): 336 nlats = len(lats) 337 elif isinstance(lats, np.ndarray) and len(lats.shape) == 1: 338 nlats = lats.shape[0] 339 else: 340 raise ValueError("Latitudes must be a list or 1-D NumPy array.") 341 if isinstance(lons, list): 342 nlons = len(lons) 343 elif isinstance(lons, np.ndarray) and len(lons.shape) == 1: 344 nlons = lons.shape[0] 345 else: 346 raise ValueError("Longitudes must be a list or 1-D NumPy array.") 347 if nlats != nlons: 348 raise ValueError("Latitudes and longitudes same length.") 349 xpts, ypts = iplib.latlon_to_ij( 350 gdtn.astype(np.int32), 351 gdt.astype(np.int32), 352 np.array(lats, dtype=np.float64), 353 np.array(lons, dtype=np.float64), 354 missing_value, 355 ) 356 357 return xpts.astype(np.float32), ypts.astype(np.float32) 358 359 360def compute_with_retries(obj, *, max_attempts: int = 6, base_sleep: float = 2.0): 361 """ 362 Compute a Dask-backed object (Xarray Dataset or DataArray) with retries for transient errors. 363 364 Transient errors typically include network timeouts, connection resets, and 365 cloud-specific errors (e.g. IcechunkError, S3 SlowDown 429). 366 367 Parameters 368 ---------- 369 obj : xarray.Dataset, xarray.DataArray, or dask.delayed.Delayed 370 The object to compute. 371 max_attempts : int, optional 372 Maximum number of attempts. Defaults to 6. 373 base_sleep : float, optional 374 Base sleep time in seconds for exponential backoff. Defaults to 2.0. 375 376 Returns 377 ------- 378 The computed result (typically a NumPy-backed Xarray object or a scalar). 379 380 Raises 381 ------ 382 Exception 383 The last exception encountered if all attempts fail, or any non-transient 384 exception. 385 """ 386 import time 387 388 for attempt in range(1, max_attempts + 1): 389 try: 390 return obj.compute() 391 except Exception as exc: 392 msg = str(exc).lower() 393 # Basic transient error detection across typical remote backends 394 exc_name = type(exc).__name__.lower() 395 transient = ( 396 "icechunkerror" in exc_name 397 or "timeout" in msg 398 or "timed out" in msg 399 or "connect" in msg 400 or "slowdown" in msg 401 or "429" in msg 402 or "503" in msg 403 or "eof occurred in violation of protocol" in msg 404 or "connection reset" in msg 405 ) 406 if (not transient) or attempt == max_attempts: 407 raise 408 sleep_s = base_sleep**attempt 409 print(f"Transient read error ({type(exc).__name__}) on attempt {attempt}/{max_attempts}; retrying in {sleep_s}s...") 410 time.sleep(sleep_s) 411 412 413def percentile_string(pct): 414 """ 415 Return a percentile string with the proper English ordinal suffix. 416 417 Parameters 418 ---------- 419 pct : int 420 Percentile value in the range [0, 100]. 421 422 Returns 423 ------- 424 str 425 Percentile string, e.g., ``"1st percentile"``, 426 ``"21st percentile"``, or ``"90th percentile"``. 427 428 Raises 429 ------ 430 ValueError 431 If `pct` is not in the range [0, 100]. 432 433 Examples 434 -------- 435 >>> percentile_string(1) 436 '1st percentile' 437 >>> percentile_string(21) 438 '21st percentile' 439 >>> percentile_string(90) 440 '90th percentile' 441 """ 442 if not (0 <= pct <= 100): 443 raise ValueError("percentile must be between 0 and 100") 444 445 if 11 <= (pct % 100) <= 13: 446 suffix = "th" 447 else: 448 suffix = {1: "st", 2: "nd", 3: "rd"}.get(pct % 10, "th") 449 450 return f"{pct}{suffix} percentile"
23def decimal_to_scaled_int( 24 value: Union[float, str, int], 25 scale_factor: Optional[int] = None, 26) -> Tuple[int, int]: 27 """ 28 Convert a float-like value to a scaled integer using the minimal decimal scaling factor. 29 30 The input value is internally converted to a `Decimal` to ensure precise scaling. 31 32 Parameters 33 ---------- 34 value : float, str, or int 35 The numeric value to scale. 36 scaled_value : int 37 The integer result of scaling the original value by `10**scale_factor`. 38 39 Returns 40 ------- 41 scale_factor : int 42 The smallest power of 10 such that `value * 10**scale_factor` is an exact integer. 43 scaled_value : int 44 The integer result of scaling the original value by `10**scale_factor`. 45 """ 46 dec_value = Decimal(str(value)) # Preserve exact decimal representation 47 48 with localcontext() as ctx: 49 ctx.prec = 28 50 51 if scale_factor is not None: 52 scaled = dec_value * (10**scale_factor) 53 if scaled != scaled.to_integral_value(): 54 raise ValueError(f"Value {value} cannot be exactly scaled by 10^{scale_factor}") 55 return scale_factor, int(scaled) 56 else: 57 scale_factor = 0 58 while dec_value != dec_value.to_integral_value(): 59 dec_value *= 10 60 scale_factor += 1 61 if scale_factor > 20: 62 raise ValueError(f"Could not find exact scale factor for value {value} within bounds.") 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 ) 184 d = min(ntime, 1) * duration_unit * pdt[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 struct.unpack(">H", lus[1:3])[0] 213 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 < 0: 275 sfacl = 0 276 if sfacu < 0: 277 sfacu = 0 278 lower = svall / (10**sfacl) 279 upper = svalu / (10**sfacu) 280 if probtype == 0: 281 probstr = "prob <%g" % (lower) 282 elif probtype == 1: 283 probstr = "prob >%g" % (upper) 284 elif probtype == 2: 285 if lower == upper: 286 probstr = "prob =%g" % (lower) 287 else: 288 probstr = "prob >=%g <%g" % (lower, upper) 289 elif probtype == 3: 290 probstr = "prob >%g" % (lower) 291 elif probtype == 4: 292 probstr = "prob <%g" % (upper) 293 else: 294 probstr = "" 295 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.
298def latlon_to_ij( 299 gdtn, 300 gdt, 301 lats, 302 lons, 303 missing_value=np.nan, 304): 305 """ 306 Convert latitude/longitude coordinates to grid (i, j) indices using the 307 GRIB2 Grid Definition Section (GDS). 308 309 This function calls the grib2io iplib Cython extension module function, 310 `grib2io.iplib.latlon_to_ij`. 311 312 Parameters 313 ---------- 314 gdtn : int 315 GRIB2 grid definition template number. 316 gdt : ndarray of int32 317 GRIB2 grid definition template values. 318 lats : numpy.ndarray or list 319 Latitude coordinates in degrees. 320 lons : numpy.ndarray or list 321 Longitude coordinates in degrees. 322 missing_value : float, optional 323 Missing value to represent when latitude/longitude coordinate is 324 outside the grid domain. 325 326 Returns 327 ------- 328 xpts : ndarray of float32 329 Grid x-coordinates (i-indices) corresponding to the input 330 latitude/longitude points. 331 ypts : ndarray of float32 332 Grid y-coordinates (j-indices) corresponding to the input 333 latitude/longitude points. 334 """ 335 # Check lats and lons 336 if isinstance(lats, list): 337 nlats = len(lats) 338 elif isinstance(lats, np.ndarray) and len(lats.shape) == 1: 339 nlats = lats.shape[0] 340 else: 341 raise ValueError("Latitudes must be a list or 1-D NumPy array.") 342 if isinstance(lons, list): 343 nlons = len(lons) 344 elif isinstance(lons, np.ndarray) and len(lons.shape) == 1: 345 nlons = lons.shape[0] 346 else: 347 raise ValueError("Longitudes must be a list or 1-D NumPy array.") 348 if nlats != nlons: 349 raise ValueError("Latitudes and longitudes same length.") 350 xpts, ypts = iplib.latlon_to_ij( 351 gdtn.astype(np.int32), 352 gdt.astype(np.int32), 353 np.array(lats, dtype=np.float64), 354 np.array(lons, dtype=np.float64), 355 missing_value, 356 ) 357 358 return xpts.astype(np.float32), ypts.astype(np.float32)
Convert latitude/longitude coordinates to grid (i, j) indices using the GRIB2 Grid Definition Section (GDS).
This function calls the grib2io iplib Cython extension module function,
grib2io.iplib.latlon_to_ij.
Parameters
- gdtn (int): GRIB2 grid definition template number.
- gdt (ndarray of int32): GRIB2 grid definition template values.
- lats (numpy.ndarray or list): Latitude coordinates in degrees.
- lons (numpy.ndarray or list): Longitude coordinates in degrees.
- missing_value (float, optional): Missing value to represent when latitude/longitude coordinate is outside the grid domain.
Returns
- xpts (ndarray of float32): Grid x-coordinates (i-indices) corresponding to the input latitude/longitude points.
- ypts (ndarray of float32): Grid y-coordinates (j-indices) corresponding to the input latitude/longitude points.
361def compute_with_retries(obj, *, max_attempts: int = 6, base_sleep: float = 2.0): 362 """ 363 Compute a Dask-backed object (Xarray Dataset or DataArray) with retries for transient errors. 364 365 Transient errors typically include network timeouts, connection resets, and 366 cloud-specific errors (e.g. IcechunkError, S3 SlowDown 429). 367 368 Parameters 369 ---------- 370 obj : xarray.Dataset, xarray.DataArray, or dask.delayed.Delayed 371 The object to compute. 372 max_attempts : int, optional 373 Maximum number of attempts. Defaults to 6. 374 base_sleep : float, optional 375 Base sleep time in seconds for exponential backoff. Defaults to 2.0. 376 377 Returns 378 ------- 379 The computed result (typically a NumPy-backed Xarray object or a scalar). 380 381 Raises 382 ------ 383 Exception 384 The last exception encountered if all attempts fail, or any non-transient 385 exception. 386 """ 387 import time 388 389 for attempt in range(1, max_attempts + 1): 390 try: 391 return obj.compute() 392 except Exception as exc: 393 msg = str(exc).lower() 394 # Basic transient error detection across typical remote backends 395 exc_name = type(exc).__name__.lower() 396 transient = ( 397 "icechunkerror" in exc_name 398 or "timeout" in msg 399 or "timed out" in msg 400 or "connect" in msg 401 or "slowdown" in msg 402 or "429" in msg 403 or "503" in msg 404 or "eof occurred in violation of protocol" in msg 405 or "connection reset" in msg 406 ) 407 if (not transient) or attempt == max_attempts: 408 raise 409 sleep_s = base_sleep**attempt 410 print(f"Transient read error ({type(exc).__name__}) on attempt {attempt}/{max_attempts}; retrying in {sleep_s}s...") 411 time.sleep(sleep_s)
Compute a Dask-backed object (Xarray Dataset or DataArray) with retries for transient errors.
Transient errors typically include network timeouts, connection resets, and cloud-specific errors (e.g. IcechunkError, S3 SlowDown 429).
Parameters
- obj (xarray.Dataset, xarray.DataArray, or dask.delayed.Delayed): The object to compute.
- max_attempts (int, optional): Maximum number of attempts. Defaults to 6.
- base_sleep (float, optional): Base sleep time in seconds for exponential backoff. Defaults to 2.0.
Returns
- The computed result (typically a NumPy-backed Xarray object or a scalar).
Raises
- Exception: The last exception encountered if all attempts fail, or any non-transient exception.
414def percentile_string(pct): 415 """ 416 Return a percentile string with the proper English ordinal suffix. 417 418 Parameters 419 ---------- 420 pct : int 421 Percentile value in the range [0, 100]. 422 423 Returns 424 ------- 425 str 426 Percentile string, e.g., ``"1st percentile"``, 427 ``"21st percentile"``, or ``"90th percentile"``. 428 429 Raises 430 ------ 431 ValueError 432 If `pct` is not in the range [0, 100]. 433 434 Examples 435 -------- 436 >>> percentile_string(1) 437 '1st percentile' 438 >>> percentile_string(21) 439 '21st percentile' 440 >>> percentile_string(90) 441 '90th percentile' 442 """ 443 if not (0 <= pct <= 100): 444 raise ValueError("percentile must be between 0 and 100") 445 446 if 11 <= (pct % 100) <= 13: 447 suffix = "th" 448 else: 449 suffix = {1: "st", 2: "nd", 3: "rd"}.get(pct % 10, "th") 450 451 return f"{pct}{suffix} percentile"
Return a percentile string with the proper English ordinal suffix.
Parameters
- pct (int): Percentile value in the range [0, 100].
Returns
- str: Percentile string, e.g.,
"1st percentile","21st percentile", or"90th percentile".
Raises
- ValueError: If
pctis not in the range [0, 100].
Examples
>>> percentile_string(1)
'1st percentile'
>>> percentile_string(21)
'21st percentile'
>>> percentile_string(90)
'90th percentile'