grib2io
Introduction
grib2io is a Python package that provides an interface to the NCEP GRIB2 C (g2c) library for the purpose of reading and writing WMO GRIdded Binary, Edition 2 (GRIB2) messages. A physical file can contain one or more GRIB2 messages.
GRIB2 file IO is performed directly in Python. The unpacking/packing of GRIB2 integer, coded metadata and data sections is performed by the g2c library functions via the g2clib Cython wrapper module. The decoding/encoding of GRIB2 metadata is translated into more descriptive, plain language metadata by looking up the integer code values against the appropriate GRIB2 code tables. These code tables are a part of the grib2io module.
Tutorials
The following Jupyter Notebooks are available as tutorials:
1from ._grib2io import * 2from ._grib2io import __doc__ 3from ._grib2io import _Grib2Message 4 5__all__ = ['open', 'show_config', 'interpolate', 'interpolate_to_stations', 6 'tables', 'templates', 'utils', 7 'Grib2Message', '_Grib2Message', 'Grib2GridDef'] 8 9try: 10 from . import __config__ 11 __version__ = __config__.grib2io_version 12except(ImportError): 13 pass 14 15from .g2clib import __version__ as __g2clib_version__ 16from .g2clib import _has_jpeg 17from .g2clib import _has_png 18from .g2clib import _has_aec 19 20has_jpeg_support = bool(_has_jpeg) 21has_png_support = bool(_has_png) 22has_aec_support = bool(_has_aec) 23 24from .tables.originating_centers import _ncep_grib2_table_version 25ncep_grib2_table_version = _ncep_grib2_table_version 26g2c_version = __g2clib_version__ 27 28def show_config(): 29 """Print grib2io build configuration information.""" 30 print(f'grib2io version {__version__} Configuration:\n') 31 print(f'\tg2c library version: {__g2clib_version__}') 32 print(f'\tJPEG compression support: {has_jpeg_support}') 33 print(f'\tPNG compression support: {has_png_support}') 34 print(f'\tAEC compression support: {has_aec_support}') 35 print(f'') 36 print(f'\tNCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
60class open(): 61 """ 62 GRIB2 File Object. 63 64 A physical file can contain one or more GRIB2 messages. When instantiated, 65 class `grib2io.open`, the file named `filename` is opened for reading (`mode 66 = 'r'`) and is automatically indexed. The indexing procedure reads some of 67 the GRIB2 metadata for all GRIB2 Messages. A GRIB2 Message may contain 68 submessages whereby Section 2-7 can be repeated. grib2io accommodates for 69 this by flattening any GRIB2 submessages into multiple individual messages. 70 71 It is important to note that GRIB2 files from some Meteorological agencies 72 contain other data than GRIB2 messages. GRIB2 files from ECMWF can contain 73 GRIB1 and GRIB2 messages. grib2io checks for these and safely ignores them. 74 75 Attributes 76 ---------- 77 closed : bool 78 `True` is file handle is close; `False` otherwise. 79 current_message : int 80 Current position of the file in units of GRIB2 Messages. 81 levels : tuple 82 Tuple containing a unique list of wgrib2-formatted level/layer strings. 83 messages : int 84 Count of GRIB2 Messages contained in the file. 85 mode : str 86 File IO mode of opening the file. 87 name : str 88 Full path name of the GRIB2 file. 89 size : int 90 Size of the file in units of bytes. 91 variables : tuple 92 Tuple containing a unique list of variable short names (i.e. GRIB2 93 abbreviation names). 94 """ 95 __slots__ = ('_fileid', '_filehandle', '_hasindex', '_index', '_nodata', 96 '_pos', 'closed', 'current_message', 'messages', 'mode', 97 'name', 'size') 98 def __init__(self, filename: str, mode: str="r", **kwargs): 99 """ 100 Initialize GRIB2 File object instance. 101 102 Parameters 103 ---------- 104 filename 105 File name containing GRIB2 messages. 106 mode: default="r" 107 File access mode where "r" opens the files for reading only; "w" 108 opens the file for writing. 109 """ 110 # Manage keywords 111 if "_xarray_backend" not in kwargs: 112 kwargs["_xarray_backend"] = False 113 self._nodata = False 114 else: 115 self._nodata = kwargs["_xarray_backend"] 116 if mode in {'a','r','w'}: 117 mode = mode+'b' 118 if 'w' in mode: mode += '+' 119 if 'a' in mode: mode += '+' 120 121 # Some GRIB2 files are gzipped, so check for that here, but 122 # raise error when using xarray backend. 123 if 'r' in mode: 124 self._filehandle = builtins.open(filename, mode=mode) 125 # Gzip files contain a 2-byte header b'\x1f\x8b'. 126 if self._filehandle.read(2) == b'\x1f\x8b': 127 self._filehandle.close() 128 if '_xarray_backend' in kwargs.keys(): 129 raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.') 130 import gzip 131 self._filehandle = gzip.open(filename, mode=mode) 132 else: 133 self._filehandle = builtins.open(filename, mode=mode) 134 else: 135 self._filehandle = builtins.open(filename, mode=mode) 136 137 self.name = os.path.abspath(filename) 138 fstat = os.stat(self.name) 139 self._hasindex = False 140 self._index = {} 141 self.mode = mode 142 self.messages = 0 143 self.current_message = 0 144 self.size = fstat.st_size 145 self.closed = self._filehandle.closed 146 self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+ 147 str(self.size)).encode('ASCII')).hexdigest() 148 if 'r' in self.mode: 149 self._build_index() 150 # FIX: Cannot perform reads on mode='a' 151 #if 'a' in self.mode and self.size > 0: self._build_index() 152 153 154 def __delete__(self, instance): 155 self.close() 156 del self._index 157 158 159 def __enter__(self): 160 return self 161 162 163 def __exit__(self, atype, value, traceback): 164 self.close() 165 166 167 def __iter__(self): 168 yield from self._index['msg'] 169 170 171 def __len__(self): 172 return self.messages 173 174 175 def __repr__(self): 176 strings = [] 177 for k in self.__slots__: 178 if k.startswith('_'): continue 179 strings.append('%s = %s\n'%(k,eval('self.'+k))) 180 return ''.join(strings) 181 182 183 def __getitem__(self, key): 184 if isinstance(key,int): 185 if abs(key) >= len(self._index['msg']): 186 raise IndexError("index out of range") 187 else: 188 return self._index['msg'][key] 189 elif isinstance(key,str): 190 return self.select(shortName=key) 191 elif isinstance(key,slice): 192 return self._index['msg'][key] 193 else: 194 raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.') 195 196 197 def _build_index(self): 198 """Perform indexing of GRIB2 Messages.""" 199 # Initialize index dictionary 200 if not self._hasindex: 201 self._index['sectionOffset'] = [] 202 self._index['sectionSize'] = [] 203 self._index['msgSize'] = [] 204 self._index['msgNumber'] = [] 205 self._index['msg'] = [] 206 self._index['isSubmessage'] = [] 207 self._hasindex = True 208 209 # Iterate 210 while True: 211 try: 212 # Initialize 213 bmapflag = None 214 pos = self._filehandle.tell() 215 section2 = b'' 216 trailer = b'' 217 _secpos = dict.fromkeys(range(8)) 218 _secsize = dict.fromkeys(range(8)) 219 _isSubmessage = False 220 221 # Ignore headers (usually text) that are not part of the GRIB2 222 # file. For example, NAVGEM files have a http header at the 223 # beginning that needs to be ignored. 224 225 # Read a byte at a time until "GRIB" is found. Using 226 # "wgrib2" on a NAVGEM file, the header was 421 bytes and 227 # decided to go to 2048 bytes to be safe. For normal GRIB2 228 # files this should be quick and break out of the first 229 # loop when "test_offset" is 0. 230 for test_offset in range(2048): 231 self._filehandle.seek(pos + test_offset) 232 header = struct.unpack(">i", self._filehandle.read(4))[0] 233 if header.to_bytes(4, "big") == b"GRIB": 234 pos = pos + test_offset 235 break 236 237 # Read the rest of Section 0 using struct. 238 _secpos[0] = self._filehandle.tell()-4 239 _secsize[0] = 16 240 secmsg = self._filehandle.read(12) 241 section0 = np.concatenate(([header],list(struct.unpack('>HBBQ',secmsg))),dtype=np.int64) 242 243 # Make sure message is GRIB2. 244 if section0[3] != 2: 245 # Check for GRIB1 and ignore. 246 if secmsg[3] == 1: 247 warnings.warn("GRIB version 1 message detected. Ignoring...") 248 grib1_size = int.from_bytes(secmsg[:3],"big") 249 self._filehandle.seek(self._filehandle.tell()+grib1_size-16) 250 continue 251 else: 252 raise ValueError("Bad GRIB version number.") 253 254 # Read and unpack sections 1 through 8 which all follow a 255 # pattern of section size, number, and content. 256 while 1: 257 # Read first 5 bytes of the section which contains the size 258 # of the section (4 bytes) and the section number (1 byte). 259 secmsg = self._filehandle.read(5) 260 secsize, secnum = struct.unpack('>iB',secmsg) 261 262 # Record the offset of the section number and "append" the 263 # rest of the section to secmsg. 264 _secpos[secnum] = self._filehandle.tell()-5 265 _secsize[secnum] = secsize 266 if secnum in {1,3,4,5}: 267 secmsg += self._filehandle.read(secsize-5) 268 grbpos = 0 269 270 # Unpack section 271 if secnum == 1: 272 # Unpack Section 1 273 section1, grbpos = g2clib.unpack1(secmsg,grbpos,np.empty) 274 elif secnum == 2: 275 # Unpack Section 2 276 section2 = self._filehandle.read(secsize-5) 277 elif secnum == 3: 278 # Unpack Section 3 279 gds, gdt, deflist, grbpos = g2clib.unpack3(secmsg,grbpos,np.empty) 280 gds = gds.tolist() 281 gdt = gdt.tolist() 282 section3 = np.concatenate((gds,gdt)) 283 section3 = np.where(section3==4294967295,-1,section3) 284 elif secnum == 4: 285 # Unpack Section 4 286 numcoord, pdt, pdtnum, coordlist, grbpos = g2clib.unpack4(secmsg,grbpos,np.empty) 287 pdt = pdt.tolist() 288 section4 = np.concatenate((np.array((numcoord,pdtnum)),pdt)) 289 elif secnum == 5: 290 # Unpack Section 5 291 drt, drtn, npts, self._pos = g2clib.unpack5(secmsg,grbpos,np.empty) 292 section5 = np.concatenate((np.array((npts,drtn)),drt)) 293 section5 = np.where(section5==4294967295,-1,section5) 294 elif secnum == 6: 295 # Unpack Section 6 - Just the bitmap flag 296 bmapflag = struct.unpack('>B',self._filehandle.read(1))[0] 297 if bmapflag == 0: 298 bmappos = self._filehandle.tell()-6 299 elif bmapflag == 254: 300 # Do this to keep the previous position value 301 pass 302 else: 303 bmappos = None 304 self._filehandle.seek(self._filehandle.tell()+secsize-6) 305 elif secnum == 7: 306 # Do not unpack section 7 here, but move the file pointer 307 # to after section 7. 308 self._filehandle.seek(self._filehandle.tell()+secsize-5) 309 310 # Update the file index. 311 self.messages += 1 312 self._index['sectionOffset'].append(copy.deepcopy(_secpos)) 313 self._index['sectionSize'].append(copy.deepcopy(_secsize)) 314 self._index['msgSize'].append(section0[-1]) 315 self._index['msgNumber'].append(self.messages) 316 self._index['isSubmessage'].append(_isSubmessage) 317 318 # Create Grib2Message with data. 319 msg = Grib2Message(section0,section1,section2,section3,section4,section5,bmapflag) 320 msg._msgnum = self.messages-1 321 msg._deflist = deflist 322 msg._coordlist = coordlist 323 if not self._nodata: 324 msg._data = Grib2MessageOnDiskArray((msg.ny,msg.nx), 2, 325 TYPE_OF_VALUES_DTYPE[msg.typeOfValues], 326 self._filehandle, 327 msg, pos, _secpos[6], _secpos[7]) 328 self._index['msg'].append(msg) 329 330 # If here, then we have moved through GRIB2 section 1-7. 331 # Now we need to check the next 4 bytes after section 7. 332 trailer = struct.unpack('>i',self._filehandle.read(4))[0] 333 334 # If we reach the GRIB2 trailer string ('7777'), then we 335 # can break begin processing the next GRIB2 message. If 336 # not, then we continue within the same iteration to 337 # process a GRIB2 submessage. 338 if trailer.to_bytes(4, "big") == b'7777': 339 break 340 else: 341 # If here, trailer should be the size of the first 342 # section of the next submessage, then the next byte 343 # is the section number. Check this value. 344 nextsec = struct.unpack('>B',self._filehandle.read(1))[0] 345 if nextsec not in {2,3,4}: 346 raise ValueError("Bad GRIB2 message structure.") 347 self._filehandle.seek(self._filehandle.tell()-5) 348 _isSubmessage = True 349 continue 350 else: 351 raise ValueError("Bad GRIB2 section number.") 352 353 except(struct.error): 354 if 'r' in self.mode: 355 self._filehandle.seek(0) 356 break 357 358 359 @property 360 def levels(self): 361 if self._hasindex and not self._nodata: 362 return tuple(sorted(set([msg.level for msg in self._index['msg']]))) 363 else: 364 return None 365 366 367 @property 368 def variables(self): 369 if self._hasindex and not self._nodata: 370 return tuple(sorted(set([msg.shortName for msg in self._index['msg']]))) 371 else: 372 return None 373 374 375 def close(self): 376 """Close the file handle.""" 377 if not self._filehandle.closed: 378 self.messages = 0 379 self.current_message = 0 380 self._filehandle.close() 381 self.closed = self._filehandle.closed 382 383 384 def read(self, size: Optional[int]=None): 385 """ 386 Read size amount of GRIB2 messages from the current position. 387 388 If no argument is given, then size is None and all messages are returned 389 from the current position in the file. This read method follows the 390 behavior of Python's builtin open() function, but whereas that operates 391 on units of bytes, we operate on units of GRIB2 messages. 392 393 Parameters 394 ---------- 395 size: default=None 396 The number of GRIB2 messages to read from the current position. If 397 no argument is give, the default value is None and remainder of 398 the file is read. 399 400 Returns 401 ------- 402 read 403 ``Grib2Message`` object when size = 1 or a list of Grib2Messages 404 when size > 1. 405 """ 406 if size is not None and size < 0: 407 size = None 408 if size is None or size > 1: 409 start = self.tell() 410 stop = self.messages if size is None else start+size 411 if size is None: 412 self.current_message = self.messages-1 413 else: 414 self.current_message += size 415 return self._index['msg'][slice(start,stop,1)] 416 elif size == 1: 417 self.current_message += 1 418 return self._index['msg'][self.current_message] 419 else: 420 None 421 422 423 def seek(self, pos: int): 424 """ 425 Set the position within the file in units of GRIB2 messages. 426 427 Parameters 428 ---------- 429 pos 430 The GRIB2 Message number to set the file pointer to. 431 """ 432 if self._hasindex: 433 self._filehandle.seek(self._index['sectionOffset'][0][pos]) 434 self.current_message = pos 435 436 437 def tell(self): 438 """Returns the position of the file in units of GRIB2 Messages.""" 439 return self.current_message 440 441 442 def select(self, **kwargs): 443 """Select GRIB2 messages by `Grib2Message` attributes.""" 444 # TODO: Added ability to process multiple values for each keyword (attribute) 445 idxs = [] 446 nkeys = len(kwargs.keys()) 447 for k,v in kwargs.items(): 448 for m in self._index['msg']: 449 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 450 idxs = np.array(idxs,dtype=np.int32) 451 return [self._index['msg'][i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]] 452 453 454 def write(self, msg): 455 """ 456 Writes GRIB2 message object to file. 457 458 Parameters 459 ---------- 460 msg 461 GRIB2 message objects to write to file. 462 """ 463 if isinstance(msg,list): 464 for m in msg: 465 self.write(m) 466 return 467 468 if issubclass(msg.__class__,_Grib2Message): 469 if hasattr(msg,'_msg'): 470 self._filehandle.write(msg._msg) 471 else: 472 if msg._signature != msg._generate_signature(): 473 msg.pack() 474 self._filehandle.write(msg._msg) 475 else: 476 if hasattr(msg._data,'filehandle'): 477 msg._data.filehandle.seek(msg._data.offset) 478 self._filehandle.write(msg._data.filehandle.read(msg.section0[-1])) 479 else: 480 msg.pack() 481 self._filehandle.write(msg._msg) 482 self.flush() 483 self.size = os.path.getsize(self.name) 484 self._filehandle.seek(self.size-msg.section0[-1]) 485 self._build_index() 486 else: 487 raise TypeError("msg must be a Grib2Message object.") 488 return 489 490 491 def flush(self): 492 """Flush the file object buffer.""" 493 self._filehandle.flush() 494 495 496 def levels_by_var(self, name: str): 497 """ 498 Return a list of level strings given a variable shortName. 499 500 Parameters 501 ---------- 502 name 503 Grib2Message variable shortName 504 505 Returns 506 ------- 507 levels_by_var 508 A list of unique level strings. 509 """ 510 return list(sorted(set([msg.level for msg in self.select(shortName=name)]))) 511 512 513 def vars_by_level(self, level: str): 514 """ 515 Return a list of variable shortName strings given a level. 516 517 Parameters 518 ---------- 519 level 520 Grib2Message variable level 521 522 Returns 523 ------- 524 vars_by_level 525 A list of unique variable shortName strings. 526 """ 527 return list(sorted(set([msg.shortName for msg in self.select(level=level)])))
GRIB2 File Object.
A physical file can contain one or more GRIB2 messages. When instantiated,
class grib2io.open
, the file named filename
is opened for reading (mode
= 'r'
) and is automatically indexed. The indexing procedure reads some of
the GRIB2 metadata for all GRIB2 Messages. A GRIB2 Message may contain
submessages whereby Section 2-7 can be repeated. grib2io accommodates for
this by flattening any GRIB2 submessages into multiple individual messages.
It is important to note that GRIB2 files from some Meteorological agencies contain other data than GRIB2 messages. GRIB2 files from ECMWF can contain GRIB1 and GRIB2 messages. grib2io checks for these and safely ignores them.
Attributes
- closed (bool):
True
is file handle is close;False
otherwise. - current_message (int): Current position of the file in units of GRIB2 Messages.
- levels (tuple): Tuple containing a unique list of wgrib2-formatted level/layer strings.
- messages (int): Count of GRIB2 Messages contained in the file.
- mode (str): File IO mode of opening the file.
- name (str): Full path name of the GRIB2 file.
- size (int): Size of the file in units of bytes.
- variables (tuple): Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
98 def __init__(self, filename: str, mode: str="r", **kwargs): 99 """ 100 Initialize GRIB2 File object instance. 101 102 Parameters 103 ---------- 104 filename 105 File name containing GRIB2 messages. 106 mode: default="r" 107 File access mode where "r" opens the files for reading only; "w" 108 opens the file for writing. 109 """ 110 # Manage keywords 111 if "_xarray_backend" not in kwargs: 112 kwargs["_xarray_backend"] = False 113 self._nodata = False 114 else: 115 self._nodata = kwargs["_xarray_backend"] 116 if mode in {'a','r','w'}: 117 mode = mode+'b' 118 if 'w' in mode: mode += '+' 119 if 'a' in mode: mode += '+' 120 121 # Some GRIB2 files are gzipped, so check for that here, but 122 # raise error when using xarray backend. 123 if 'r' in mode: 124 self._filehandle = builtins.open(filename, mode=mode) 125 # Gzip files contain a 2-byte header b'\x1f\x8b'. 126 if self._filehandle.read(2) == b'\x1f\x8b': 127 self._filehandle.close() 128 if '_xarray_backend' in kwargs.keys(): 129 raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.') 130 import gzip 131 self._filehandle = gzip.open(filename, mode=mode) 132 else: 133 self._filehandle = builtins.open(filename, mode=mode) 134 else: 135 self._filehandle = builtins.open(filename, mode=mode) 136 137 self.name = os.path.abspath(filename) 138 fstat = os.stat(self.name) 139 self._hasindex = False 140 self._index = {} 141 self.mode = mode 142 self.messages = 0 143 self.current_message = 0 144 self.size = fstat.st_size 145 self.closed = self._filehandle.closed 146 self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+ 147 str(self.size)).encode('ASCII')).hexdigest() 148 if 'r' in self.mode: 149 self._build_index() 150 # FIX: Cannot perform reads on mode='a' 151 #if 'a' in self.mode and self.size > 0: self._build_index()
Initialize GRIB2 File object instance.
Parameters
- filename: File name containing GRIB2 messages.
- mode (default="r"): File access mode where "r" opens the files for reading only; "w" opens the file for writing.
375 def close(self): 376 """Close the file handle.""" 377 if not self._filehandle.closed: 378 self.messages = 0 379 self.current_message = 0 380 self._filehandle.close() 381 self.closed = self._filehandle.closed
Close the file handle.
384 def read(self, size: Optional[int]=None): 385 """ 386 Read size amount of GRIB2 messages from the current position. 387 388 If no argument is given, then size is None and all messages are returned 389 from the current position in the file. This read method follows the 390 behavior of Python's builtin open() function, but whereas that operates 391 on units of bytes, we operate on units of GRIB2 messages. 392 393 Parameters 394 ---------- 395 size: default=None 396 The number of GRIB2 messages to read from the current position. If 397 no argument is give, the default value is None and remainder of 398 the file is read. 399 400 Returns 401 ------- 402 read 403 ``Grib2Message`` object when size = 1 or a list of Grib2Messages 404 when size > 1. 405 """ 406 if size is not None and size < 0: 407 size = None 408 if size is None or size > 1: 409 start = self.tell() 410 stop = self.messages if size is None else start+size 411 if size is None: 412 self.current_message = self.messages-1 413 else: 414 self.current_message += size 415 return self._index['msg'][slice(start,stop,1)] 416 elif size == 1: 417 self.current_message += 1 418 return self._index['msg'][self.current_message] 419 else: 420 None
Read size amount of GRIB2 messages from the current position.
If no argument is given, then size is None and all messages are returned from the current position in the file. This read method follows the behavior of Python's builtin open() function, but whereas that operates on units of bytes, we operate on units of GRIB2 messages.
Parameters
- size (default=None): The number of GRIB2 messages to read from the current position. If no argument is give, the default value is None and remainder of the file is read.
Returns
- read:
Grib2Message
object when size = 1 or a list of Grib2Messages when size > 1.
423 def seek(self, pos: int): 424 """ 425 Set the position within the file in units of GRIB2 messages. 426 427 Parameters 428 ---------- 429 pos 430 The GRIB2 Message number to set the file pointer to. 431 """ 432 if self._hasindex: 433 self._filehandle.seek(self._index['sectionOffset'][0][pos]) 434 self.current_message = pos
Set the position within the file in units of GRIB2 messages.
Parameters
- pos: The GRIB2 Message number to set the file pointer to.
437 def tell(self): 438 """Returns the position of the file in units of GRIB2 Messages.""" 439 return self.current_message
Returns the position of the file in units of GRIB2 Messages.
442 def select(self, **kwargs): 443 """Select GRIB2 messages by `Grib2Message` attributes.""" 444 # TODO: Added ability to process multiple values for each keyword (attribute) 445 idxs = [] 446 nkeys = len(kwargs.keys()) 447 for k,v in kwargs.items(): 448 for m in self._index['msg']: 449 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 450 idxs = np.array(idxs,dtype=np.int32) 451 return [self._index['msg'][i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]
Select GRIB2 messages by Grib2Message
attributes.
454 def write(self, msg): 455 """ 456 Writes GRIB2 message object to file. 457 458 Parameters 459 ---------- 460 msg 461 GRIB2 message objects to write to file. 462 """ 463 if isinstance(msg,list): 464 for m in msg: 465 self.write(m) 466 return 467 468 if issubclass(msg.__class__,_Grib2Message): 469 if hasattr(msg,'_msg'): 470 self._filehandle.write(msg._msg) 471 else: 472 if msg._signature != msg._generate_signature(): 473 msg.pack() 474 self._filehandle.write(msg._msg) 475 else: 476 if hasattr(msg._data,'filehandle'): 477 msg._data.filehandle.seek(msg._data.offset) 478 self._filehandle.write(msg._data.filehandle.read(msg.section0[-1])) 479 else: 480 msg.pack() 481 self._filehandle.write(msg._msg) 482 self.flush() 483 self.size = os.path.getsize(self.name) 484 self._filehandle.seek(self.size-msg.section0[-1]) 485 self._build_index() 486 else: 487 raise TypeError("msg must be a Grib2Message object.") 488 return
Writes GRIB2 message object to file.
Parameters
- msg: GRIB2 message objects to write to file.
496 def levels_by_var(self, name: str): 497 """ 498 Return a list of level strings given a variable shortName. 499 500 Parameters 501 ---------- 502 name 503 Grib2Message variable shortName 504 505 Returns 506 ------- 507 levels_by_var 508 A list of unique level strings. 509 """ 510 return list(sorted(set([msg.level for msg in self.select(shortName=name)])))
Return a list of level strings given a variable shortName.
Parameters
- name: Grib2Message variable shortName
Returns
- levels_by_var: A list of unique level strings.
513 def vars_by_level(self, level: str): 514 """ 515 Return a list of variable shortName strings given a level. 516 517 Parameters 518 ---------- 519 level 520 Grib2Message variable level 521 522 Returns 523 ------- 524 vars_by_level 525 A list of unique variable shortName strings. 526 """ 527 return list(sorted(set([msg.shortName for msg in self.select(level=level)])))
Return a list of variable shortName strings given a level.
Parameters
- level: Grib2Message variable level
Returns
- vars_by_level: A list of unique variable shortName strings.
29def show_config(): 30 """Print grib2io build configuration information.""" 31 print(f'grib2io version {__version__} Configuration:\n') 32 print(f'\tg2c library version: {__g2clib_version__}') 33 print(f'\tJPEG compression support: {has_jpeg_support}') 34 print(f'\tPNG compression support: {has_png_support}') 35 print(f'\tAEC compression support: {has_aec_support}') 36 print(f'') 37 print(f'\tNCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
Print grib2io build configuration information.
1490def interpolate(a, method: Union[int, str], grid_def_in, grid_def_out, 1491 method_options=None, num_threads=1): 1492 """ 1493 This is the module-level interpolation function. 1494 1495 This interfaces with the grib2io_interp component package that interfaces to 1496 the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip). 1497 1498 Parameters 1499 ---------- 1500 a : numpy.ndarray or tuple 1501 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 1502 performed. If `a` is a `tuple`, then vector interpolation will be 1503 performed with the assumption that u = a[0] and v = a[1] and are both 1504 `numpy.ndarray`. 1505 1506 These data are expected to be in 2-dimensional form with shape (ny, nx) 1507 or 3-dimensional (:, ny, nx) where the 1st dimension represents another 1508 spatial, temporal, or classification (i.e. ensemble members) dimension. 1509 The function will properly flatten the (ny,nx) dimensions into (nx * ny) 1510 acceptable for input into the interpolation subroutines. 1511 method 1512 Interpolate method to use. This can either be an integer or string using 1513 the following mapping: 1514 1515 | Interpolate Scheme | Integer Value | 1516 | :---: | :---: | 1517 | 'bilinear' | 0 | 1518 | 'bicubic' | 1 | 1519 | 'neighbor' | 2 | 1520 | 'budget' | 3 | 1521 | 'spectral' | 4 | 1522 | 'neighbor-budget' | 6 | 1523 1524 grid_def_in : grib2io.Grib2GridDef 1525 Grib2GridDef object for the input grid. 1526 grid_def_out : grib2io.Grib2GridDef 1527 Grib2GridDef object for the output grid or station points. 1528 method_options : list of ints, optional 1529 Interpolation options. See the NCEPLIBS-ip documentation for 1530 more information on how these are used. 1531 num_threads : int, optional 1532 Number of OpenMP threads to use for interpolation. The default 1533 value is 1. If grib2io_interp was not built with OpenMP, then 1534 this keyword argument and value will have no impact. 1535 1536 Returns 1537 ------- 1538 interpolate 1539 Returns a `numpy.ndarray` when scalar interpolation is performed or a 1540 `tuple` of `numpy.ndarray`s when vector interpolation is performed with 1541 the assumptions that 0-index is the interpolated u and 1-index is the 1542 interpolated v. 1543 """ 1544 import grib2io_interp 1545 from grib2io_interp import interpolate 1546 1547 prev_num_threads = 1 1548 try: 1549 import grib2io_interp 1550 if grib2io_interp.has_openmp_support: 1551 prev_num_threads = grib2io_interp.get_openmp_threads() 1552 grib2io_interp.set_openmp_threads(num_threads) 1553 except(AttributeError): 1554 pass 1555 1556 if isinstance(method,int) and method not in _interp_schemes.values(): 1557 raise ValueError('Invalid interpolation method.') 1558 elif isinstance(method,str): 1559 if method in _interp_schemes.keys(): 1560 method = _interp_schemes[method] 1561 else: 1562 raise ValueError('Invalid interpolation method.') 1563 1564 if method_options is None: 1565 method_options = np.zeros((20),dtype=np.int32) 1566 if method in {3,6}: 1567 method_options[0:2] = -1 1568 1569 ni = grid_def_in.npoints 1570 no = grid_def_out.npoints 1571 1572 # Adjust shape of input array(s) 1573 a,newshp = _adjust_array_shape_for_interp(a,grid_def_in,grid_def_out) 1574 1575 # Set lats and lons if stations, else create array for grids. 1576 if grid_def_out.gdtn == -1: 1577 rlat = np.array(grid_def_out.lats,dtype=np.float32) 1578 rlon = np.array(grid_def_out.lons,dtype=np.float32) 1579 else: 1580 rlat = np.zeros((no),dtype=np.float32) 1581 rlon = np.zeros((no),dtype=np.float32) 1582 1583 # Call interpolation subroutines according to type of a. 1584 if isinstance(a,np.ndarray): 1585 # Scalar 1586 if np.any(np.isnan(a)): 1587 ibi = np.zeros((a.shape[0]),dtype=np.int32)+1 1588 li = np.where(np.isnan(a),0,1).astype(np.int8) 1589 else: 1590 ibi = np.zeros((a.shape[0]),dtype=np.int32) 1591 li = np.zeros(a.shape,dtype=np.int8) 1592 go = np.zeros((a.shape[0],no),dtype=np.float32) 1593 no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options, 1594 grid_def_in.gdtn,grid_def_in.gdt, 1595 grid_def_out.gdtn,grid_def_out.gdt, 1596 ibi,li.T,a.T,go.T,rlat,rlon) 1597 lo = lo.T.reshape(newshp) 1598 out = go.reshape(newshp) 1599 out = np.where(lo==0,np.nan,out) 1600 elif isinstance(a,tuple): 1601 # Vector 1602 if np.any(np.isnan(a)): 1603 ibi = np.zeros((a.shape[0]),dtype=np.int32)+1 1604 li = np.where(np.isnan(a),0,1).astype(np.int8) 1605 else: 1606 ibi = np.zeros((a.shape[0]),dtype=np.int32) 1607 li = np.zeros(a.shape,dtype=np.int8) 1608 uo = np.zeros((a[0].shape[0],no),dtype=np.float32) 1609 vo = np.zeros((a[1].shape[0],no),dtype=np.float32) 1610 crot = np.ones((no),dtype=np.float32) 1611 srot = np.zeros((no),dtype=np.float32) 1612 no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options, 1613 grid_def_in.gdtn,grid_def_in.gdt, 1614 grid_def_out.gdtn,grid_def_out.gdt, 1615 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T, 1616 rlat,rlon,crot,srot) 1617 del crot 1618 del srot 1619 lo = lo[:,0].reshape(newshp) 1620 uo = uo.reshape(new) 1621 vo = vo.reshape(new) 1622 uo = np.where(lo==0,np.nan,uo) 1623 vo = np.where(lo==0,np.nan,vo) 1624 out = (uo,vo) 1625 1626 del rlat 1627 del rlon 1628 1629 try: 1630 if grib2io_interp.has_openmp_support: 1631 grib2io_interp.set_openmp_threads(prev_num_threads) 1632 except(AttributeError): 1633 pass 1634 1635 return out
This is the module-level interpolation function.
This interfaces with the grib2io_interp component package that interfaces to the NCEPLIBS-ip library.
Parameters
a (numpy.ndarray or tuple): Input data. If
a
is anumpy.ndarray
, scalar interpolation will be performed. Ifa
is atuple
, then vector interpolation will be performed with the assumption that u = a[0] and v = a[1] and are bothnumpy.ndarray
.These data are expected to be in 2-dimensional form with shape (ny, nx) or 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, temporal, or classification (i.e. ensemble members) dimension. The function will properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into the interpolation subroutines.
- method: Interpolate method to use. This can either be an integer or string using the following mapping:
Interpolate Scheme | Integer Value |
---|---|
'bilinear' | 0 |
'bicubic' | 1 |
'neighbor' | 2 |
'budget' | 3 |
'spectral' | 4 |
'neighbor-budget' | 6 |
- grid_def_in (grib2io.Grib2GridDef): Grib2GridDef object for the input grid.
- grid_def_out (grib2io.Grib2GridDef): Grib2GridDef object for the output grid or station points.
- method_options (list of ints, optional): Interpolation options. See the NCEPLIBS-ip documentation for more information on how these are used.
- num_threads (int, optional): Number of OpenMP threads to use for interpolation. The default value is 1. If grib2io_interp was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
- interpolate: Returns a
numpy.ndarray
when scalar interpolation is performed or atuple
ofnumpy.ndarray
s when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
1638def interpolate_to_stations(a, method, grid_def_in, lats, lons, 1639 method_options=None, num_threads=1): 1640 """ 1641 Module-level interpolation function for interpolation to stations. 1642 1643 Interfaces with the grib2io_interp component package that interfaces to the 1644 [NCEPLIBS-ip 1645 library](https://github.com/NOAA-EMC/NCEPLIBS-ip). It supports scalar and 1646 vector interpolation according to the type of object a. 1647 1648 Parameters 1649 ---------- 1650 a : numpy.ndarray or tuple 1651 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 1652 performed. If `a` is a `tuple`, then vector interpolation will be 1653 performed with the assumption that u = a[0] and v = a[1] and are both 1654 `numpy.ndarray`. 1655 1656 These data are expected to be in 2-dimensional form with shape (ny, nx) 1657 or 3-dimensional (:, ny, nx) where the 1st dimension represents another 1658 spatial, temporal, or classification (i.e. ensemble members) dimension. 1659 The function will properly flatten the (ny,nx) dimensions into (nx * ny) 1660 acceptable for input into the interpolation subroutines. 1661 method 1662 Interpolate method to use. This can either be an integer or string using 1663 the following mapping: 1664 1665 | Interpolate Scheme | Integer Value | 1666 | :---: | :---: | 1667 | 'bilinear' | 0 | 1668 | 'bicubic' | 1 | 1669 | 'neighbor' | 2 | 1670 | 'budget' | 3 | 1671 | 'spectral' | 4 | 1672 | 'neighbor-budget' | 6 | 1673 1674 grid_def_in : grib2io.Grib2GridDef 1675 Grib2GridDef object for the input grid. 1676 lats : numpy.ndarray or list 1677 Latitudes for station points 1678 lons : numpy.ndarray or list 1679 Longitudes for station points 1680 method_options : list of ints, optional 1681 Interpolation options. See the NCEPLIBS-ip documentation for 1682 more information on how these are used. 1683 num_threads : int, optional 1684 Number of OpenMP threads to use for interpolation. The default 1685 value is 1. If grib2io_interp was not built with OpenMP, then 1686 this keyword argument and value will have no impact. 1687 1688 Returns 1689 ------- 1690 interpolate_to_stations 1691 Returns a `numpy.ndarray` when scalar interpolation is performed or a 1692 `tuple` of `numpy.ndarray`s when vector interpolation is performed with 1693 the assumptions that 0-index is the interpolated u and 1-index is the 1694 interpolated v. 1695 """ 1696 import grib2io_interp 1697 from grib2io_interp import interpolate 1698 1699 prev_num_threads = 1 1700 try: 1701 import grib2io_interp 1702 if grib2io_interp.has_openmp_support: 1703 prev_num_threads = grib2io_interp.get_openmp_threads() 1704 grib2io_interp.set_openmp_threads(num_threads) 1705 except(AttributeError): 1706 pass 1707 1708 if isinstance(method,int) and method not in _interp_schemes.values(): 1709 raise ValueError('Invalid interpolation method.') 1710 elif isinstance(method,str): 1711 if method in _interp_schemes.keys(): 1712 method = _interp_schemes[method] 1713 else: 1714 raise ValueError('Invalid interpolation method.') 1715 1716 if method_options is None: 1717 method_options = np.zeros((20),dtype=np.int32) 1718 if method in {3,6}: 1719 method_options[0:2] = -1 1720 1721 # Check lats and lons 1722 if isinstance(lats,list): 1723 nlats = len(lats) 1724 elif isinstance(lats,np.ndarray) and len(lats.shape) == 1: 1725 nlats = lats.shape[0] 1726 else: 1727 raise ValueError('Station latitudes must be a list or 1-D NumPy array.') 1728 if isinstance(lons,list): 1729 nlons = len(lons) 1730 elif isinstance(lons,np.ndarray) and len(lons.shape) == 1: 1731 nlons = lons.shape[0] 1732 else: 1733 raise ValueError('Station longitudes must be a list or 1-D NumPy array.') 1734 if nlats != nlons: 1735 raise ValueError('Station lats and lons must be same size.') 1736 1737 ni = grid_def_in.npoints 1738 no = nlats 1739 1740 # Adjust shape of input array(s) 1741 a,newshp = _adjust_array_shape_for_interp_stations(a,grid_def_in,no) 1742 1743 # Set lats and lons if stations 1744 rlat = np.array(lats,dtype=np.float32) 1745 rlon = np.array(lons,dtype=np.float32) 1746 1747 # Use gdtn = -1 for stations and an empty template array 1748 gdtn = -1 1749 gdt = np.zeros((200),dtype=np.int32) 1750 1751 # Call interpolation subroutines according to type of a. 1752 if isinstance(a,np.ndarray): 1753 # Scalar 1754 ibi = np.zeros((a.shape[0]),dtype=np.int32) 1755 li = np.zeros(a.shape,dtype=np.int32) 1756 go = np.zeros((a.shape[0],no),dtype=np.float32) 1757 no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options, 1758 grid_def_in.gdtn,grid_def_in.gdt, 1759 gdtn,gdt, 1760 ibi,li.T,a.T,go.T,rlat,rlon) 1761 out = go.reshape(newshp) 1762 elif isinstance(a,tuple): 1763 # Vector 1764 ibi = np.zeros((a[0].shape[0]),dtype=np.int32) 1765 li = np.zeros(a[0].shape,dtype=np.int32) 1766 uo = np.zeros((a[0].shape[0],no),dtype=np.float32) 1767 vo = np.zeros((a[1].shape[0],no),dtype=np.float32) 1768 crot = np.ones((no),dtype=np.float32) 1769 srot = np.zeros((no),dtype=np.float32) 1770 no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options, 1771 grid_def_in.gdtn,grid_def_in.gdt, 1772 gdtn,gdt, 1773 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T, 1774 rlat,rlon,crot,srot) 1775 del crot 1776 del srot 1777 out = (uo.reshape(newshp),vo.reshape(newshp)) 1778 1779 del rlat 1780 del rlon 1781 1782 try: 1783 if grib2io_interp.has_openmp_support: 1784 grib2io_interp.set_openmp_threads(prev_num_threads) 1785 except(AttributeError): 1786 pass 1787 1788 return out
Module-level interpolation function for interpolation to stations.
Interfaces with the grib2io_interp component package that interfaces to the NCEPLIBS-ip library. It supports scalar and vector interpolation according to the type of object a.
Parameters
a (numpy.ndarray or tuple): Input data. If
a
is anumpy.ndarray
, scalar interpolation will be performed. Ifa
is atuple
, then vector interpolation will be performed with the assumption that u = a[0] and v = a[1] and are bothnumpy.ndarray
.These data are expected to be in 2-dimensional form with shape (ny, nx) or 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, temporal, or classification (i.e. ensemble members) dimension. The function will properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into the interpolation subroutines.
- method: Interpolate method to use. This can either be an integer or string using the following mapping:
Interpolate Scheme | Integer Value |
---|---|
'bilinear' | 0 |
'bicubic' | 1 |
'neighbor' | 2 |
'budget' | 3 |
'spectral' | 4 |
'neighbor-budget' | 6 |
- grid_def_in (grib2io.Grib2GridDef): Grib2GridDef object for the input grid.
- lats (numpy.ndarray or list): Latitudes for station points
- lons (numpy.ndarray or list): Longitudes for station points
- method_options (list of ints, optional): Interpolation options. See the NCEPLIBS-ip documentation for more information on how these are used.
- num_threads (int, optional): Number of OpenMP threads to use for interpolation. The default value is 1. If grib2io_interp was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
- interpolate_to_stations: Returns a
numpy.ndarray
when scalar interpolation is performed or atuple
ofnumpy.ndarray
s when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
530class Grib2Message: 531 """ 532 Creation class for a GRIB2 message. 533 534 This class returns a dynamically-created Grib2Message object that 535 inherits from `_Grib2Message` and grid, product, data representation 536 template classes according to the template numbers for the respective 537 sections. If `section3`, `section4`, or `section5` are omitted, then 538 the appropriate keyword arguments for the template number `gdtn=`, 539 `pdtn=`, or `drtn=` must be provided. 540 541 Parameters 542 ---------- 543 section0 544 GRIB2 section 0 array. 545 section1 546 GRIB2 section 1 array. 547 section2 548 Local Use section data. 549 section3 550 GRIB2 section 3 array. 551 section4 552 GRIB2 section 4 array. 553 section5 554 GRIB2 section 5 array. 555 556 Returns 557 ------- 558 Msg 559 A dynamically-create Grib2Message object that inherits from 560 _Grib2Message, a grid definition template class, product 561 definition template class, and a data representation template 562 class. 563 """ 564 def __new__(self, section0: NDArray = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]), 565 section1: NDArray = np.zeros((13),dtype=np.int64), 566 section2: Optional[bytes] = None, 567 section3: Optional[NDArray] = None, 568 section4: Optional[NDArray] = None, 569 section5: Optional[NDArray] = None, *args, **kwargs): 570 571 if np.all(section1==0): 572 try: 573 # Python >= 3.10 574 section1[5:11] = datetime.datetime.fromtimestamp(0, datetime.UTC).timetuple()[:6] 575 except(AttributeError): 576 # Python < 3.10 577 section1[5:11] = datetime.datetime.utcfromtimestamp(0).timetuple()[:6] 578 579 bases = list() 580 if section3 is None: 581 if 'gdtn' in kwargs.keys(): 582 gdtn = kwargs['gdtn'] 583 Gdt = templates.gdt_class_by_gdtn(gdtn) 584 bases.append(Gdt) 585 section3 = np.zeros((Gdt._len+5),dtype=np.int64) 586 section3[4] = gdtn 587 else: 588 raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array") 589 else: 590 gdtn = section3[4] 591 Gdt = templates.gdt_class_by_gdtn(gdtn) 592 bases.append(Gdt) 593 594 if section4 is None: 595 if 'pdtn' in kwargs.keys(): 596 pdtn = kwargs['pdtn'] 597 Pdt = templates.pdt_class_by_pdtn(pdtn) 598 bases.append(Pdt) 599 section4 = np.zeros((Pdt._len+2),dtype=np.int64) 600 section4[1] = pdtn 601 else: 602 raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array") 603 else: 604 pdtn = section4[1] 605 Pdt = templates.pdt_class_by_pdtn(pdtn) 606 bases.append(Pdt) 607 608 if section5 is None: 609 if 'drtn' in kwargs.keys(): 610 drtn = kwargs['drtn'] 611 Drt = templates.drt_class_by_drtn(drtn) 612 bases.append(Drt) 613 section5 = np.zeros((Drt._len+2),dtype=np.int64) 614 section5[1] = drtn 615 else: 616 raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array") 617 else: 618 drtn = section5[1] 619 Drt = templates.drt_class_by_drtn(drtn) 620 bases.append(Drt) 621 622 # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo 623 try: 624 Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] 625 except KeyError: 626 @dataclass(init=False, repr=False) 627 class Msg(_Grib2Message, *bases): 628 pass 629 _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg 630 631 return Msg(section0, section1, section2, section3, section4, section5, *args)
Creation class for a GRIB2 message.
This class returns a dynamically-created Grib2Message object that
inherits from _Grib2Message
and grid, product, data representation
template classes according to the template numbers for the respective
sections. If section3
, section4
, or section5
are omitted, then
the appropriate keyword arguments for the template number gdtn=
,
pdtn=
, or drtn=
must be provided.
Parameters
- section0: GRIB2 section 0 array.
- section1: GRIB2 section 1 array.
- section2: Local Use section data.
- section3: GRIB2 section 3 array.
- section4: GRIB2 section 4 array.
- section5: GRIB2 section 5 array.
Returns
- Msg: A dynamically-create Grib2Message object that inherits from _Grib2Message, a grid definition template class, product definition template class, and a data representation template class.
634@dataclass 635class _Grib2Message: 636 """ 637 GRIB2 Message base class. 638 """ 639 # GRIB2 Sections 640 section0: NDArray = field(init=True,repr=False) 641 section1: NDArray = field(init=True,repr=False) 642 section2: bytes = field(init=True,repr=False) 643 section3: NDArray = field(init=True,repr=False) 644 section4: NDArray = field(init=True,repr=False) 645 section5: NDArray = field(init=True,repr=False) 646 bitMapFlag: templates.Grib2Metadata = field(init=True,repr=False,default=255) 647 648 # Section 0 looked up attributes 649 indicatorSection: NDArray = field(init=False,repr=False,default=templates.IndicatorSection()) 650 discipline: templates.Grib2Metadata = field(init=False,repr=False,default=templates.Discipline()) 651 652 # Section 1 looked up attributes 653 identificationSection: NDArray = field(init=False,repr=False,default=templates.IdentificationSection()) 654 originatingCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingCenter()) 655 originatingSubCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingSubCenter()) 656 masterTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.MasterTableInfo()) 657 localTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.LocalTableInfo()) 658 significanceOfReferenceTime: templates.Grib2Metadata = field(init=False,repr=False,default=templates.SignificanceOfReferenceTime()) 659 year: int = field(init=False,repr=False,default=templates.Year()) 660 month: int = field(init=False,repr=False,default=templates.Month()) 661 day: int = field(init=False,repr=False,default=templates.Day()) 662 hour: int = field(init=False,repr=False,default=templates.Hour()) 663 minute: int = field(init=False,repr=False,default=templates.Minute()) 664 second: int = field(init=False,repr=False,default=templates.Second()) 665 refDate: datetime.datetime = field(init=False,repr=False,default=templates.RefDate()) 666 productionStatus: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductionStatus()) 667 typeOfData: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfData()) 668 669 # Section 3 looked up common attributes. Other looked up attributes are available according 670 # to the Grid Definition Template. 671 gridDefinitionSection: NDArray = field(init=False,repr=False,default=templates.GridDefinitionSection()) 672 sourceOfGridDefinition: int = field(init=False,repr=False,default=templates.SourceOfGridDefinition()) 673 numberOfDataPoints: int = field(init=False,repr=False,default=templates.NumberOfDataPoints()) 674 interpretationOfListOfNumbers: templates.Grib2Metadata = field(init=False,repr=False,default=templates.InterpretationOfListOfNumbers()) 675 gridDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.GridDefinitionTemplateNumber()) 676 gridDefinitionTemplate: list = field(init=False,repr=False,default=templates.GridDefinitionTemplate()) 677 _earthparams: dict = field(init=False,repr=False,default=templates.EarthParams()) 678 _dxsign: float = field(init=False,repr=False,default=templates.DxSign()) 679 _dysign: float = field(init=False,repr=False,default=templates.DySign()) 680 _llscalefactor: float = field(init=False,repr=False,default=templates.LLScaleFactor()) 681 _lldivisor: float = field(init=False,repr=False,default=templates.LLDivisor()) 682 _xydivisor: float = field(init=False,repr=False,default=templates.XYDivisor()) 683 shapeOfEarth: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ShapeOfEarth()) 684 earthShape: str = field(init=False,repr=False,default=templates.EarthShape()) 685 earthRadius: float = field(init=False,repr=False,default=templates.EarthRadius()) 686 earthMajorAxis: float = field(init=False,repr=False,default=templates.EarthMajorAxis()) 687 earthMinorAxis: float = field(init=False,repr=False,default=templates.EarthMinorAxis()) 688 resolutionAndComponentFlags: list = field(init=False,repr=False,default=templates.ResolutionAndComponentFlags()) 689 ny: int = field(init=False,repr=False,default=templates.Ny()) 690 nx: int = field(init=False,repr=False,default=templates.Nx()) 691 scanModeFlags: list = field(init=False,repr=False,default=templates.ScanModeFlags()) 692 projParameters: dict = field(init=False,repr=False,default=templates.ProjParameters()) 693 694 # Section 4 695 productDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductDefinitionTemplateNumber()) 696 productDefinitionTemplate: NDArray = field(init=False,repr=False,default=templates.ProductDefinitionTemplate()) 697 698 # Section 5 looked up common attributes. Other looked up attributes are 699 # available according to the Data Representation Template. 700 numberOfPackedValues: int = field(init=False,repr=False,default=templates.NumberOfPackedValues()) 701 dataRepresentationTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.DataRepresentationTemplateNumber()) 702 dataRepresentationTemplate: list = field(init=False,repr=False,default=templates.DataRepresentationTemplate()) 703 typeOfValues: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfValues()) 704 705 706 def __post_init__(self): 707 """Set some attributes after init.""" 708 self._msgnum = -1 709 self._deflist = None 710 self._coordlist = None 711 self._signature = self._generate_signature() 712 try: 713 self._sha1_section3 = hashlib.sha1(self.section3).hexdigest() 714 except(TypeError): 715 pass 716 self.bitMapFlag = templates.Grib2Metadata(self.bitMapFlag,table='6.0') 717 self.bitmap = None 718 719 720 @property 721 def _isNDFD(self): 722 """Check if GRIB2 message is from NWS NDFD""" 723 return np.all(self.section1[0:2]==[8,65535]) 724 725 726 @property 727 def gdtn(self): 728 """Return Grid Definition Template Number""" 729 return self.section3[4] 730 731 732 @property 733 def gdt(self): 734 """Return Grid Definition Template.""" 735 return self.gridDefinitionTemplate 736 737 738 @property 739 def pdtn(self): 740 """Return Product Definition Template Number.""" 741 return self.section4[1] 742 743 744 @property 745 def pdt(self): 746 """Return Product Definition Template.""" 747 return self.productDefinitionTemplate 748 749 750 @property 751 def drtn(self): 752 """Return Data Representation Template Number.""" 753 return self.section5[1] 754 755 756 @property 757 def drt(self): 758 """Return Data Representation Template.""" 759 return self.dataRepresentationTemplate 760 761 762 @property 763 def pdy(self): 764 """Return the PDY ('YYYYMMDD').""" 765 return ''.join([str(i) for i in self.section1[5:8]]) 766 767 768 @property 769 def griddef(self): 770 """Return a Grib2GridDef instance for a GRIB2 message.""" 771 return Grib2GridDef.from_section3(self.section3) 772 773 774 @property 775 def lats(self): 776 """Return grid latitudes.""" 777 return self.latlons()[0] 778 779 780 @property 781 def lons(self): 782 """Return grid longitudes.""" 783 return self.latlons()[1] 784 785 @property 786 def min(self): 787 """Return minimum value of data.""" 788 return np.nanmin(self.data) 789 790 791 @property 792 def max(self): 793 """Return maximum value of data.""" 794 return np.nanmax(self.data) 795 796 797 @property 798 def mean(self): 799 """Return mean value of data.""" 800 return np.nanmean(self.data) 801 802 803 @property 804 def median(self): 805 """Return median value of data.""" 806 return np.nanmedian(self.data) 807 808 809 def __repr__(self): 810 """ 811 Return an unambiguous string representation of the object. 812 813 Returns 814 ------- 815 repr 816 A string representation of the object, including information from 817 sections 0, 1, 3, 4, 5, and 6. 818 """ 819 info = '' 820 for sect in [0,1,3,4,5,6]: 821 for k,v in self.attrs_by_section(sect,values=True).items(): 822 info += f'Section {sect}: {k} = {v}\n' 823 return info 824 825 def __str__(self): 826 """ 827 Return a readable string representation of the object. 828 829 Returns 830 ------- 831 str 832 A formatted string representation of the object, including 833 selected attributes. 834 """ 835 return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:' 836 f'{self.fullName} ({self.units}):{self.level}:' 837 f'{self.leadTime}') 838 839 840 def _generate_signature(self): 841 """Generature SHA-1 hash string from GRIB2 integer sections.""" 842 return hashlib.sha1(np.concatenate((self.section0,self.section1, 843 self.section3,self.section4, 844 self.section5))).hexdigest() 845 846 847 def attrs_by_section(self, sect: int, values: bool=False): 848 """ 849 Provide a tuple of attribute names for the given GRIB2 section. 850 851 Parameters 852 ---------- 853 sect 854 The GRIB2 section number. 855 values 856 Optional (default is `False`) argument to return attributes values. 857 858 Returns 859 ------- 860 attrs_by_section 861 A list of attribute names or dict of name:value pairs if `values = 862 True`. 863 """ 864 if sect in {0,1,6}: 865 attrs = templates._section_attrs[sect] 866 elif sect in {3,4,5}: 867 def _find_class_index(n): 868 _key = {3:'Grid', 4:'Product', 5:'Data'} 869 for i,c in enumerate(self.__class__.__mro__): 870 if _key[n] in c.__name__: 871 return i 872 else: 873 return [] 874 if sys.version_info.minor <= 8: 875 attrs = templates._section_attrs[sect]+\ 876 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 877 else: 878 attrs = templates._section_attrs[sect]+\ 879 self.__class__.__mro__[_find_class_index(sect)]._attrs 880 else: 881 attrs = [] 882 if values: 883 return {k:getattr(self,k) for k in attrs} 884 else: 885 return attrs 886 887 888 def pack(self): 889 """ 890 Pack GRIB2 section data into a binary message. 891 892 It is the user's responsibility to populate the GRIB2 section 893 information with appropriate metadata. 894 """ 895 # Create beginning of packed binary message with section 0 and 1 data. 896 self._sections = [] 897 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 898 self._sections += [0,1] 899 900 # Add section 2 if present. 901 if isinstance(self.section2,bytes) and len(self.section2) > 0: 902 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 903 self._sections.append(2) 904 905 # Add section 3. 906 self.section3[1] = self.nx * self.ny 907 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 908 self.gridDefinitionTemplate, 909 self._deflist) 910 self._sections.append(3) 911 912 # Prepare data. 913 field = np.copy(self.data) 914 if self.scanModeFlags is not None: 915 if self.scanModeFlags[3]: 916 fieldsave = field.astype('f') # Casting makes a copy 917 field[1::2,:] = fieldsave[1::2,::-1] 918 fld = field.astype('f') 919 920 # Prepare bitmap, if necessary 921 bitmapflag = self.bitMapFlag.value 922 if bitmapflag == 0: 923 if hasattr(self,'bitmap'): 924 bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT) 925 else: 926 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 927 else: 928 bmap = None 929 930 # Prepare optional coordinate list 931 if self._coordlist is not None: 932 crdlist = np.array(self._coordlist,'f') 933 else: 934 crdlist = None 935 936 # Prepare data for packing if nans are present 937 fld = np.ravel(fld) 938 if bitmapflag in {0,254}: 939 fld = np.where(np.isnan(fld),0,fld) 940 else: 941 if np.isnan(fld).any(): 942 if hasattr(self,'priMissingValue'): 943 fld = np.where(np.isnan(fld),self.priMissingValue,fld) 944 if hasattr(self,'_missvalmap'): 945 if hasattr(self,'priMissingValue'): 946 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 947 if hasattr(self,'secMissingValue'): 948 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 949 950 # Add sections 4, 5, 6, and 7. 951 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 952 self.productDefinitionTemplate, 953 crdlist, 954 self.drtn, 955 self.dataRepresentationTemplate, 956 fld, 957 bitmapflag, 958 bmap) 959 self._sections.append(4) 960 self._sections.append(5) 961 self._sections.append(6) 962 self._sections.append(7) 963 964 # Finalize GRIB2 message with section 8. 965 self._msg, self._pos = g2clib.grib2_end(self._msg) 966 self._sections.append(8) 967 self.section0[-1] = len(self._msg) 968 969 970 @property 971 def data(self) -> np.array: 972 """Access the unpacked data values.""" 973 if not hasattr(self,'_auto_nans'): self._auto_nans = _AUTO_NANS 974 if hasattr(self,'_data'): 975 if self._auto_nans != _AUTO_NANS: 976 self._data = self._ondiskarray 977 if isinstance(self._data, Grib2MessageOnDiskArray): 978 self._ondiskarray = self._data 979 self._data = np.asarray(self._data) 980 return self._data 981 raise ValueError 982 983 984 @data.setter 985 def data(self, data): 986 if not isinstance(data, np.ndarray): 987 raise ValueError('Grib2Message data only supports numpy arrays') 988 self._data = data 989 990 991 def flush_data(self): 992 """Flush the unpacked data values from the Grib2Message object.""" 993 del self._data 994 self._data = self._ondiskarray 995 self.bitmap = None 996 997 998 def __getitem__(self, item): 999 return self.data[item] 1000 1001 1002 def __setitem__(self, item): 1003 raise NotImplementedError('assignment of data not supported via setitem') 1004 1005 1006 def latlons(self, *args, **kwrgs): 1007 """Alias for `grib2io.Grib2Message.grid` method.""" 1008 return self.grid(*args, **kwrgs) 1009 1010 1011 def grid(self, unrotate: bool=True): 1012 """ 1013 Return lats,lons (in degrees) of grid. 1014 1015 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, 1016 stereographic, lambert conformal, albers equal-area, space-view and 1017 azimuthal equidistant grids. 1018 1019 Parameters 1020 ---------- 1021 unrotate 1022 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the 1023 grid, otherwise `False`, do not. 1024 1025 Returns 1026 ------- 1027 lats, lons : numpy.ndarray 1028 Returns two numpy.ndarrays with dtype=numpy.float32 of grid 1029 latitudes and longitudes in units of degrees. 1030 """ 1031 if self._sha1_section3 in _latlon_datastore.keys(): 1032 return (_latlon_datastore[self._sha1_section3]['latitude'], 1033 _latlon_datastore[self._sha1_section3]['longitude']) 1034 gdtn = self.gridDefinitionTemplateNumber.value 1035 gdtmpl = self.gridDefinitionTemplate 1036 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 1037 if gdtn == 0: 1038 # Regular lat/lon grid 1039 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1040 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1041 dlon = self.gridlengthXDirection 1042 dlat = self.gridlengthYDirection 1043 if lon2 < lon1 and dlon < 0: lon1 = -lon1 1044 lats = np.linspace(lat1,lat2,self.ny) 1045 if reggrid: 1046 lons = np.linspace(lon1,lon2,self.nx) 1047 else: 1048 lons = np.linspace(lon1,lon2,self.ny*2) 1049 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1050 elif gdtn == 1: # Rotated Lat/Lon grid 1051 pj = pyproj.Proj(self.projParameters) 1052 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 1053 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 1054 if lon1 > 180.0: lon1 -= 360.0 1055 if lon2 > 180.0: lon2 -= 360.0 1056 lats = np.linspace(lat1,lat2,self.ny) 1057 lons = np.linspace(lon1,lon2,self.nx) 1058 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1059 if unrotate: 1060 from grib2io.utils import rotated_grid 1061 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 1062 self.latitudeSouthernPole, 1063 self.longitudeSouthernPole) 1064 elif gdtn == 40: # Gaussian grid (only works for global!) 1065 from grib2io.utils.gauss_grid import gaussian_latitudes 1066 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1067 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1068 nlats = self.ny 1069 if not reggrid: # Reduced Gaussian grid. 1070 nlons = 2*nlats 1071 dlon = 360./nlons 1072 else: 1073 nlons = self.nx 1074 dlon = self.gridlengthXDirection 1075 lons = np.linspace(lon1,lon2,nlons) 1076 # Compute Gaussian lats (north to south) 1077 lats = gaussian_latitudes(nlats) 1078 if lat1 < lat2: # reverse them if necessary 1079 lats = lats[::-1] 1080 lons,lats = np.meshgrid(lons,lats) 1081 elif gdtn in {10,20,30,31,110}: 1082 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, 1083 # Azimuthal Equidistant 1084 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 1085 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1086 pj = pyproj.Proj(self.projParameters) 1087 llcrnrx, llcrnry = pj(lon1,lat1) 1088 x = llcrnrx+dx*np.arange(self.nx) 1089 y = llcrnry+dy*np.arange(self.ny) 1090 x,y = np.meshgrid(x, y) 1091 lons,lats = pj(x, y, inverse=True) 1092 elif gdtn == 90: 1093 # Satellite Projection 1094 dx = self.gridlengthXDirection 1095 dy = self.gridlengthYDirection 1096 pj = pyproj.Proj(self.projParameters) 1097 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 1098 x -= 0.5*x.max() 1099 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 1100 y -= 0.5*y.max() 1101 lons,lats = pj(x,y,inverse=True) 1102 # Set lons,lats to 1.e30 where undefined 1103 abslons = np.fabs(lons) 1104 abslats = np.fabs(lats) 1105 lons = np.where(abslons < 1.e20, lons, 1.e30) 1106 lats = np.where(abslats < 1.e20, lats, 1.e30) 1107 elif gdtn == 32769: 1108 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 1109 from grib2io.utils import arakawa_rotated_grid 1110 from grib2io.utils.rotated_grid import DEG2RAD 1111 di, dj = 0.0, 0.0 1112 do_180 = False 1113 idir = 1 if self.scanModeFlags[0] == 0 else -1 1114 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1115 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1116 do_rot = 1 if grid_oriented == 1 else 0 1117 la1 = self.latitudeFirstGridpoint 1118 lo1 = self.longitudeFirstGridpoint 1119 clon = self.longitudeCenterGridpoint 1120 clat = self.latitudeCenterGridpoint 1121 lasp = clat - 90.0 1122 losp = clon 1123 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1124 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1125 rlat = -llat 1126 rlon = -llon 1127 if self.nx == 1: 1128 di = 0.0 1129 elif idir == 1: 1130 ti = rlon 1131 while ti < llon: 1132 ti += 360.0 1133 di = (ti - llon)/float(self.nx-1) 1134 else: 1135 ti = llon 1136 while ti < rlon: 1137 ti += 360.0 1138 di = (ti - rlon)/float(self.nx-1) 1139 if self.ny == 1: 1140 dj = 0.0 1141 else: 1142 dj = (rlat - llat)/float(self.ny-1) 1143 if dj < 0.0: 1144 dj = -dj 1145 if idir == 1: 1146 if llon > rlon: 1147 llon -= 360.0 1148 if llon < 0 and rlon > 0: 1149 do_180 = True 1150 else: 1151 if rlon > llon: 1152 rlon -= 360.0 1153 if rlon < 0 and llon > 0: 1154 do_180 = True 1155 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1156 xlon1d = llon + (np.arange(self.nx)*idir*di) 1157 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1158 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1159 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1160 if do_180: 1161 lons = np.where(lons>180.0,lons-360.0,lons) 1162 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1163 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1164 del xlat1d, xlon1d, xlats, xlons 1165 else: 1166 raise ValueError('Unsupported grid') 1167 1168 _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons) 1169 try: 1170 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1171 except(NameError): 1172 pass 1173 1174 return lats, lons 1175 1176 1177 def map_keys(self): 1178 """ 1179 Unpack data grid replacing integer values with strings. 1180 1181 These types of fields are categorical or classifications where data 1182 values do not represent an observable or predictable physical quantity. 1183 An example of such a field would be [Dominant Precipitation Type - 1184 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1185 1186 Returns 1187 ------- 1188 map_keys 1189 numpy.ndarray of string values per element. 1190 """ 1191 hold_auto_nans = _AUTO_NANS 1192 set_auto_nans(False) 1193 if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \ 1194 (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'): 1195 keys = utils.decode_wx_strings(self.section2) 1196 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1197 keys[int(self.priMissingValue)] = 'Missing' 1198 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1199 keys[int(self.secMissingValue)] = 'Missing' 1200 u,inv = np.unique(self.data,return_inverse=True) 1201 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1202 else: 1203 # For data whose units are defined in a code table (i.e. classification or mask) 1204 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1205 fld = self.data.astype(np.int32).astype(str) 1206 tbl = tables.get_table(tblname,expand=True) 1207 for val in np.unique(fld): 1208 fld = np.where(fld==val,tbl[val],fld) 1209 set_auto_nans(hold_auto_nans) 1210 return fld 1211 1212 1213 def to_bytes(self, validate: bool=True): 1214 """ 1215 Return packed GRIB2 message in bytes format. 1216 1217 This will be useful for exporting data in non-file formats. For example, 1218 can be used to output grib data directly to S3 using the boto3 client 1219 without the need to write a temporary file to upload first. 1220 1221 Parameters 1222 ---------- 1223 validate: default=True 1224 If `True` (DEFAULT), validates first/last four bytes for proper 1225 formatting, else returns None. If `False`, message is output as is. 1226 1227 Returns 1228 ------- 1229 to_bytes 1230 Returns GRIB2 formatted message as bytes. 1231 """ 1232 if hasattr(self,'_msg'): 1233 if validate: 1234 if self.validate(): 1235 return self._msg 1236 else: 1237 return None 1238 else: 1239 return self._msg 1240 else: 1241 return None 1242 1243 1244 def interpolate(self, method, grid_def_out, method_options=None, drtn=None, 1245 num_threads=1): 1246 """ 1247 Grib2Message Interpolator 1248 1249 Performs spatial interpolation via the [NCEPLIBS-ip 1250 library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate 1251 method only supports scalar interpolation. If you need to perform 1252 vector interpolation, use the module-level `grib2io.interpolate` 1253 function. 1254 1255 Parameters 1256 ---------- 1257 method 1258 Interpolate method to use. This can either be an integer or string 1259 using the following mapping: 1260 1261 | Interpolate Scheme | Integer Value | 1262 | :---: | :---: | 1263 | 'bilinear' | 0 | 1264 | 'bicubic' | 1 | 1265 | 'neighbor' | 2 | 1266 | 'budget' | 3 | 1267 | 'spectral' | 4 | 1268 | 'neighbor-budget' | 6 | 1269 1270 grid_def_out : grib2io.Grib2GridDef 1271 Grib2GridDef object of the output grid. 1272 method_options : list of ints, optional 1273 Interpolation options. See the NCEPLIBS-ip documentation for 1274 more information on how these are used. 1275 drtn 1276 Data Representation Template to be used for the returned 1277 interpolated GRIB2 message. When `None`, the data representation 1278 template of the source GRIB2 message is used. Once again, it is the 1279 user's responsibility to properly set the Data Representation 1280 Template attributes. 1281 num_threads : int, optional 1282 Number of OpenMP threads to use for interpolation. The default 1283 value is 1. If grib2io_interp was not built with OpenMP, then 1284 this keyword argument and value will have no impact. 1285 1286 Returns 1287 ------- 1288 interpolate 1289 If interpolating to a grid, a new Grib2Message object is returned. 1290 The GRIB2 metadata of the new Grib2Message object is identical to 1291 the input except where required to be different because of the new 1292 grid specs and possibly a new data representation template. 1293 1294 If interpolating to station points, the interpolated data values are 1295 returned as a numpy.ndarray. 1296 """ 1297 section0 = self.section0 1298 section0[-1] = 0 1299 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1300 section3 = np.concatenate((gds,grid_def_out.gdt)) 1301 drtn = self.drtn if drtn is None else drtn 1302 1303 msg = Grib2Message(section0,self.section1,self.section2,section3, 1304 self.section4,None,self.bitMapFlag.value,drtn=drtn) 1305 1306 msg._msgnum = -1 1307 msg._deflist = self._deflist 1308 msg._coordlist = self._coordlist 1309 shape = (msg.ny,msg.nx) 1310 ndim = 2 1311 if msg.typeOfValues == 0: 1312 dtype = 'float32' 1313 elif msg.typeOfValues == 1: 1314 dtype = 'int32' 1315 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1316 method_options=method_options).reshape(msg.ny,msg.nx) 1317 msg.section5[0] = grid_def_out.npoints 1318 return msg 1319 1320 1321 def validate(self): 1322 """ 1323 Validate a complete GRIB2 message. 1324 1325 The g2c library does its own internal validation when g2_gribend() is called, but 1326 we will check in grib2io also. The validation checks if the first 4 bytes in 1327 self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in 1328 section 0 equals the length of the packed message. 1329 1330 Returns 1331 ------- 1332 `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise. 1333 """ 1334 valid = False 1335 if hasattr(self,'_msg'): 1336 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1337 if self.section0[-1] == len(self._msg): 1338 valid = True 1339 return valid
GRIB2 Message base class.
GRIB2 Section 1, Identification Section
GRIB2 Section 3, Grid Definition Section
Product Definition Template
726 @property 727 def gdtn(self): 728 """Return Grid Definition Template Number""" 729 return self.section3[4]
Return Grid Definition Template Number
732 @property 733 def gdt(self): 734 """Return Grid Definition Template.""" 735 return self.gridDefinitionTemplate
Return Grid Definition Template.
738 @property 739 def pdtn(self): 740 """Return Product Definition Template Number.""" 741 return self.section4[1]
Return Product Definition Template Number.
744 @property 745 def pdt(self): 746 """Return Product Definition Template.""" 747 return self.productDefinitionTemplate
Return Product Definition Template.
750 @property 751 def drtn(self): 752 """Return Data Representation Template Number.""" 753 return self.section5[1]
Return Data Representation Template Number.
756 @property 757 def drt(self): 758 """Return Data Representation Template.""" 759 return self.dataRepresentationTemplate
Return Data Representation Template.
762 @property 763 def pdy(self): 764 """Return the PDY ('YYYYMMDD').""" 765 return ''.join([str(i) for i in self.section1[5:8]])
Return the PDY ('YYYYMMDD').
768 @property 769 def griddef(self): 770 """Return a Grib2GridDef instance for a GRIB2 message.""" 771 return Grib2GridDef.from_section3(self.section3)
Return a Grib2GridDef instance for a GRIB2 message.
785 @property 786 def min(self): 787 """Return minimum value of data.""" 788 return np.nanmin(self.data)
Return minimum value of data.
791 @property 792 def max(self): 793 """Return maximum value of data.""" 794 return np.nanmax(self.data)
Return maximum value of data.
797 @property 798 def mean(self): 799 """Return mean value of data.""" 800 return np.nanmean(self.data)
Return mean value of data.
803 @property 804 def median(self): 805 """Return median value of data.""" 806 return np.nanmedian(self.data)
Return median value of data.
847 def attrs_by_section(self, sect: int, values: bool=False): 848 """ 849 Provide a tuple of attribute names for the given GRIB2 section. 850 851 Parameters 852 ---------- 853 sect 854 The GRIB2 section number. 855 values 856 Optional (default is `False`) argument to return attributes values. 857 858 Returns 859 ------- 860 attrs_by_section 861 A list of attribute names or dict of name:value pairs if `values = 862 True`. 863 """ 864 if sect in {0,1,6}: 865 attrs = templates._section_attrs[sect] 866 elif sect in {3,4,5}: 867 def _find_class_index(n): 868 _key = {3:'Grid', 4:'Product', 5:'Data'} 869 for i,c in enumerate(self.__class__.__mro__): 870 if _key[n] in c.__name__: 871 return i 872 else: 873 return [] 874 if sys.version_info.minor <= 8: 875 attrs = templates._section_attrs[sect]+\ 876 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 877 else: 878 attrs = templates._section_attrs[sect]+\ 879 self.__class__.__mro__[_find_class_index(sect)]._attrs 880 else: 881 attrs = [] 882 if values: 883 return {k:getattr(self,k) for k in attrs} 884 else: 885 return attrs
Provide a tuple of attribute names for the given GRIB2 section.
Parameters
- sect: The GRIB2 section number.
- values: Optional (default is
False
) argument to return attributes values.
Returns
- attrs_by_section: A list of attribute names or dict of name:value pairs if
values = True
.
888 def pack(self): 889 """ 890 Pack GRIB2 section data into a binary message. 891 892 It is the user's responsibility to populate the GRIB2 section 893 information with appropriate metadata. 894 """ 895 # Create beginning of packed binary message with section 0 and 1 data. 896 self._sections = [] 897 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 898 self._sections += [0,1] 899 900 # Add section 2 if present. 901 if isinstance(self.section2,bytes) and len(self.section2) > 0: 902 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 903 self._sections.append(2) 904 905 # Add section 3. 906 self.section3[1] = self.nx * self.ny 907 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 908 self.gridDefinitionTemplate, 909 self._deflist) 910 self._sections.append(3) 911 912 # Prepare data. 913 field = np.copy(self.data) 914 if self.scanModeFlags is not None: 915 if self.scanModeFlags[3]: 916 fieldsave = field.astype('f') # Casting makes a copy 917 field[1::2,:] = fieldsave[1::2,::-1] 918 fld = field.astype('f') 919 920 # Prepare bitmap, if necessary 921 bitmapflag = self.bitMapFlag.value 922 if bitmapflag == 0: 923 if hasattr(self,'bitmap'): 924 bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT) 925 else: 926 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 927 else: 928 bmap = None 929 930 # Prepare optional coordinate list 931 if self._coordlist is not None: 932 crdlist = np.array(self._coordlist,'f') 933 else: 934 crdlist = None 935 936 # Prepare data for packing if nans are present 937 fld = np.ravel(fld) 938 if bitmapflag in {0,254}: 939 fld = np.where(np.isnan(fld),0,fld) 940 else: 941 if np.isnan(fld).any(): 942 if hasattr(self,'priMissingValue'): 943 fld = np.where(np.isnan(fld),self.priMissingValue,fld) 944 if hasattr(self,'_missvalmap'): 945 if hasattr(self,'priMissingValue'): 946 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 947 if hasattr(self,'secMissingValue'): 948 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 949 950 # Add sections 4, 5, 6, and 7. 951 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 952 self.productDefinitionTemplate, 953 crdlist, 954 self.drtn, 955 self.dataRepresentationTemplate, 956 fld, 957 bitmapflag, 958 bmap) 959 self._sections.append(4) 960 self._sections.append(5) 961 self._sections.append(6) 962 self._sections.append(7) 963 964 # Finalize GRIB2 message with section 8. 965 self._msg, self._pos = g2clib.grib2_end(self._msg) 966 self._sections.append(8) 967 self.section0[-1] = len(self._msg)
Pack GRIB2 section data into a binary message.
It is the user's responsibility to populate the GRIB2 section information with appropriate metadata.
970 @property 971 def data(self) -> np.array: 972 """Access the unpacked data values.""" 973 if not hasattr(self,'_auto_nans'): self._auto_nans = _AUTO_NANS 974 if hasattr(self,'_data'): 975 if self._auto_nans != _AUTO_NANS: 976 self._data = self._ondiskarray 977 if isinstance(self._data, Grib2MessageOnDiskArray): 978 self._ondiskarray = self._data 979 self._data = np.asarray(self._data) 980 return self._data 981 raise ValueError
Access the unpacked data values.
991 def flush_data(self): 992 """Flush the unpacked data values from the Grib2Message object.""" 993 del self._data 994 self._data = self._ondiskarray 995 self.bitmap = None
Flush the unpacked data values from the Grib2Message object.
1006 def latlons(self, *args, **kwrgs): 1007 """Alias for `grib2io.Grib2Message.grid` method.""" 1008 return self.grid(*args, **kwrgs)
Alias for grib2io.Grib2Message.grid
method.
1011 def grid(self, unrotate: bool=True): 1012 """ 1013 Return lats,lons (in degrees) of grid. 1014 1015 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, 1016 stereographic, lambert conformal, albers equal-area, space-view and 1017 azimuthal equidistant grids. 1018 1019 Parameters 1020 ---------- 1021 unrotate 1022 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the 1023 grid, otherwise `False`, do not. 1024 1025 Returns 1026 ------- 1027 lats, lons : numpy.ndarray 1028 Returns two numpy.ndarrays with dtype=numpy.float32 of grid 1029 latitudes and longitudes in units of degrees. 1030 """ 1031 if self._sha1_section3 in _latlon_datastore.keys(): 1032 return (_latlon_datastore[self._sha1_section3]['latitude'], 1033 _latlon_datastore[self._sha1_section3]['longitude']) 1034 gdtn = self.gridDefinitionTemplateNumber.value 1035 gdtmpl = self.gridDefinitionTemplate 1036 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 1037 if gdtn == 0: 1038 # Regular lat/lon grid 1039 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1040 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1041 dlon = self.gridlengthXDirection 1042 dlat = self.gridlengthYDirection 1043 if lon2 < lon1 and dlon < 0: lon1 = -lon1 1044 lats = np.linspace(lat1,lat2,self.ny) 1045 if reggrid: 1046 lons = np.linspace(lon1,lon2,self.nx) 1047 else: 1048 lons = np.linspace(lon1,lon2,self.ny*2) 1049 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1050 elif gdtn == 1: # Rotated Lat/Lon grid 1051 pj = pyproj.Proj(self.projParameters) 1052 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 1053 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 1054 if lon1 > 180.0: lon1 -= 360.0 1055 if lon2 > 180.0: lon2 -= 360.0 1056 lats = np.linspace(lat1,lat2,self.ny) 1057 lons = np.linspace(lon1,lon2,self.nx) 1058 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1059 if unrotate: 1060 from grib2io.utils import rotated_grid 1061 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 1062 self.latitudeSouthernPole, 1063 self.longitudeSouthernPole) 1064 elif gdtn == 40: # Gaussian grid (only works for global!) 1065 from grib2io.utils.gauss_grid import gaussian_latitudes 1066 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1067 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1068 nlats = self.ny 1069 if not reggrid: # Reduced Gaussian grid. 1070 nlons = 2*nlats 1071 dlon = 360./nlons 1072 else: 1073 nlons = self.nx 1074 dlon = self.gridlengthXDirection 1075 lons = np.linspace(lon1,lon2,nlons) 1076 # Compute Gaussian lats (north to south) 1077 lats = gaussian_latitudes(nlats) 1078 if lat1 < lat2: # reverse them if necessary 1079 lats = lats[::-1] 1080 lons,lats = np.meshgrid(lons,lats) 1081 elif gdtn in {10,20,30,31,110}: 1082 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, 1083 # Azimuthal Equidistant 1084 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 1085 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1086 pj = pyproj.Proj(self.projParameters) 1087 llcrnrx, llcrnry = pj(lon1,lat1) 1088 x = llcrnrx+dx*np.arange(self.nx) 1089 y = llcrnry+dy*np.arange(self.ny) 1090 x,y = np.meshgrid(x, y) 1091 lons,lats = pj(x, y, inverse=True) 1092 elif gdtn == 90: 1093 # Satellite Projection 1094 dx = self.gridlengthXDirection 1095 dy = self.gridlengthYDirection 1096 pj = pyproj.Proj(self.projParameters) 1097 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 1098 x -= 0.5*x.max() 1099 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 1100 y -= 0.5*y.max() 1101 lons,lats = pj(x,y,inverse=True) 1102 # Set lons,lats to 1.e30 where undefined 1103 abslons = np.fabs(lons) 1104 abslats = np.fabs(lats) 1105 lons = np.where(abslons < 1.e20, lons, 1.e30) 1106 lats = np.where(abslats < 1.e20, lats, 1.e30) 1107 elif gdtn == 32769: 1108 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 1109 from grib2io.utils import arakawa_rotated_grid 1110 from grib2io.utils.rotated_grid import DEG2RAD 1111 di, dj = 0.0, 0.0 1112 do_180 = False 1113 idir = 1 if self.scanModeFlags[0] == 0 else -1 1114 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1115 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1116 do_rot = 1 if grid_oriented == 1 else 0 1117 la1 = self.latitudeFirstGridpoint 1118 lo1 = self.longitudeFirstGridpoint 1119 clon = self.longitudeCenterGridpoint 1120 clat = self.latitudeCenterGridpoint 1121 lasp = clat - 90.0 1122 losp = clon 1123 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1124 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1125 rlat = -llat 1126 rlon = -llon 1127 if self.nx == 1: 1128 di = 0.0 1129 elif idir == 1: 1130 ti = rlon 1131 while ti < llon: 1132 ti += 360.0 1133 di = (ti - llon)/float(self.nx-1) 1134 else: 1135 ti = llon 1136 while ti < rlon: 1137 ti += 360.0 1138 di = (ti - rlon)/float(self.nx-1) 1139 if self.ny == 1: 1140 dj = 0.0 1141 else: 1142 dj = (rlat - llat)/float(self.ny-1) 1143 if dj < 0.0: 1144 dj = -dj 1145 if idir == 1: 1146 if llon > rlon: 1147 llon -= 360.0 1148 if llon < 0 and rlon > 0: 1149 do_180 = True 1150 else: 1151 if rlon > llon: 1152 rlon -= 360.0 1153 if rlon < 0 and llon > 0: 1154 do_180 = True 1155 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1156 xlon1d = llon + (np.arange(self.nx)*idir*di) 1157 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1158 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1159 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1160 if do_180: 1161 lons = np.where(lons>180.0,lons-360.0,lons) 1162 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1163 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1164 del xlat1d, xlon1d, xlats, xlons 1165 else: 1166 raise ValueError('Unsupported grid') 1167 1168 _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons) 1169 try: 1170 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1171 except(NameError): 1172 pass 1173 1174 return lats, lons
Return lats,lons (in degrees) of grid.
Currently can handle reg. lat/lon,cglobal Gaussian, mercator, stereographic, lambert conformal, albers equal-area, space-view and azimuthal equidistant grids.
Parameters
- unrotate: If
True
[DEFAULT], and grid is rotated lat/lon, then unrotate the grid, otherwiseFalse
, do not.
Returns
- lats, lons (numpy.ndarray): Returns two numpy.ndarrays with dtype=numpy.float32 of grid latitudes and longitudes in units of degrees.
1177 def map_keys(self): 1178 """ 1179 Unpack data grid replacing integer values with strings. 1180 1181 These types of fields are categorical or classifications where data 1182 values do not represent an observable or predictable physical quantity. 1183 An example of such a field would be [Dominant Precipitation Type - 1184 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1185 1186 Returns 1187 ------- 1188 map_keys 1189 numpy.ndarray of string values per element. 1190 """ 1191 hold_auto_nans = _AUTO_NANS 1192 set_auto_nans(False) 1193 if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \ 1194 (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'): 1195 keys = utils.decode_wx_strings(self.section2) 1196 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1197 keys[int(self.priMissingValue)] = 'Missing' 1198 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1199 keys[int(self.secMissingValue)] = 'Missing' 1200 u,inv = np.unique(self.data,return_inverse=True) 1201 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1202 else: 1203 # For data whose units are defined in a code table (i.e. classification or mask) 1204 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1205 fld = self.data.astype(np.int32).astype(str) 1206 tbl = tables.get_table(tblname,expand=True) 1207 for val in np.unique(fld): 1208 fld = np.where(fld==val,tbl[val],fld) 1209 set_auto_nans(hold_auto_nans) 1210 return fld
Unpack data grid replacing integer values with strings.
These types of fields are categorical or classifications where data values do not represent an observable or predictable physical quantity. An example of such a field would be Dominant Precipitation Type - DPTYPE
Returns
- map_keys: numpy.ndarray of string values per element.
1213 def to_bytes(self, validate: bool=True): 1214 """ 1215 Return packed GRIB2 message in bytes format. 1216 1217 This will be useful for exporting data in non-file formats. For example, 1218 can be used to output grib data directly to S3 using the boto3 client 1219 without the need to write a temporary file to upload first. 1220 1221 Parameters 1222 ---------- 1223 validate: default=True 1224 If `True` (DEFAULT), validates first/last four bytes for proper 1225 formatting, else returns None. If `False`, message is output as is. 1226 1227 Returns 1228 ------- 1229 to_bytes 1230 Returns GRIB2 formatted message as bytes. 1231 """ 1232 if hasattr(self,'_msg'): 1233 if validate: 1234 if self.validate(): 1235 return self._msg 1236 else: 1237 return None 1238 else: 1239 return self._msg 1240 else: 1241 return None
Return packed GRIB2 message in bytes format.
This will be useful for exporting data in non-file formats. For example, can be used to output grib data directly to S3 using the boto3 client without the need to write a temporary file to upload first.
Parameters
- validate (default=True):
If
True
(DEFAULT), validates first/last four bytes for proper formatting, else returns None. IfFalse
, message is output as is.
Returns
- to_bytes: Returns GRIB2 formatted message as bytes.
1244 def interpolate(self, method, grid_def_out, method_options=None, drtn=None, 1245 num_threads=1): 1246 """ 1247 Grib2Message Interpolator 1248 1249 Performs spatial interpolation via the [NCEPLIBS-ip 1250 library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate 1251 method only supports scalar interpolation. If you need to perform 1252 vector interpolation, use the module-level `grib2io.interpolate` 1253 function. 1254 1255 Parameters 1256 ---------- 1257 method 1258 Interpolate method to use. This can either be an integer or string 1259 using the following mapping: 1260 1261 | Interpolate Scheme | Integer Value | 1262 | :---: | :---: | 1263 | 'bilinear' | 0 | 1264 | 'bicubic' | 1 | 1265 | 'neighbor' | 2 | 1266 | 'budget' | 3 | 1267 | 'spectral' | 4 | 1268 | 'neighbor-budget' | 6 | 1269 1270 grid_def_out : grib2io.Grib2GridDef 1271 Grib2GridDef object of the output grid. 1272 method_options : list of ints, optional 1273 Interpolation options. See the NCEPLIBS-ip documentation for 1274 more information on how these are used. 1275 drtn 1276 Data Representation Template to be used for the returned 1277 interpolated GRIB2 message. When `None`, the data representation 1278 template of the source GRIB2 message is used. Once again, it is the 1279 user's responsibility to properly set the Data Representation 1280 Template attributes. 1281 num_threads : int, optional 1282 Number of OpenMP threads to use for interpolation. The default 1283 value is 1. If grib2io_interp was not built with OpenMP, then 1284 this keyword argument and value will have no impact. 1285 1286 Returns 1287 ------- 1288 interpolate 1289 If interpolating to a grid, a new Grib2Message object is returned. 1290 The GRIB2 metadata of the new Grib2Message object is identical to 1291 the input except where required to be different because of the new 1292 grid specs and possibly a new data representation template. 1293 1294 If interpolating to station points, the interpolated data values are 1295 returned as a numpy.ndarray. 1296 """ 1297 section0 = self.section0 1298 section0[-1] = 0 1299 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1300 section3 = np.concatenate((gds,grid_def_out.gdt)) 1301 drtn = self.drtn if drtn is None else drtn 1302 1303 msg = Grib2Message(section0,self.section1,self.section2,section3, 1304 self.section4,None,self.bitMapFlag.value,drtn=drtn) 1305 1306 msg._msgnum = -1 1307 msg._deflist = self._deflist 1308 msg._coordlist = self._coordlist 1309 shape = (msg.ny,msg.nx) 1310 ndim = 2 1311 if msg.typeOfValues == 0: 1312 dtype = 'float32' 1313 elif msg.typeOfValues == 1: 1314 dtype = 'int32' 1315 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1316 method_options=method_options).reshape(msg.ny,msg.nx) 1317 msg.section5[0] = grid_def_out.npoints 1318 return msg
Grib2Message Interpolator
Performs spatial interpolation via the NCEPLIBS-ip
library. This interpolate
method only supports scalar interpolation. If you need to perform
vector interpolation, use the module-level grib2io.interpolate
function.
Parameters
- method: Interpolate method to use. This can either be an integer or string using the following mapping:
Interpolate Scheme | Integer Value |
---|---|
'bilinear' | 0 |
'bicubic' | 1 |
'neighbor' | 2 |
'budget' | 3 |
'spectral' | 4 |
'neighbor-budget' | 6 |
- grid_def_out (grib2io.Grib2GridDef): Grib2GridDef object of the output grid.
- method_options (list of ints, optional): Interpolation options. See the NCEPLIBS-ip documentation for more information on how these are used.
- drtn: Data Representation Template to be used for the returned
interpolated GRIB2 message. When
None
, the data representation template of the source GRIB2 message is used. Once again, it is the user's responsibility to properly set the Data Representation Template attributes. - num_threads (int, optional): Number of OpenMP threads to use for interpolation. The default value is 1. If grib2io_interp was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
- interpolate: If interpolating to a grid, a new Grib2Message object is returned. The GRIB2 metadata of the new Grib2Message object is identical to the input except where required to be different because of the new grid specs and possibly a new data representation template.
If interpolating to station points, the interpolated data values are returned as a numpy.ndarray.
1321 def validate(self): 1322 """ 1323 Validate a complete GRIB2 message. 1324 1325 The g2c library does its own internal validation when g2_gribend() is called, but 1326 we will check in grib2io also. The validation checks if the first 4 bytes in 1327 self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in 1328 section 0 equals the length of the packed message. 1329 1330 Returns 1331 ------- 1332 `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise. 1333 """ 1334 valid = False 1335 if hasattr(self,'_msg'): 1336 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1337 if self.section0[-1] == len(self._msg): 1338 valid = True 1339 return valid
Validate a complete GRIB2 message.
The g2c library does its own internal validation when g2_gribend() is called, but we will check in grib2io also. The validation checks if the first 4 bytes in self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in section 0 equals the length of the packed message.
Returns
True
if the packed GRIB2 message is complete and well-formed,False
otherwise.
1791@dataclass 1792class Grib2GridDef: 1793 """ 1794 Class for Grid Definition Template Number and Template as attributes. 1795 1796 This allows for cleaner looking code when passing these metadata around. 1797 For example, the `grib2io._Grib2Message.interpolate` method and 1798 `grib2io.interpolate` function accepts these objects. 1799 """ 1800 gdtn: int 1801 gdt: NDArray 1802 1803 @classmethod 1804 def from_section3(cls, section3): 1805 return cls(section3[4],section3[5:]) 1806 1807 @property 1808 def nx(self): 1809 return self.gdt[7] 1810 1811 @property 1812 def ny(self): 1813 return self.gdt[8] 1814 1815 @property 1816 def npoints(self): 1817 return self.gdt[7] * self.gdt[8] 1818 1819 @property 1820 def shape(self): 1821 return (self.ny, self.nx)
Class for Grid Definition Template Number and Template as attributes.
This allows for cleaner looking code when passing these metadata around.
For example, the grib2io._Grib2Message.interpolate
method and
grib2io.interpolate
function accepts these objects.