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 5try: 6 from . import __config__ 7 __version__ = __config__.grib2io_version 8except(ImportError): 9 pass 10 11__all__ = ['open','Grib2Message','_Grib2Message','show_config','interpolate', 12 'interpolate_to_stations','tables','templates','utils','Grib2GridDef'] 13 14from .g2clib import __version__ as __g2clib_version__ 15from .g2clib import _has_jpeg 16from .g2clib import _has_png 17from .g2clib import _has_aec 18 19has_jpeg_support = bool(_has_jpeg) 20has_png_support = bool(_has_png) 21has_aec_support = bool(_has_aec) 22 23def show_config(): 24 """Print grib2io build configuration information.""" 25 print(f'grib2io version {__version__} Configuration:\n') 26 print(f'\tg2c library version: {__g2clib_version__}') 27 print(f'\tJPEG compression support: {has_jpeg_support}') 28 print(f'\tPNG compression support: {has_png_support}') 29 print(f'\tAEC compression support: {has_aec_support}')
60class open(): 61 """ 62 GRIB2 File Object. 63 64 A physical file can contain one or more GRIB2 messages. When instantiated, class `grib2io.open`, 65 the file named `filename` is opened for reading (`mode = 'r'`) and is automatically indexed. 66 The indexing procedure reads some of the GRIB2 metadata for all GRIB2 Messages. A GRIB2 Message 67 may contain submessages whereby Section 2-7 can be repeated. grib2io accommodates for this by 68 flattening any GRIB2 submessages into multiple individual messages. 69 70 Attributes 71 ---------- 72 **`mode : str`** 73 File IO mode of opening the file. 74 75 **`name : str`** 76 Full path name of the GRIB2 file. 77 78 **`messages : int`** 79 Count of GRIB2 Messages contained in the file. 80 81 **`current_message : int`** 82 Current position of the file in units of GRIB2 Messages. 83 84 **`size : int`** 85 Size of the file in units of bytes. 86 87 **`closed : bool`** 88 `True` is file handle is close; `False` otherwise. 89 90 **`variables : tuple`** 91 Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names). 92 93 **`levels : tuple`** 94 Tuple containing a unique list of wgrib2-formatted level/layer strings. 95 """ 96 __slots__ = ('_filehandle','_hasindex','_index','mode','name','messages', 97 'current_message','size','closed','variables','levels','_pos') 98 def __init__(self, filename, mode='r', **kwargs): 99 """ 100 Initialize GRIB2 File object instance. 101 102 Parameters 103 ---------- 104 105 **`filename : str`** 106 File name containing GRIB2 messages. 107 108 **`mode : str, optional`** 109 File access mode where `r` opens the files for reading only; `w` opens the file for writing. 110 """ 111 if mode in {'a','r','w'}: 112 mode = mode+'b' 113 if 'w' in mode: mode += '+' 114 if 'a' in mode: mode += '+' 115 self._filehandle = builtins.open(filename,mode=mode,buffering=ONE_MB) 116 self._hasindex = False 117 self._index = {} 118 self.mode = mode 119 self.name = os.path.abspath(filename) 120 self.messages = 0 121 self.current_message = 0 122 self.size = os.path.getsize(self.name) 123 self.closed = self._filehandle.closed 124 self.levels = None 125 self.variables = None 126 if 'r' in self.mode: 127 try: 128 self._build_index(no_data=kwargs['_xarray_backend']) 129 except(KeyError): 130 self._build_index() 131 # FIX: Cannot perform reads on mode='a' 132 #if 'a' in self.mode and self.size > 0: self._build_index() 133 134 135 def __delete__(self, instance): 136 self.close() 137 del self._index 138 139 140 def __enter__(self): 141 return self 142 143 144 def __exit__(self, atype, value, traceback): 145 self.close() 146 147 148 def __iter__(self): 149 yield from self._index['msg'] 150 151 152 def __len__(self): 153 return self.messages 154 155 156 def __repr__(self): 157 strings = [] 158 for k in self.__slots__: 159 if k.startswith('_'): continue 160 strings.append('%s = %s\n'%(k,eval('self.'+k))) 161 return ''.join(strings) 162 163 164 def __getitem__(self, key): 165 if isinstance(key,int): 166 if abs(key) >= len(self._index['msg']): 167 raise IndexError("index out of range") 168 else: 169 return self._index['msg'][key] 170 elif isinstance(key,str): 171 return self.select(shortName=key) 172 elif isinstance(key,slice): 173 return self._index['msg'][key] 174 else: 175 raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.') 176 177 178 def _build_index(self, no_data=False): 179 """ 180 Perform indexing of GRIB2 Messages. 181 """ 182 # Initialize index dictionary 183 if not self._hasindex: 184 self._index['offset'] = [] 185 self._index['bitmap_offset'] = [] 186 self._index['data_offset'] = [] 187 self._index['size'] = [] 188 self._index['data_size'] = [] 189 self._index['submessageOffset'] = [] 190 self._index['submessageBeginSection'] = [] 191 self._index['isSubmessage'] = [] 192 self._index['messageNumber'] = [] 193 self._index['msg'] = [] 194 self._hasindex = True 195 196 # Iterate 197 while True: 198 try: 199 # Read first 4 bytes and decode...looking for "GRIB" 200 pos = self._filehandle.tell() 201 header = struct.unpack('>i',self._filehandle.read(4))[0] 202 203 # Test header. Then get information from GRIB2 Section 0: the discipline 204 # number, edition number (should always be 2), and GRIB2 message size. 205 # Then iterate to check for submessages. 206 if header.to_bytes(4,'big') == b'GRIB': 207 208 _issubmessage = False 209 _submsgoffset = 0 210 _submsgbegin = 0 211 _bmapflag = None 212 213 # Read the rest of Section 0 using struct. 214 section0 = np.concatenate(([header],list(struct.unpack('>HBBQ',self._filehandle.read(12)))),dtype=np.int64) 215 assert section0[3] == 2 216 217 # Read and unpack Section 1 218 secsize = struct.unpack('>i',self._filehandle.read(4))[0] 219 secnum = struct.unpack('>B',self._filehandle.read(1))[0] 220 assert secnum == 1 221 self._filehandle.seek(self._filehandle.tell()-5) 222 _grbmsg = self._filehandle.read(secsize) 223 _grbpos = 0 224 section1,_grbpos = g2clib.unpack1(_grbmsg,_grbpos,np.empty) 225 secrange = range(2,8) 226 while 1: 227 section2 = b'' 228 for num in secrange: 229 secsize = struct.unpack('>i',self._filehandle.read(4))[0] 230 secnum = struct.unpack('>B',self._filehandle.read(1))[0] 231 if secnum == num: 232 if secnum == 2: 233 if secsize > 0: 234 section2 = self._filehandle.read(secsize-5) 235 elif secnum == 3: 236 self._filehandle.seek(self._filehandle.tell()-5) 237 _grbmsg = self._filehandle.read(secsize) 238 _grbpos = 0 239 # Unpack Section 3 240 _gds,_gdt,_deflist,_grbpos = g2clib.unpack3(_grbmsg,_grbpos,np.empty) 241 _gds = _gds.tolist() 242 _gdt = _gdt.tolist() 243 section3 = np.concatenate((_gds,_gdt)) 244 section3 = np.where(section3==4294967295,-1,section3) 245 elif secnum == 4: 246 self._filehandle.seek(self._filehandle.tell()-5) 247 _grbmsg = self._filehandle.read(secsize) 248 _grbpos = 0 249 # Unpack Section 4 250 _numcoord,_pdt,_pdtnum,_coordlist,_grbpos = g2clib.unpack4(_grbmsg,_grbpos,np.empty) 251 _pdt = _pdt.tolist() 252 section4 = np.concatenate((np.array((_numcoord,_pdtnum)),_pdt)) 253 elif secnum == 5: 254 self._filehandle.seek(self._filehandle.tell()-5) 255 _grbmsg = self._filehandle.read(secsize) 256 _grbpos = 0 257 # Unpack Section 5 258 _drt,_drtn,_npts,self._pos = g2clib.unpack5(_grbmsg,_grbpos,np.empty) 259 section5 = np.concatenate((np.array((_npts,_drtn)),_drt)) 260 section5 = np.where(section5==4294967295,-1,section5) 261 elif secnum == 6: 262 # Unpack Section 6. Not really...just get the flag value. 263 _bmapflag = struct.unpack('>B',self._filehandle.read(1))[0] 264 if _bmapflag == 0: 265 _bmappos = self._filehandle.tell()-6 266 elif _bmapflag == 254: 267 pass # Do this to keep the previous position value 268 else: 269 _bmappos = None 270 self._filehandle.seek(self._filehandle.tell()+secsize-6) 271 elif secnum == 7: 272 # Unpack Section 7. No need to read it, just index the position in file. 273 _datapos = self._filehandle.tell()-5 274 _datasize = secsize 275 self._filehandle.seek(self._filehandle.tell()+secsize-5) 276 else: 277 self._filehandle.seek(self._filehandle.tell()+secsize-5) 278 else: 279 if num == 2 and secnum == 3: 280 pass # Allow this. Just means no Local Use Section. 281 else: 282 _issubmessage = True 283 _submsgoffset = (self._filehandle.tell()-5)-(self._index['offset'][-1]) 284 _submsgbegin = secnum 285 self._filehandle.seek(self._filehandle.tell()-5) 286 continue 287 trailer = struct.unpack('>4s',self._filehandle.read(4))[0] 288 if trailer == b'7777': 289 self.messages += 1 290 self._index['offset'].append(pos) 291 self._index['bitmap_offset'].append(_bmappos) 292 self._index['data_offset'].append(_datapos) 293 self._index['size'].append(section0[-1]) 294 self._index['data_size'].append(_datasize) 295 self._index['messageNumber'].append(self.messages) 296 self._index['isSubmessage'].append(_issubmessage) 297 if _issubmessage: 298 self._index['submessageOffset'].append(_submsgoffset) 299 self._index['submessageBeginSection'].append(_submsgbegin) 300 else: 301 self._index['submessageOffset'].append(0) 302 self._index['submessageBeginSection'].append(_submsgbegin) 303 304 # Create Grib2Message with data. 305 msg = Grib2Message(section0,section1,section2,section3,section4,section5,_bmapflag) 306 msg._msgnum = self.messages-1 307 msg._deflist = _deflist 308 msg._coordlist = _coordlist 309 if not no_data: 310 msg._data = Grib2MessageOnDiskArray((msg.ny,msg.nx), 2, 311 TYPE_OF_VALUES_DTYPE[msg.typeOfValues], 312 self._filehandle, 313 msg, pos, _bmappos, _datapos) 314 self._index['msg'].append(msg) 315 316 break 317 else: 318 self._filehandle.seek(self._filehandle.tell()-4) 319 self.messages += 1 320 self._index['offset'].append(pos) 321 self._index['bitmap_offset'].append(_bmappos) 322 self._index['data_offset'].append(_datapos) 323 self._index['size'].append(section0[-1]) 324 self._index['data_size'].append(_datasize) 325 self._index['messageNumber'].append(self.messages) 326 self._index['isSubmessage'].append(_issubmessage) 327 self._index['submessageOffset'].append(_submsgoffset) 328 self._index['submessageBeginSection'].append(_submsgbegin) 329 330 # Create Grib2Message with data. 331 msg = Grib2Message(section0,section1,section2,section3,section4,section5,_bmapflag) 332 msg._msgnum = self.messages-1 333 msg._deflist = _deflist 334 msg._coordlist = _coordlist 335 if not no_data: 336 msg._data = Grib2MessageOnDiskArray((msg.ny,msg.nx), 2, 337 TYPE_OF_VALUES_DTYPE[msg.typeOfValues], 338 self._filehandle, 339 msg, pos, _bmappos, _datapos) 340 self._index['msg'].append(msg) 341 342 continue 343 344 except(struct.error): 345 if 'r' in self.mode: 346 self._filehandle.seek(0) 347 break 348 349 # Index at end of _build_index() 350 if self._hasindex and not no_data: 351 self.variables = tuple(sorted(set([msg.shortName for msg in self._index['msg']]))) 352 self.levels = tuple(sorted(set([msg.level for msg in self._index['msg']]))) 353 354 355 def close(self): 356 """ 357 Close the file handle 358 """ 359 if not self._filehandle.closed: 360 self.messages = 0 361 self.current_message = 0 362 self._filehandle.close() 363 self.closed = self._filehandle.closed 364 365 366 def read(self, size=None): 367 """ 368 Read size amount of GRIB2 messages from the current position. 369 370 If no argument is given, then size is None and all messages are returned from 371 the current position in the file. This read method follows the behavior of 372 Python's builtin open() function, but whereas that operates on units of bytes, 373 we operate on units of GRIB2 messages. 374 375 Parameters 376 ---------- 377 **`size : int, optional`** 378 The number of GRIB2 messages to read from the current position. If no argument is 379 give, the default value is `None` and remainder of the file is read. 380 381 Returns 382 ------- 383 `Grib2Message` object when size = 1 or a `list` of Grib2Messages when size > 1. 384 """ 385 if size is not None and size < 0: 386 size = None 387 if size is None or size > 1: 388 start = self.tell() 389 stop = self.messages if size is None else start+size 390 if size is None: 391 self.current_message = self.messages-1 392 else: 393 self.current_message += size 394 return self._index['msg'][slice(start,stop,1)] 395 elif size == 1: 396 self.current_message += 1 397 return self._index['msg'][self.current_message] 398 else: 399 None 400 401 402 def seek(self, pos): 403 """ 404 Set the position within the file in units of GRIB2 messages. 405 406 Parameters 407 ---------- 408 **`pos : int`** 409 The GRIB2 Message number to set the file pointer to. 410 """ 411 if self._hasindex: 412 self._filehandle.seek(self._index['offset'][pos]) 413 self.current_message = pos 414 415 416 def tell(self): 417 """ 418 Returns the position of the file in units of GRIB2 Messages. 419 """ 420 return self.current_message 421 422 423 def select(self, **kwargs): 424 """ 425 Select GRIB2 messages by `Grib2Message` attributes. 426 """ 427 # TODO: Added ability to process multiple values for each keyword (attribute) 428 idxs = [] 429 nkeys = len(kwargs.keys()) 430 for k,v in kwargs.items(): 431 for m in self._index['msg']: 432 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 433 idxs = np.array(idxs,dtype=np.int32) 434 return [self._index['msg'][i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]] 435 436 437 def write(self, msg): 438 """ 439 Writes GRIB2 message object to file. 440 441 Parameters 442 ---------- 443 **`msg : Grib2Message or sequence of Grib2Messages`** 444 GRIB2 message objects to write to file. 445 """ 446 if isinstance(msg,list): 447 for m in msg: 448 self.write(m) 449 return 450 451 if issubclass(msg.__class__,_Grib2Message): 452 if hasattr(msg,'_msg'): 453 self._filehandle.write(msg._msg) 454 else: 455 if msg._signature != msg._generate_signature(): 456 msg.pack() 457 self._filehandle.write(msg._msg) 458 else: 459 if hasattr(msg._data,'filehandle'): 460 msg._data.filehandle.seek(msg._data.offset) 461 self._filehandle.write(msg._data.filehandle.read(msg.section0[-1])) 462 else: 463 msg.pack() 464 self._filehandle.write(msg._msg) 465 self.flush() 466 self.size = os.path.getsize(self.name) 467 self._filehandle.seek(self.size-msg.section0[-1]) 468 self._build_index() 469 else: 470 raise TypeError("msg must be a Grib2Message object.") 471 return 472 473 474 def flush(self): 475 """ 476 Flush the file object buffer. 477 """ 478 self._filehandle.flush() 479 480 481 def levels_by_var(self, name): 482 """ 483 Return a list of level strings given a variable shortName. 484 485 Parameters 486 ---------- 487 **`name : str`** 488 Grib2Message variable shortName 489 490 Returns 491 ------- 492 A list of strings of unique level strings. 493 """ 494 return list(sorted(set([msg.level for msg in self.select(shortName=name)]))) 495 496 497 def vars_by_level(self, level): 498 """ 499 Return a list of variable shortName strings given a level. 500 501 Parameters 502 ---------- 503 **`level : str`** 504 Grib2Message variable level 505 506 Returns 507 ------- 508 A list of strings of variable shortName strings. 509 """ 510 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.
Attributes
mode : str
File IO mode of opening the file.
name : str
Full path name of the GRIB2 file.
messages : int
Count of GRIB2 Messages contained in the file.
current_message : int
Current position of the file in units of GRIB2 Messages.
size : int
Size of the file in units of bytes.
closed : bool
True
is file handle is close; False
otherwise.
variables : tuple
Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
levels : tuple
Tuple containing a unique list of wgrib2-formatted level/layer strings.
98 def __init__(self, filename, mode='r', **kwargs): 99 """ 100 Initialize GRIB2 File object instance. 101 102 Parameters 103 ---------- 104 105 **`filename : str`** 106 File name containing GRIB2 messages. 107 108 **`mode : str, optional`** 109 File access mode where `r` opens the files for reading only; `w` opens the file for writing. 110 """ 111 if mode in {'a','r','w'}: 112 mode = mode+'b' 113 if 'w' in mode: mode += '+' 114 if 'a' in mode: mode += '+' 115 self._filehandle = builtins.open(filename,mode=mode,buffering=ONE_MB) 116 self._hasindex = False 117 self._index = {} 118 self.mode = mode 119 self.name = os.path.abspath(filename) 120 self.messages = 0 121 self.current_message = 0 122 self.size = os.path.getsize(self.name) 123 self.closed = self._filehandle.closed 124 self.levels = None 125 self.variables = None 126 if 'r' in self.mode: 127 try: 128 self._build_index(no_data=kwargs['_xarray_backend']) 129 except(KeyError): 130 self._build_index() 131 # FIX: Cannot perform reads on mode='a' 132 #if 'a' in self.mode and self.size > 0: self._build_index()
Initialize GRIB2 File object instance.
Parameters
filename : str
File name containing GRIB2 messages.
mode : str, optional
File access mode where r
opens the files for reading only; w
opens the file for writing.
355 def close(self): 356 """ 357 Close the file handle 358 """ 359 if not self._filehandle.closed: 360 self.messages = 0 361 self.current_message = 0 362 self._filehandle.close() 363 self.closed = self._filehandle.closed
Close the file handle
366 def read(self, size=None): 367 """ 368 Read size amount of GRIB2 messages from the current position. 369 370 If no argument is given, then size is None and all messages are returned from 371 the current position in the file. This read method follows the behavior of 372 Python's builtin open() function, but whereas that operates on units of bytes, 373 we operate on units of GRIB2 messages. 374 375 Parameters 376 ---------- 377 **`size : int, optional`** 378 The number of GRIB2 messages to read from the current position. If no argument is 379 give, the default value is `None` and remainder of the file is read. 380 381 Returns 382 ------- 383 `Grib2Message` object when size = 1 or a `list` of Grib2Messages when size > 1. 384 """ 385 if size is not None and size < 0: 386 size = None 387 if size is None or size > 1: 388 start = self.tell() 389 stop = self.messages if size is None else start+size 390 if size is None: 391 self.current_message = self.messages-1 392 else: 393 self.current_message += size 394 return self._index['msg'][slice(start,stop,1)] 395 elif size == 1: 396 self.current_message += 1 397 return self._index['msg'][self.current_message] 398 else: 399 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 : int, optional
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
Grib2Message
object when size = 1 or a list
of Grib2Messages when size > 1.
402 def seek(self, pos): 403 """ 404 Set the position within the file in units of GRIB2 messages. 405 406 Parameters 407 ---------- 408 **`pos : int`** 409 The GRIB2 Message number to set the file pointer to. 410 """ 411 if self._hasindex: 412 self._filehandle.seek(self._index['offset'][pos]) 413 self.current_message = pos
Set the position within the file in units of GRIB2 messages.
Parameters
pos : int
The GRIB2 Message number to set the file pointer to.
416 def tell(self): 417 """ 418 Returns the position of the file in units of GRIB2 Messages. 419 """ 420 return self.current_message
Returns the position of the file in units of GRIB2 Messages.
423 def select(self, **kwargs): 424 """ 425 Select GRIB2 messages by `Grib2Message` attributes. 426 """ 427 # TODO: Added ability to process multiple values for each keyword (attribute) 428 idxs = [] 429 nkeys = len(kwargs.keys()) 430 for k,v in kwargs.items(): 431 for m in self._index['msg']: 432 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 433 idxs = np.array(idxs,dtype=np.int32) 434 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.
437 def write(self, msg): 438 """ 439 Writes GRIB2 message object to file. 440 441 Parameters 442 ---------- 443 **`msg : Grib2Message or sequence of Grib2Messages`** 444 GRIB2 message objects to write to file. 445 """ 446 if isinstance(msg,list): 447 for m in msg: 448 self.write(m) 449 return 450 451 if issubclass(msg.__class__,_Grib2Message): 452 if hasattr(msg,'_msg'): 453 self._filehandle.write(msg._msg) 454 else: 455 if msg._signature != msg._generate_signature(): 456 msg.pack() 457 self._filehandle.write(msg._msg) 458 else: 459 if hasattr(msg._data,'filehandle'): 460 msg._data.filehandle.seek(msg._data.offset) 461 self._filehandle.write(msg._data.filehandle.read(msg.section0[-1])) 462 else: 463 msg.pack() 464 self._filehandle.write(msg._msg) 465 self.flush() 466 self.size = os.path.getsize(self.name) 467 self._filehandle.seek(self.size-msg.section0[-1]) 468 self._build_index() 469 else: 470 raise TypeError("msg must be a Grib2Message object.") 471 return
Writes GRIB2 message object to file.
Parameters
msg : Grib2Message or sequence of Grib2Messages
GRIB2 message objects to write to file.
481 def levels_by_var(self, name): 482 """ 483 Return a list of level strings given a variable shortName. 484 485 Parameters 486 ---------- 487 **`name : str`** 488 Grib2Message variable shortName 489 490 Returns 491 ------- 492 A list of strings of unique level strings. 493 """ 494 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 : str
Grib2Message variable shortName
Returns
A list of strings of unique level strings.
497 def vars_by_level(self, level): 498 """ 499 Return a list of variable shortName strings given a level. 500 501 Parameters 502 ---------- 503 **`level : str`** 504 Grib2Message variable level 505 506 Returns 507 ------- 508 A list of strings of variable shortName strings. 509 """ 510 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 : str
Grib2Message variable level
Returns
A list of strings of variable shortName strings.
513class Grib2Message: 514 """ 515 Creation class for a GRIB2 message. 516 """ 517 def __new__(self, section0: np.array = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]), 518 section1: np.array = np.zeros((13),dtype=np.int64), 519 section2: bytes = None, 520 section3: np.array = None, 521 section4: np.array = None, 522 section5: np.array = None, *args, **kwargs): 523 524 if np.all(section1==0): 525 section1[5:11] = datetime.datetime.utcfromtimestamp(0).timetuple()[:6] 526 527 bases = list() 528 if section3 is None: 529 if 'gdtn' in kwargs.keys(): 530 gdtn = kwargs['gdtn'] 531 Gdt = templates.gdt_class_by_gdtn(gdtn) 532 bases.append(Gdt) 533 section3 = np.zeros((Gdt._len+5),dtype=np.int64) 534 section3[4] = gdtn 535 else: 536 raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array") 537 else: 538 gdtn = section3[4] 539 Gdt = templates.gdt_class_by_gdtn(gdtn) 540 bases.append(Gdt) 541 542 if section4 is None: 543 if 'pdtn' in kwargs.keys(): 544 pdtn = kwargs['pdtn'] 545 Pdt = templates.pdt_class_by_pdtn(pdtn) 546 bases.append(Pdt) 547 section4 = np.zeros((Pdt._len+2),dtype=np.int64) 548 section4[1] = pdtn 549 else: 550 raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array") 551 else: 552 pdtn = section4[1] 553 Pdt = templates.pdt_class_by_pdtn(pdtn) 554 bases.append(Pdt) 555 556 if section5 is None: 557 if 'drtn' in kwargs.keys(): 558 drtn = kwargs['drtn'] 559 Drt = templates.drt_class_by_drtn(drtn) 560 bases.append(Drt) 561 section5 = np.zeros((Drt._len+2),dtype=np.int64) 562 section5[1] = drtn 563 else: 564 raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array") 565 else: 566 drtn = section5[1] 567 Drt = templates.drt_class_by_drtn(drtn) 568 bases.append(Drt) 569 570 # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo 571 try: 572 Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] 573 except KeyError: 574 @dataclass(init=False, repr=False) 575 class Msg(_Grib2Message, *bases): 576 pass 577 _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg 578 579 return Msg(section0, section1, section2, section3, section4, section5, *args)
Creation class for a GRIB2 message.
582@dataclass 583class _Grib2Message: 584 """GRIB2 Message base class""" 585 # GRIB2 Sections 586 section0: np.array = field(init=True,repr=False) 587 section1: np.array = field(init=True,repr=False) 588 section2: bytes = field(init=True,repr=False) 589 section3: np.array = field(init=True,repr=False) 590 section4: np.array = field(init=True,repr=False) 591 section5: np.array = field(init=True,repr=False) 592 bitMapFlag: templates.Grib2Metadata = field(init=True,repr=False,default=255) 593 594 # Section 0 looked up attributes 595 indicatorSection: np.array = field(init=False,repr=False,default=templates.IndicatorSection()) 596 discipline: templates.Grib2Metadata = field(init=False,repr=False,default=templates.Discipline()) 597 598 # Section 1 looked up attributes 599 identificationSection: np.array = field(init=False,repr=False,default=templates.IdentificationSection()) 600 originatingCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingCenter()) 601 originatingSubCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingSubCenter()) 602 masterTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.MasterTableInfo()) 603 localTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.LocalTableInfo()) 604 significanceOfReferenceTime: templates.Grib2Metadata = field(init=False,repr=False,default=templates.SignificanceOfReferenceTime()) 605 year: int = field(init=False,repr=False,default=templates.Year()) 606 month: int = field(init=False,repr=False,default=templates.Month()) 607 day: int = field(init=False,repr=False,default=templates.Day()) 608 hour: int = field(init=False,repr=False,default=templates.Hour()) 609 minute: int = field(init=False,repr=False,default=templates.Minute()) 610 second: int = field(init=False,repr=False,default=templates.Second()) 611 refDate: datetime.datetime = field(init=False,repr=False,default=templates.RefDate()) 612 productionStatus: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductionStatus()) 613 typeOfData: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfData()) 614 615 @property 616 def _isNDFD(self): 617 """Check if GRIB2 message is from NWS NDFD""" 618 return np.all(self.section1[0:2]==[8,65535]) 619 620 # Section 3 looked up common attributes. Other looked up attributes are available according 621 # to the Grid Definition Template. 622 gridDefinitionSection: np.array = field(init=False,repr=False,default=templates.GridDefinitionSection()) 623 sourceOfGridDefinition: int = field(init=False,repr=False,default=templates.SourceOfGridDefinition()) 624 numberOfDataPoints: int = field(init=False,repr=False,default=templates.NumberOfDataPoints()) 625 interpretationOfListOfNumbers: templates.Grib2Metadata = field(init=False,repr=False,default=templates.InterpretationOfListOfNumbers()) 626 gridDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.GridDefinitionTemplateNumber()) 627 gridDefinitionTemplate: list = field(init=False,repr=False,default=templates.GridDefinitionTemplate()) 628 _earthparams: dict = field(init=False,repr=False,default=templates.EarthParams()) 629 _dxsign: float = field(init=False,repr=False,default=templates.DxSign()) 630 _dysign: float = field(init=False,repr=False,default=templates.DySign()) 631 _llscalefactor: float = field(init=False,repr=False,default=templates.LLScaleFactor()) 632 _lldivisor: float = field(init=False,repr=False,default=templates.LLDivisor()) 633 _xydivisor: float = field(init=False,repr=False,default=templates.XYDivisor()) 634 shapeOfEarth: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ShapeOfEarth()) 635 earthShape: str = field(init=False,repr=False,default=templates.EarthShape()) 636 earthRadius: float = field(init=False,repr=False,default=templates.EarthRadius()) 637 earthMajorAxis: float = field(init=False,repr=False,default=templates.EarthMajorAxis()) 638 earthMinorAxis: float = field(init=False,repr=False,default=templates.EarthMinorAxis()) 639 resolutionAndComponentFlags: list = field(init=False,repr=False,default=templates.ResolutionAndComponentFlags()) 640 ny: int = field(init=False,repr=False,default=templates.Ny()) 641 nx: int = field(init=False,repr=False,default=templates.Nx()) 642 scanModeFlags: list = field(init=False,repr=False,default=templates.ScanModeFlags()) 643 projParameters: dict = field(init=False,repr=False,default=templates.ProjParameters()) 644 645 # Section 4 attributes. Listed here are "extra" or "helper" attrs that use metadata from 646 # the given PDT, but not a formal part of the PDT. 647 productDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductDefinitionTemplateNumber()) 648 productDefinitionTemplate: np.array = field(init=False,repr=False,default=templates.ProductDefinitionTemplate()) 649 _varinfo: list = field(init=False, repr=False, default=templates.VarInfo()) 650 _fixedsfc1info: list = field(init=False, repr=False, default=templates.FixedSfc1Info()) 651 _fixedsfc2info: list = field(init=False, repr=False, default=templates.FixedSfc2Info()) 652 fullName: str = field(init=False, repr=False, default=templates.FullName()) 653 units: str = field(init=False, repr=False, default=templates.Units()) 654 shortName: str = field(init=False, repr=False, default=templates.ShortName()) 655 leadTime: datetime.timedelta = field(init=False,repr=False,default=templates.LeadTime()) 656 unitOfFirstFixedSurface: str = field(init=False,repr=False,default=templates.UnitOfFirstFixedSurface()) 657 valueOfFirstFixedSurface: int = field(init=False,repr=False,default=templates.ValueOfFirstFixedSurface()) 658 unitOfSecondFixedSurface: str = field(init=False,repr=False,default=templates.UnitOfSecondFixedSurface()) 659 valueOfSecondFixedSurface: int = field(init=False,repr=False,default=templates.ValueOfSecondFixedSurface()) 660 level: str = field(init=False, repr=False, default=templates.Level()) 661 duration: datetime.timedelta = field(init=False,repr=False,default=templates.Duration()) 662 validDate: datetime.datetime = field(init=False,repr=False,default=templates.ValidDate()) 663 664 # Section 5 looked up common attributes. Other looked up attributes are available according 665 # to the Data Representation Template. 666 numberOfPackedValues: int = field(init=False,repr=False,default=templates.NumberOfPackedValues()) 667 dataRepresentationTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.DataRepresentationTemplateNumber()) 668 dataRepresentationTemplate: list = field(init=False,repr=False,default=templates.DataRepresentationTemplate()) 669 typeOfValues: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfValues()) 670 671 672 def __post_init__(self): 673 """Set some attributes after init""" 674 self._msgnum = -1 675 self._deflist = None 676 self._coordlist = None 677 self._signature = self._generate_signature() 678 try: 679 self._sha1_section3 = hashlib.sha1(self.section3).hexdigest() 680 except(TypeError): 681 pass 682 self.bitMapFlag = templates.Grib2Metadata(self.bitMapFlag,table='6.0') 683 684 685 @property 686 def gdtn(self): 687 """Return Grid Definition Template Number""" 688 return self.section3[4] 689 690 691 @property 692 def gdt(self): 693 """Return Grid Definition Template""" 694 return self.gridDefinitionTemplate 695 696 697 @property 698 def pdtn(self): 699 """Return Product Definition Template Number""" 700 return self.section4[1] 701 702 703 @property 704 def pdt(self): 705 """Return Product Definition Template""" 706 return self.productDefinitionTemplate 707 708 709 @property 710 def drtn(self): 711 """Return Data Representation Template Number""" 712 return self.section5[1] 713 714 715 @property 716 def drt(self): 717 """Return Data Representation Template""" 718 return self.dataRepresentationTemplate 719 720 721 @property 722 def pdy(self): 723 """Return the PDY ('YYYYMMDD')""" 724 return ''.join([str(i) for i in self.section1[5:8]]) 725 726 727 @property 728 def griddef(self): 729 """Return a Grib2GridDef instance for a GRIB2 message""" 730 return Grib2GridDef.from_section3(self.section3) 731 732 733 def __repr__(self): 734 info = '' 735 for sect in [0,1,3,4,5,6]: 736 for k,v in self.attrs_by_section(sect,values=True).items(): 737 info += f'Section {sect}: {k} = {v}\n' 738 return info 739 740 741 def __str__(self): 742 return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:' 743 f'{self.fullName} ({self.units}):{self.level}:' 744 f'{self.leadTime}') 745 746 747 def _generate_signature(self): 748 """Generature SHA-1 hash string from GRIB2 integer sections""" 749 return hashlib.sha1(np.concatenate((self.section0,self.section1, 750 self.section3,self.section4, 751 self.section5))).hexdigest() 752 753 754 def attrs_by_section(self, sect, values=False): 755 """ 756 Provide a tuple of attribute names for the given GRIB2 section. 757 758 Parameters 759 ---------- 760 **`sect : int`** 761 The GRIB2 section number. 762 763 **`values : bool, optional`** 764 Optional (default is `False`) arugment to return attributes values. 765 766 Returns 767 ------- 768 A List attribute names or Dict if `values = True`. 769 """ 770 if sect in {0,1,6}: 771 attrs = templates._section_attrs[sect] 772 elif sect in {3,4,5}: 773 def _find_class_index(n): 774 _key = {3:'Grid', 4:'Product', 5:'Data'} 775 for i,c in enumerate(self.__class__.__mro__): 776 if _key[n] in c.__name__: 777 return i 778 else: 779 return [] 780 if sys.version_info.minor <= 8: 781 attrs = templates._section_attrs[sect]+\ 782 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 783 else: 784 attrs = templates._section_attrs[sect]+\ 785 self.__class__.__mro__[_find_class_index(sect)]._attrs 786 else: 787 attrs = [] 788 if values: 789 return {k:getattr(self,k) for k in attrs} 790 else: 791 return attrs 792 793 794 def pack(self): 795 """ 796 Packs GRIB2 section data into a binary message. It is the user's responsibility 797 to populate the GRIB2 section information with appropriate metadata. 798 """ 799 # Create beginning of packed binary message with section 0 and 1 data. 800 self._sections = [] 801 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 802 self._sections += [0,1] 803 804 # Add section 2 if present. 805 if isinstance(self.section2,bytes) and len(self.section2) > 0: 806 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 807 self._sections.append(2) 808 809 # Add section 3. 810 self.section3[1] = self.nx * self.ny 811 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 812 self.gridDefinitionTemplate, 813 self._deflist) 814 self._sections.append(3) 815 816 # Prepare data. 817 field = np.copy(self.data) 818 if self.scanModeFlags is not None: 819 if self.scanModeFlags[3]: 820 fieldsave = field.astype('f') # Casting makes a copy 821 field[1::2,:] = fieldsave[1::2,::-1] 822 fld = field.astype('f') 823 824 # Prepare bitmap, if necessary 825 bitmapflag = self.bitMapFlag.value 826 if bitmapflag == 0: 827 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 828 else: 829 bmap = None 830 831 # Prepare optional coordinate list 832 if self._coordlist is not None: 833 crdlist = np.array(self._coordlist,'f') 834 else: 835 crdlist = None 836 837 # Prepare data for packing if nans are present 838 fld = np.ravel(fld) 839 if np.isnan(fld).any() and hasattr(self,'_missvalmap'): 840 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 841 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 842 843 # Add sections 4, 5, 6 (if present), and 7. 844 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 845 self.productDefinitionTemplate, 846 crdlist, 847 self.drtn, 848 self.dataRepresentationTemplate, 849 fld, 850 bitmapflag, 851 bmap) 852 self._sections.append(4) 853 self._sections.append(5) 854 if bmap is not None: self._sections.append(6) 855 self._sections.append(7) 856 857 # Finalize GRIB2 message with section 8. 858 self._msg, self._pos = g2clib.grib2_end(self._msg) 859 self._sections.append(8) 860 self.section0[-1] = len(self._msg) 861 862 863 @property 864 def data(self) -> np.array: 865 """ 866 Accessing the data attribute loads data into memmory 867 """ 868 if not hasattr(self,'_auto_nans'): self._auto_nans = _AUTO_NANS 869 if hasattr(self,'_data'): 870 if self._auto_nans != _AUTO_NANS: 871 self._data = self._ondiskarray 872 if isinstance(self._data, Grib2MessageOnDiskArray): 873 self._ondiskarray = self._data 874 self._data = np.asarray(self._data) 875 return self._data 876 raise ValueError 877 878 @data.setter 879 def data(self, data): 880 if not isinstance(data, np.ndarray): 881 raise ValueError('Grib2Message data only supports numpy arrays') 882 self._data = data 883 884 885 def __getitem__(self, item): 886 return self.data[item] 887 888 889 def __setitem__(self, item): 890 raise NotImplementedError('assignment of data not supported via setitem') 891 892 893 def latlons(self, *args, **kwrgs): 894 """Alias for `grib2io.Grib2Message.grid` method""" 895 return self.grid(*args, **kwrgs) 896 897 898 def grid(self, unrotate=True): 899 """ 900 Return lats,lons (in degrees) of grid. 901 902 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, stereographic, 903 lambert conformal, albers equal-area, space-view and azimuthal equidistant grids. 904 905 Parameters 906 ---------- 907 **`unrotate : bool`** 908 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the grid, 909 otherwise `False`, do not. 910 911 Returns 912 ------- 913 **`lats, lons : numpy.ndarray`** 914 Returns two numpy.ndarrays with dtype=numpy.float32 of grid latitudes and 915 longitudes in units of degrees. 916 """ 917 if self._sha1_section3 in _latlon_datastore.keys(): 918 return (_latlon_datastore[self._sha1_section3]['latitude'], 919 _latlon_datastore[self._sha1_section3]['longitude']) 920 else: 921 _latlon_datastore[self._sha1_section3] = {} 922 gdtn = self.gridDefinitionTemplateNumber.value 923 gdtmpl = self.gridDefinitionTemplate 924 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 925 if gdtn == 0: 926 # Regular lat/lon grid 927 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 928 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 929 dlon = self.gridlengthXDirection 930 dlat = self.gridlengthYDirection 931 if lon2 < lon1 and dlon < 0: lon1 = -lon1 932 lats = np.linspace(lat1,lat2,self.ny) 933 if reggrid: 934 lons = np.linspace(lon1,lon2,self.nx) 935 else: 936 lons = np.linspace(lon1,lon2,self.ny*2) 937 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 938 elif gdtn == 1: # Rotated Lat/Lon grid 939 pj = pyproj.Proj(self.projParameters) 940 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 941 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 942 if lon1 > 180.0: lon1 -= 360.0 943 if lon2 > 180.0: lon2 -= 360.0 944 lats = np.linspace(lat1,lat2,self.ny) 945 lons = np.linspace(lon1,lon2,self.nx) 946 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 947 if unrotate: 948 from grib2io.utils import rotated_grid 949 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 950 self.latitudeSouthernPole, 951 self.longitudeSouthernPole) 952 elif gdtn == 40: # Gaussian grid (only works for global!) 953 from utils.gauss_grids import gaussian_latitudes 954 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 955 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 956 nlats = self.ny 957 if not reggrid: # Reduced Gaussian grid. 958 nlons = 2*nlats 959 dlon = 360./nlons 960 else: 961 nlons = self.nx 962 dlon = self.gridlengthXDirection 963 lons = np.arange(lon1,lon2+dlon,dlon) 964 # Compute Gaussian lats (north to south) 965 lats = gaussian_latitudes(nlats) 966 if lat1 < lat2: # reverse them if necessary 967 lats = lats[::-1] 968 lons,lats = np.meshgrid(lons,lats) 969 elif gdtn in {10,20,30,31,110}: 970 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, Azimuthal Equidistant 971 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 972 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 973 pj = pyproj.Proj(self.projParameters) 974 llcrnrx, llcrnry = pj(lon1,lat1) 975 x = llcrnrx+dx*np.arange(self.nx) 976 y = llcrnry+dy*np.arange(self.ny) 977 x,y = np.meshgrid(x, y) 978 lons,lats = pj(x, y, inverse=True) 979 elif gdtn == 90: 980 # Satellite Projection 981 dx = self.gridlengthXDirection 982 dy = self.gridlengthYDirection 983 pj = pyproj.Proj(self.projParameters) 984 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 985 x -= 0.5*x.max() 986 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 987 y -= 0.5*y.max() 988 lons,lats = pj(x,y,inverse=True) 989 # Set lons,lats to 1.e30 where undefined 990 abslons = np.fabs(lons) 991 abslats = np.fabs(lats) 992 lons = np.where(abslons < 1.e20, lons, 1.e30) 993 lats = np.where(abslats < 1.e20, lats, 1.e30) 994 elif gdtn == 32769: 995 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 996 from grib2io.utils import arakawa_rotated_grid 997 from grib2io.utils.rotated_grid import DEG2RAD 998 di, dj = 0.0, 0.0 999 do_180 = False 1000 idir = 1 if self.scanModeFlags[0] == 0 else -1 1001 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1002 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1003 do_rot = 1 if grid_oriented == 1 else 0 1004 la1 = self.latitudeFirstGridpoint 1005 lo1 = self.longitudeFirstGridpoint 1006 clon = self.longitudeCenterGridpoint 1007 clat = self.latitudeCenterGridpoint 1008 lasp = clat - 90.0 1009 losp = clon 1010 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1011 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1012 rlat = -llat 1013 rlon = -llon 1014 if self.nx == 1: 1015 di = 0.0 1016 elif idir == 1: 1017 ti = rlon 1018 while ti < llon: 1019 ti += 360.0 1020 di = (ti - llon)/float(self.nx-1) 1021 else: 1022 ti = llon 1023 while ti < rlon: 1024 ti += 360.0 1025 di = (ti - rlon)/float(self.nx-1) 1026 if self.ny == 1: 1027 dj = 0.0 1028 else: 1029 dj = (rlat - llat)/float(self.ny-1) 1030 if dj < 0.0: 1031 dj = -dj 1032 if idir == 1: 1033 if llon > rlon: 1034 llon -= 360.0 1035 if llon < 0 and rlon > 0: 1036 do_180 = True 1037 else: 1038 if rlon > llon: 1039 rlon -= 360.0 1040 if rlon < 0 and llon > 0: 1041 do_180 = True 1042 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1043 xlon1d = llon + (np.arange(self.nx)*idir*di) 1044 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1045 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1046 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1047 if do_180: 1048 lons = np.where(lons>180.0,lons-360.0,lons) 1049 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1050 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1051 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1052 del xlat1d, xlon1d, xlats, xlons 1053 else: 1054 raise ValueError('Unsupported grid') 1055 1056 _latlon_datastore[self._sha1_section3]['latitude'] = lats 1057 _latlon_datastore[self._sha1_section3]['longitude'] = lons 1058 1059 return lats, lons 1060 1061 1062 def map_keys(self): 1063 """ 1064 Returns an unpacked data grid where integer grid values are replaced with 1065 a string.in which the numeric value is a representation of. 1066 1067 These types of fields are cateogrical or classifications where data values 1068 do not represent an observable or predictable physical quantity. An example 1069 of such a field field would be [Dominant Precipitation Type - 1070 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1071 1072 Returns 1073 ------- 1074 **`numpy.ndarray`** of string values per element. 1075 """ 1076 hold_auto_nans = _AUTO_NANS 1077 set_auto_nans(False) 1078 if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \ 1079 (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'): 1080 keys = utils.decode_wx_strings(self.section2) 1081 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1082 keys[int(self.priMissingValue)] = 'Missing' 1083 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1084 keys[int(self.secMissingValue)] = 'Missing' 1085 u,inv = np.unique(self.data,return_inverse=True) 1086 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1087 else: 1088 # For data whose units are defined in a code table (i.e. classification or mask) 1089 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1090 fld = self.data.astype(np.int32).astype(str) 1091 tbl = tables.get_table(tblname,expand=True) 1092 for val in np.unique(fld): 1093 fld = np.where(fld==val,tbl[val],fld) 1094 set_auto_nans(hold_auto_nans) 1095 return fld 1096 1097 1098 def to_bytes(self, validate=True): 1099 """ 1100 Return packed GRIB2 message in bytes format. 1101 1102 This will be Useful for exporting data in non-file formats. For example, 1103 can be used to output grib data directly to S3 using the boto3 client 1104 without the need to write a temporary file to upload first. 1105 1106 Parameters 1107 ---------- 1108 **`validate : bool, optional`** 1109 If `True` (DEFAULT), validates first/last four bytes for proper 1110 formatting, else returns None. If `False`, message is output as is. 1111 1112 Returns 1113 ------- 1114 Returns GRIB2 formatted message as bytes. 1115 """ 1116 if validate: 1117 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1118 return self._msg 1119 else: 1120 return None 1121 else: 1122 return self._msg 1123 1124 1125 def interpolate(self, method, grid_def_out, method_options=None): 1126 """ 1127 Perform grid spatial interpolation via the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip). 1128 1129 **IMPORTANT:** This interpolate method only supports scalar interpolation. If you 1130 need to perform vector interpolation, use the module-level `grib2io.interpolate` function. 1131 1132 Parameters 1133 ---------- 1134 **`method : int or str`** 1135 Interpolate method to use. This can either be an integer or string using 1136 the following mapping: 1137 1138 | Interpolate Scheme | Integer Value | 1139 | :---: | :---: | 1140 | 'bilinear' | 0 | 1141 | 'bicubic' | 1 | 1142 | 'neighbor' | 2 | 1143 | 'budget' | 3 | 1144 | 'spectral' | 4 | 1145 | 'neighbor-budget' | 6 | 1146 1147 **`grid_def_out : grib2io.Grib2GridDef`** 1148 Grib2GridDef object of the output grid. 1149 1150 **`method_options : list of ints, optional`** 1151 Interpolation options. See the NCEPLIBS-ip doucmentation for 1152 more information on how these are used. 1153 1154 Returns 1155 ------- 1156 If interpolating to a grid, a new Grib2Message object is returned. The GRIB2 metadata of 1157 the new Grib2Message object is indentical to the input except where required to be different 1158 because of the new grid specs. 1159 1160 If interpolating to station points, the interpolated data values are returned as a numpy.ndarray. 1161 """ 1162 section0 = self.section0 1163 section0[-1] = 0 1164 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1165 section3 = np.concatenate((gds,grid_def_out.gdt)) 1166 1167 msg = Grib2Message(section0,self.section1,self.section2,section3, 1168 self.section4,self.section5,self.bitMapFlag.value) 1169 1170 msg._msgnum = -1 1171 msg._deflist = self._deflist 1172 msg._coordlist = self._coordlist 1173 shape = (msg.ny,msg.nx) 1174 ndim = 2 1175 if msg.typeOfValues == 0: 1176 dtype = 'float32' 1177 elif msg.typeOfValues == 1: 1178 dtype = 'int32' 1179 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1180 method_options=method_options).reshape(msg.ny,msg.nx) 1181 return msg
GRIB2 Message base class
754 def attrs_by_section(self, sect, values=False): 755 """ 756 Provide a tuple of attribute names for the given GRIB2 section. 757 758 Parameters 759 ---------- 760 **`sect : int`** 761 The GRIB2 section number. 762 763 **`values : bool, optional`** 764 Optional (default is `False`) arugment to return attributes values. 765 766 Returns 767 ------- 768 A List attribute names or Dict if `values = True`. 769 """ 770 if sect in {0,1,6}: 771 attrs = templates._section_attrs[sect] 772 elif sect in {3,4,5}: 773 def _find_class_index(n): 774 _key = {3:'Grid', 4:'Product', 5:'Data'} 775 for i,c in enumerate(self.__class__.__mro__): 776 if _key[n] in c.__name__: 777 return i 778 else: 779 return [] 780 if sys.version_info.minor <= 8: 781 attrs = templates._section_attrs[sect]+\ 782 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 783 else: 784 attrs = templates._section_attrs[sect]+\ 785 self.__class__.__mro__[_find_class_index(sect)]._attrs 786 else: 787 attrs = [] 788 if values: 789 return {k:getattr(self,k) for k in attrs} 790 else: 791 return attrs
Provide a tuple of attribute names for the given GRIB2 section.
Parameters
sect : int
The GRIB2 section number.
values : bool, optional
Optional (default is False
) arugment to return attributes values.
Returns
A List attribute names or Dict if values = True
.
794 def pack(self): 795 """ 796 Packs GRIB2 section data into a binary message. It is the user's responsibility 797 to populate the GRIB2 section information with appropriate metadata. 798 """ 799 # Create beginning of packed binary message with section 0 and 1 data. 800 self._sections = [] 801 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 802 self._sections += [0,1] 803 804 # Add section 2 if present. 805 if isinstance(self.section2,bytes) and len(self.section2) > 0: 806 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 807 self._sections.append(2) 808 809 # Add section 3. 810 self.section3[1] = self.nx * self.ny 811 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 812 self.gridDefinitionTemplate, 813 self._deflist) 814 self._sections.append(3) 815 816 # Prepare data. 817 field = np.copy(self.data) 818 if self.scanModeFlags is not None: 819 if self.scanModeFlags[3]: 820 fieldsave = field.astype('f') # Casting makes a copy 821 field[1::2,:] = fieldsave[1::2,::-1] 822 fld = field.astype('f') 823 824 # Prepare bitmap, if necessary 825 bitmapflag = self.bitMapFlag.value 826 if bitmapflag == 0: 827 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 828 else: 829 bmap = None 830 831 # Prepare optional coordinate list 832 if self._coordlist is not None: 833 crdlist = np.array(self._coordlist,'f') 834 else: 835 crdlist = None 836 837 # Prepare data for packing if nans are present 838 fld = np.ravel(fld) 839 if np.isnan(fld).any() and hasattr(self,'_missvalmap'): 840 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 841 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 842 843 # Add sections 4, 5, 6 (if present), and 7. 844 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 845 self.productDefinitionTemplate, 846 crdlist, 847 self.drtn, 848 self.dataRepresentationTemplate, 849 fld, 850 bitmapflag, 851 bmap) 852 self._sections.append(4) 853 self._sections.append(5) 854 if bmap is not None: self._sections.append(6) 855 self._sections.append(7) 856 857 # Finalize GRIB2 message with section 8. 858 self._msg, self._pos = g2clib.grib2_end(self._msg) 859 self._sections.append(8) 860 self.section0[-1] = len(self._msg)
Packs GRIB2 section data into a binary message. It is the user's responsibility to populate the GRIB2 section information with appropriate metadata.
893 def latlons(self, *args, **kwrgs): 894 """Alias for `grib2io.Grib2Message.grid` method""" 895 return self.grid(*args, **kwrgs)
Alias for grib2io.Grib2Message.grid
method
898 def grid(self, unrotate=True): 899 """ 900 Return lats,lons (in degrees) of grid. 901 902 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, stereographic, 903 lambert conformal, albers equal-area, space-view and azimuthal equidistant grids. 904 905 Parameters 906 ---------- 907 **`unrotate : bool`** 908 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the grid, 909 otherwise `False`, do not. 910 911 Returns 912 ------- 913 **`lats, lons : numpy.ndarray`** 914 Returns two numpy.ndarrays with dtype=numpy.float32 of grid latitudes and 915 longitudes in units of degrees. 916 """ 917 if self._sha1_section3 in _latlon_datastore.keys(): 918 return (_latlon_datastore[self._sha1_section3]['latitude'], 919 _latlon_datastore[self._sha1_section3]['longitude']) 920 else: 921 _latlon_datastore[self._sha1_section3] = {} 922 gdtn = self.gridDefinitionTemplateNumber.value 923 gdtmpl = self.gridDefinitionTemplate 924 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 925 if gdtn == 0: 926 # Regular lat/lon grid 927 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 928 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 929 dlon = self.gridlengthXDirection 930 dlat = self.gridlengthYDirection 931 if lon2 < lon1 and dlon < 0: lon1 = -lon1 932 lats = np.linspace(lat1,lat2,self.ny) 933 if reggrid: 934 lons = np.linspace(lon1,lon2,self.nx) 935 else: 936 lons = np.linspace(lon1,lon2,self.ny*2) 937 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 938 elif gdtn == 1: # Rotated Lat/Lon grid 939 pj = pyproj.Proj(self.projParameters) 940 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 941 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 942 if lon1 > 180.0: lon1 -= 360.0 943 if lon2 > 180.0: lon2 -= 360.0 944 lats = np.linspace(lat1,lat2,self.ny) 945 lons = np.linspace(lon1,lon2,self.nx) 946 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 947 if unrotate: 948 from grib2io.utils import rotated_grid 949 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 950 self.latitudeSouthernPole, 951 self.longitudeSouthernPole) 952 elif gdtn == 40: # Gaussian grid (only works for global!) 953 from utils.gauss_grids import gaussian_latitudes 954 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 955 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 956 nlats = self.ny 957 if not reggrid: # Reduced Gaussian grid. 958 nlons = 2*nlats 959 dlon = 360./nlons 960 else: 961 nlons = self.nx 962 dlon = self.gridlengthXDirection 963 lons = np.arange(lon1,lon2+dlon,dlon) 964 # Compute Gaussian lats (north to south) 965 lats = gaussian_latitudes(nlats) 966 if lat1 < lat2: # reverse them if necessary 967 lats = lats[::-1] 968 lons,lats = np.meshgrid(lons,lats) 969 elif gdtn in {10,20,30,31,110}: 970 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, Azimuthal Equidistant 971 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 972 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 973 pj = pyproj.Proj(self.projParameters) 974 llcrnrx, llcrnry = pj(lon1,lat1) 975 x = llcrnrx+dx*np.arange(self.nx) 976 y = llcrnry+dy*np.arange(self.ny) 977 x,y = np.meshgrid(x, y) 978 lons,lats = pj(x, y, inverse=True) 979 elif gdtn == 90: 980 # Satellite Projection 981 dx = self.gridlengthXDirection 982 dy = self.gridlengthYDirection 983 pj = pyproj.Proj(self.projParameters) 984 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 985 x -= 0.5*x.max() 986 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 987 y -= 0.5*y.max() 988 lons,lats = pj(x,y,inverse=True) 989 # Set lons,lats to 1.e30 where undefined 990 abslons = np.fabs(lons) 991 abslats = np.fabs(lats) 992 lons = np.where(abslons < 1.e20, lons, 1.e30) 993 lats = np.where(abslats < 1.e20, lats, 1.e30) 994 elif gdtn == 32769: 995 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 996 from grib2io.utils import arakawa_rotated_grid 997 from grib2io.utils.rotated_grid import DEG2RAD 998 di, dj = 0.0, 0.0 999 do_180 = False 1000 idir = 1 if self.scanModeFlags[0] == 0 else -1 1001 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1002 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1003 do_rot = 1 if grid_oriented == 1 else 0 1004 la1 = self.latitudeFirstGridpoint 1005 lo1 = self.longitudeFirstGridpoint 1006 clon = self.longitudeCenterGridpoint 1007 clat = self.latitudeCenterGridpoint 1008 lasp = clat - 90.0 1009 losp = clon 1010 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1011 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1012 rlat = -llat 1013 rlon = -llon 1014 if self.nx == 1: 1015 di = 0.0 1016 elif idir == 1: 1017 ti = rlon 1018 while ti < llon: 1019 ti += 360.0 1020 di = (ti - llon)/float(self.nx-1) 1021 else: 1022 ti = llon 1023 while ti < rlon: 1024 ti += 360.0 1025 di = (ti - rlon)/float(self.nx-1) 1026 if self.ny == 1: 1027 dj = 0.0 1028 else: 1029 dj = (rlat - llat)/float(self.ny-1) 1030 if dj < 0.0: 1031 dj = -dj 1032 if idir == 1: 1033 if llon > rlon: 1034 llon -= 360.0 1035 if llon < 0 and rlon > 0: 1036 do_180 = True 1037 else: 1038 if rlon > llon: 1039 rlon -= 360.0 1040 if rlon < 0 and llon > 0: 1041 do_180 = True 1042 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1043 xlon1d = llon + (np.arange(self.nx)*idir*di) 1044 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1045 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1046 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1047 if do_180: 1048 lons = np.where(lons>180.0,lons-360.0,lons) 1049 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1050 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1051 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1052 del xlat1d, xlon1d, xlats, xlons 1053 else: 1054 raise ValueError('Unsupported grid') 1055 1056 _latlon_datastore[self._sha1_section3]['latitude'] = lats 1057 _latlon_datastore[self._sha1_section3]['longitude'] = lons 1058 1059 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 : bool
If True
[DEFAULT], and grid is rotated lat/lon, then unrotate the grid,
otherwise False
, do not.
Returns
lats, lons : numpy.ndarray
Returns two numpy.ndarrays with dtype=numpy.float32 of grid latitudes and
longitudes in units of degrees.
1062 def map_keys(self): 1063 """ 1064 Returns an unpacked data grid where integer grid values are replaced with 1065 a string.in which the numeric value is a representation of. 1066 1067 These types of fields are cateogrical or classifications where data values 1068 do not represent an observable or predictable physical quantity. An example 1069 of such a field field would be [Dominant Precipitation Type - 1070 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1071 1072 Returns 1073 ------- 1074 **`numpy.ndarray`** of string values per element. 1075 """ 1076 hold_auto_nans = _AUTO_NANS 1077 set_auto_nans(False) 1078 if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \ 1079 (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'): 1080 keys = utils.decode_wx_strings(self.section2) 1081 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1082 keys[int(self.priMissingValue)] = 'Missing' 1083 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1084 keys[int(self.secMissingValue)] = 'Missing' 1085 u,inv = np.unique(self.data,return_inverse=True) 1086 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1087 else: 1088 # For data whose units are defined in a code table (i.e. classification or mask) 1089 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1090 fld = self.data.astype(np.int32).astype(str) 1091 tbl = tables.get_table(tblname,expand=True) 1092 for val in np.unique(fld): 1093 fld = np.where(fld==val,tbl[val],fld) 1094 set_auto_nans(hold_auto_nans) 1095 return fld
Returns an unpacked data grid where integer grid values are replaced with a string.in which the numeric value is a representation of.
These types of fields are cateogrical or classifications where data values do not represent an observable or predictable physical quantity. An example of such a field field would be Dominant Precipitation Type - DPTYPE
Returns
numpy.ndarray
of string values per element.
1098 def to_bytes(self, validate=True): 1099 """ 1100 Return packed GRIB2 message in bytes format. 1101 1102 This will be Useful for exporting data in non-file formats. For example, 1103 can be used to output grib data directly to S3 using the boto3 client 1104 without the need to write a temporary file to upload first. 1105 1106 Parameters 1107 ---------- 1108 **`validate : bool, optional`** 1109 If `True` (DEFAULT), validates first/last four bytes for proper 1110 formatting, else returns None. If `False`, message is output as is. 1111 1112 Returns 1113 ------- 1114 Returns GRIB2 formatted message as bytes. 1115 """ 1116 if validate: 1117 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1118 return self._msg 1119 else: 1120 return None 1121 else: 1122 return self._msg
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 : bool, optional
If True
(DEFAULT), validates first/last four bytes for proper
formatting, else returns None. If False
, message is output as is.
Returns
Returns GRIB2 formatted message as bytes.
1125 def interpolate(self, method, grid_def_out, method_options=None): 1126 """ 1127 Perform grid spatial interpolation via the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip). 1128 1129 **IMPORTANT:** This interpolate method only supports scalar interpolation. If you 1130 need to perform vector interpolation, use the module-level `grib2io.interpolate` function. 1131 1132 Parameters 1133 ---------- 1134 **`method : int or str`** 1135 Interpolate method to use. This can either be an integer or string using 1136 the following mapping: 1137 1138 | Interpolate Scheme | Integer Value | 1139 | :---: | :---: | 1140 | 'bilinear' | 0 | 1141 | 'bicubic' | 1 | 1142 | 'neighbor' | 2 | 1143 | 'budget' | 3 | 1144 | 'spectral' | 4 | 1145 | 'neighbor-budget' | 6 | 1146 1147 **`grid_def_out : grib2io.Grib2GridDef`** 1148 Grib2GridDef object of the output grid. 1149 1150 **`method_options : list of ints, optional`** 1151 Interpolation options. See the NCEPLIBS-ip doucmentation for 1152 more information on how these are used. 1153 1154 Returns 1155 ------- 1156 If interpolating to a grid, a new Grib2Message object is returned. The GRIB2 metadata of 1157 the new Grib2Message object is indentical to the input except where required to be different 1158 because of the new grid specs. 1159 1160 If interpolating to station points, the interpolated data values are returned as a numpy.ndarray. 1161 """ 1162 section0 = self.section0 1163 section0[-1] = 0 1164 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1165 section3 = np.concatenate((gds,grid_def_out.gdt)) 1166 1167 msg = Grib2Message(section0,self.section1,self.section2,section3, 1168 self.section4,self.section5,self.bitMapFlag.value) 1169 1170 msg._msgnum = -1 1171 msg._deflist = self._deflist 1172 msg._coordlist = self._coordlist 1173 shape = (msg.ny,msg.nx) 1174 ndim = 2 1175 if msg.typeOfValues == 0: 1176 dtype = 'float32' 1177 elif msg.typeOfValues == 1: 1178 dtype = 'int32' 1179 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1180 method_options=method_options).reshape(msg.ny,msg.nx) 1181 return msg
Perform grid spatial interpolation via the NCEPLIBS-ip library.
IMPORTANT: This interpolate method only supports scalar interpolation. If you
need to perform vector interpolation, use the module-level grib2io.interpolate
function.
Parameters
method : int or str
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 doucmentation for
more information on how these are used.
Returns
If interpolating to a grid, a new Grib2Message object is returned. The GRIB2 metadata of the new Grib2Message object is indentical to the input except where required to be different because of the new grid specs.
If interpolating to station points, the interpolated data values are returned as a numpy.ndarray.
24def show_config(): 25 """Print grib2io build configuration information.""" 26 print(f'grib2io version {__version__} Configuration:\n') 27 print(f'\tg2c library version: {__g2clib_version__}') 28 print(f'\tJPEG compression support: {has_jpeg_support}') 29 print(f'\tPNG compression support: {has_png_support}') 30 print(f'\tAEC compression support: {has_aec_support}')
Print grib2io build configuration information.
1317def interpolate(a, method, grid_def_in, grid_def_out, method_options=None): 1318 """ 1319 This is the module-level interpolation function that interfaces with the grib2io_interp 1320 component pakcage that interfaces to the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip). 1321 1322 Parameters 1323 ---------- 1324 1325 **`a : numpy.ndarray or tuple`** 1326 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 1327 performed. If `a` is a `tuple`, then vector interpolation will be performed 1328 with the assumption that u = a[0] and v = a[1] and are both `numpy.ndarray`. 1329 1330 These data are expected to be in 2-dimensional form with shape (ny, nx) or 1331 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, 1332 temporal, or classification (i.e. ensemble members) dimension. The function will 1333 properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into 1334 the interpolation subroutines. 1335 1336 **`method : int or str`** 1337 Interpolate method to use. This can either be an integer or string using 1338 the following mapping: 1339 1340 | Interpolate Scheme | Integer Value | 1341 | :---: | :---: | 1342 | 'bilinear' | 0 | 1343 | 'bicubic' | 1 | 1344 | 'neighbor' | 2 | 1345 | 'budget' | 3 | 1346 | 'spectral' | 4 | 1347 | 'neighbor-budget' | 6 | 1348 1349 **`grid_def_in : grib2io.Grib2GridDef`** 1350 Grib2GridDef object for the input grid. 1351 1352 **`grid_def_out : grib2io.Grib2GridDef`** 1353 Grib2GridDef object for the output grid or station points. 1354 1355 **`method_options : list of ints, optional`** 1356 Interpolation options. See the NCEPLIBS-ip doucmentation for 1357 more information on how these are used. 1358 1359 Returns 1360 ------- 1361 Returns a `numpy.ndarray` when scalar interpolation is performed or 1362 a `tuple` of `numpy.ndarray`s when vector interpolation is performed 1363 with the assumptions that 0-index is the interpolated u and 1-index 1364 is the interpolated v. 1365 """ 1366 from grib2io_interp import interpolate 1367 1368 if isinstance(method,int) and method not in _interp_schemes.values(): 1369 raise ValueError('Invalid interpolation method.') 1370 elif isinstance(method,str): 1371 if method in _interp_schemes.keys(): 1372 method = _interp_schemes[method] 1373 else: 1374 raise ValueError('Invalid interpolation method.') 1375 1376 if method_options is None: 1377 method_options = np.zeros((20),dtype=np.int32) 1378 if method in {3,6}: 1379 method_options[0:2] = -1 1380 1381 ni = grid_def_in.npoints 1382 no = grid_def_out.npoints 1383 1384 # Adjust shape of input array(s) 1385 a,newshp = _adjust_array_shape_for_interp(a,grid_def_in,grid_def_out) 1386 1387 # Set lats and lons if stations, else create array for grids. 1388 if grid_def_out.gdtn == -1: 1389 rlat = np.array(grid_def_out.lats,dtype=np.float32) 1390 rlon = np.array(grid_def_out.lons,dtype=np.float32) 1391 else: 1392 rlat = np.zeros((no),dtype=np.float32) 1393 rlon = np.zeros((no),dtype=np.float32) 1394 1395 # Call interpolation subroutines according to type of a. 1396 if isinstance(a,np.ndarray): 1397 # Scalar 1398 ibi = np.zeros((a.shape[0]),dtype=np.int32) 1399 li = np.zeros(a.shape,dtype=np.int32) 1400 go = np.zeros((a.shape[0],no),dtype=np.float32) 1401 no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options, 1402 grid_def_in.gdtn,grid_def_in.gdt, 1403 grid_def_out.gdtn,grid_def_out.gdt, 1404 ibi,li.T,a.T,go.T,rlat,rlon) 1405 out = go.reshape(newshp) 1406 elif isinstance(a,tuple): 1407 # Vector 1408 ibi = np.zeros((a[0].shape[0]),dtype=np.int32) 1409 li = np.zeros(a[0].shape,dtype=np.int32) 1410 uo = np.zeros((a[0].shape[0],no),dtype=np.float32) 1411 vo = np.zeros((a[1].shape[0],no),dtype=np.float32) 1412 crot = np.ones((no),dtype=np.float32) 1413 srot = np.zeros((no),dtype=np.float32) 1414 no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options, 1415 grid_def_in.gdtn,grid_def_in.gdt, 1416 grid_def_out.gdtn,grid_def_out.gdt, 1417 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T, 1418 rlat,rlon,crot,srot) 1419 del crot 1420 del srot 1421 out = (uo.reshape(newshp),vo.reshape(newshp)) 1422 1423 del rlat 1424 del rlon 1425 return out
This is the module-level interpolation function that interfaces with the grib2io_interp component pakcage that interfaces to the NCEPLIBS-ip library.
Parameters
a : numpy.ndarray or tuple
Input data. If a
is a numpy.ndarray
, scalar interpolation will be
performed. If a
is a tuple
, then vector interpolation will be performed
with the assumption that u = a[0] and v = a[1] and are both numpy.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 : int or str
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 doucmentation for
more information on how these are used.
Returns
Returns a numpy.ndarray
when scalar interpolation is performed or
a tuple
of numpy.ndarray
s when vector interpolation is performed
with the assumptions that 0-index is the interpolated u and 1-index
is the interpolated v.
1428def interpolate_to_stations(a, method, grid_def_in, lats, lons, method_options=None): 1429 """ 1430 This is the module-level interpolation function **for interpolation to stations** 1431 that interfaces with the grib2io_interp component pakcage that interfaces to 1432 the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip). It supports 1433 scalar and vector interpolation according to the type of object a. 1434 1435 Parameters 1436 ---------- 1437 **`a : numpy.ndarray or tuple`** 1438 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 1439 performed. If `a` is a `tuple`, then vector interpolation will be performed 1440 with the assumption that u = a[0] and v = a[1] and are both `numpy.ndarray`. 1441 1442 These data are expected to be in 2-dimensional form with shape (ny, nx) or 1443 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, 1444 temporal, or classification (i.e. ensemble members) dimension. The function will 1445 properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into 1446 the interpolation subroutines. 1447 1448 **`method : int or str`** 1449 Interpolate method to use. This can either be an integer or string using 1450 the following mapping: 1451 1452 | Interpolate Scheme | Integer Value | 1453 | :---: | :---: | 1454 | 'bilinear' | 0 | 1455 | 'bicubic' | 1 | 1456 | 'neighbor' | 2 | 1457 | 'budget' | 3 | 1458 | 'spectral' | 4 | 1459 | 'neighbor-budget' | 6 | 1460 1461 **`grid_def_in : grib2io.Grib2GridDef`** 1462 Grib2GridDef object for the input grid. 1463 1464 **`lats : numpy.ndarray or list`** 1465 Latitudes for station points 1466 1467 **`lons : numpy.ndarray or list`** 1468 Longitudes for station points 1469 1470 **`method_options : list of ints, optional`** 1471 Interpolation options. See the NCEPLIBS-ip doucmentation for 1472 more information on how these are used. 1473 1474 Returns 1475 ------- 1476 Returns a `numpy.ndarray` when scalar interpolation is performed or 1477 a `tuple` of `numpy.ndarray`s when vector interpolation is performed 1478 with the assumptions that 0-index is the interpolated u and 1-index 1479 is the interpolated v. 1480 """ 1481 from grib2io_interp import interpolate 1482 1483 if isinstance(method,int) and method not in _interp_schemes.values(): 1484 raise ValueError('Invalid interpolation method.') 1485 elif isinstance(method,str): 1486 if method in _interp_schemes.keys(): 1487 method = _interp_schemes[method] 1488 else: 1489 raise ValueError('Invalid interpolation method.') 1490 1491 if method_options is None: 1492 method_options = np.zeros((20),dtype=np.int32) 1493 if method in {3,6}: 1494 method_options[0:2] = -1 1495 1496 # Check lats and lons 1497 if isinstance(lats,list): 1498 nlats = len(lats) 1499 elif isinstance(lats,np.ndarray) and len(lats.shape) == 1: 1500 nlats = lats.shape[0] 1501 else: 1502 raise ValueError('Station latitudes must be a list or 1-D NumPy array.') 1503 if isinstance(lons,list): 1504 nlons = len(lons) 1505 elif isinstance(lons,np.ndarray) and len(lons.shape) == 1: 1506 nlons = lons.shape[0] 1507 else: 1508 raise ValueError('Station longitudes must be a list or 1-D NumPy array.') 1509 if nlats != nlons: 1510 raise ValueError('Station lats and lons must be same size.') 1511 1512 ni = grid_def_in.npoints 1513 no = nlats 1514 1515 # Adjust shape of input array(s) 1516 a,newshp = _adjust_array_shape_for_interp_stations(a,grid_def_in,no) 1517 1518 # Set lats and lons if stations 1519 rlat = np.array(lats,dtype=np.float32) 1520 rlon = np.array(lons,dtype=np.float32) 1521 1522 # Use gdtn = -1 for stations and an empty template array 1523 gdtn = -1 1524 gdt = np.zeros((200),dtype=np.int32) 1525 1526 # Call interpolation subroutines according to type of a. 1527 if isinstance(a,np.ndarray): 1528 # Scalar 1529 ibi = np.zeros((a.shape[0]),dtype=np.int32) 1530 li = np.zeros(a.shape,dtype=np.int32) 1531 go = np.zeros((a.shape[0],no),dtype=np.float32) 1532 no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options, 1533 grid_def_in.gdtn,grid_def_in.gdt, 1534 gdtn,gdt, 1535 ibi,li.T,a.T,go.T,rlat,rlon) 1536 out = go.reshape(newshp) 1537 elif isinstance(a,tuple): 1538 # Vector 1539 ibi = np.zeros((a[0].shape[0]),dtype=np.int32) 1540 li = np.zeros(a[0].shape,dtype=np.int32) 1541 uo = np.zeros((a[0].shape[0],no),dtype=np.float32) 1542 vo = np.zeros((a[1].shape[0],no),dtype=np.float32) 1543 crot = np.ones((no),dtype=np.float32) 1544 srot = np.zeros((no),dtype=np.float32) 1545 no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options, 1546 grid_def_in.gdtn,grid_def_in.gdt, 1547 gdtn,gdt, 1548 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T, 1549 rlat,rlon,crot,srot) 1550 del crot 1551 del srot 1552 out = (uo.reshape(newshp),vo.reshape(newshp)) 1553 1554 del rlat 1555 del rlon 1556 return out
This is the module-level interpolation function for interpolation to stations that interfaces with the grib2io_interp component pakcage 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 a numpy.ndarray
, scalar interpolation will be
performed. If a
is a tuple
, then vector interpolation will be performed
with the assumption that u = a[0] and v = a[1] and are both numpy.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 : int or str
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 doucmentation for
more information on how these are used.
Returns
Returns a numpy.ndarray
when scalar interpolation is performed or
a tuple
of numpy.ndarray
s when vector interpolation is performed
with the assumptions that 0-index is the interpolated u and 1-index
is the interpolated v.
1559@dataclass 1560class Grib2GridDef: 1561 """ 1562 Class to hold GRIB2 Grid Definition Template Number and Template as 1563 class attributes. This allows for cleaner looking code when passing these 1564 metadata around. For example, the `grib2io._Grib2Message.interpolate` 1565 method and `grib2io.interpolate` function accepts these objects. 1566 """ 1567 gdtn: int 1568 gdt: np.array 1569 1570 @classmethod 1571 def from_section3(cls, section3): 1572 return cls(section3[4],section3[5:]) 1573 1574 @property 1575 def nx(self): 1576 return self.gdt[7] 1577 1578 @property 1579 def ny(self): 1580 return self.gdt[8] 1581 1582 @property 1583 def npoints(self): 1584 return self.gdt[7] * self.gdt[8] 1585 1586 @property 1587 def shape(self): 1588 return (self.ny, self.nx)
Class to hold GRIB2 Grid Definition Template Number and Template as
class 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.