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 extension module. The decoding/encoding of GRIB2 metadata is translated into more descriptive, plain language metadata by providing a mapping of the integer coded metadata to the appropriate GRIB2 code tables. The NCEP GRIB2 code tables are a part of the grib2io package.
Index File
As of v2.6.0, grib2io provides the capability to create (and read) an index file for a GRIB2 file. The index file contents are specific to Python and grib2io in that pickle is used to dump the index dictionary to file. Using index files can dramatically improve performance in situations where the same file will be read multiple times. The index file name is the original GRIB2 file name with a hash string appended, followed by the file extension, .grib2ioidx. The hash string is the SHA-1 of the GRIB2 file name and the file size. For example, GRIB2 file,
gfs.t00z.pgrb2.1p00.f024
when opened, grib2io will generate an index file with the following name,
gfs.t00z.pgrb2.1p00.f024_0422a93bfd6d095bd0a942ba5e9fe42e76050123.grib2ioidx
By default, grib2io will generate an index file or use an existing one. The generation and usage
of a grib2io index file can be turned off by providing kwargs use_index=False and/or
save_index=False in grib2io.open().
Interpolation
As of v2.4.0, spatial interpolation via NCEPLIPS-ip Fortran library is now a part of the grib2io package. Previously, interpolation was handled by a separate component package, grib2io-interp, which is now deprecated. grib2io-interp provided interpolation via a F2PY-generated interface to NCEPLIBS-ip, which has become difficult since the removal of distutils from Python 3.12+.
NCEPLIBS-ip interpolation Fortran subroutines contain the BIND(C) attribute which
provides an equivalent C-interface. grib2io now provides a Cython-based interface, iplib,
to these Fortran subroutines via their C-interface. If NCEPLIBS-ip was built with
OpenMP support, iplib will provide functions for getting and setting the number of
OpenMP threads.
Xarray Backend
grib2io provides a Xarray backend engine so that many GRIB2 messages can be represented as N-dimensional DataArray objects and collected along common coordinates as Datasets or DataTrees. The Xarray backend engine API is experimental and is subject to change without backward compatibility.
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', 'msgs_from_index'] 8 9try: 10 from . import __config__ 11 __version__ = __config__.grib2io_version 12 has_interpolation = __config__.has_interpolation 13 has_openmp_support = __config__.has_openmp_support 14 g2c_static = __config__.g2c_static 15 ip_static = __config__.ip_static 16 extra_objects = __config__.extra_objects 17except(ImportError): 18 pass 19 20from .g2clib import __version__ as __g2clib_version__ 21from .g2clib import _has_jpeg 22from .g2clib import _has_png 23from .g2clib import _has_aec 24 25has_jpeg_support = bool(_has_jpeg) 26has_png_support = bool(_has_png) 27has_aec_support = bool(_has_aec) 28 29from .tables.originating_centers import _ncep_grib2_table_version 30ncep_grib2_table_version = _ncep_grib2_table_version 31g2c_version = __g2clib_version__ 32 33def show_config(): 34 """Print grib2io build configuration information.""" 35 print(f'grib2io version {__version__} Configuration:') 36 print(f'') 37 print(f'NCEPLIBS-g2c library version: {__g2clib_version__}') 38 print(f'\tStatic library: {g2c_static}') 39 print(f'\tJPEG compression support: {has_jpeg_support}') 40 print(f'\tPNG compression support: {has_png_support}') 41 print(f'\tAEC compression support: {has_aec_support}') 42 print(f'') 43 print(f'NCEPLIPS-ip support: {has_interpolation}') 44 print(f'\tStatic library: {ip_static}') 45 print(f'\tOpenMP support: {has_openmp_support}') 46 print(f'') 47 print(f'Static libs:') 48 for lib in extra_objects: 49 print(f'\t{lib}') 50 print(f'') 51 print(f'NCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
116class open(): 117 """ 118 GRIB2 File Object. 119 120 This class can accommodate a physical file with one or more GRIB2 121 messages or a bytes object containing a GRIB2 messages. 122 123 A physical file can contain one or more GRIB2 messages. When instantiated, 124 class `grib2io.open`, the file named `filename` is opened for reading (`mode 125 = 'r'`) and is automatically indexed. The indexing procedure reads some of 126 the GRIB2 metadata for all GRIB2 Messages. A GRIB2 Message may contain 127 submessages whereby Section 2-7 can be repeated. grib2io accommodates for 128 this by flattening any GRIB2 submessages into multiple individual messages. 129 130 It is important to note that GRIB2 files from some Meteorological agencies 131 contain other data than GRIB2 messages. GRIB2 files from ECMWF can contain 132 GRIB1 and GRIB2 messages. grib2io checks for these and safely ignores them. 133 134 Attributes 135 ---------- 136 closed : bool 137 `True` is file handle is close; `False` otherwise. 138 current_message : int 139 Current position of the file in units of GRIB2 Messages. (read only) 140 levels : tuple 141 Tuple containing a unique list of wgrib2-formatted level/layer strings. 142 messages : int 143 Count of GRIB2 Messages contained in the file. 144 mode : str 145 File IO mode of opening the file. 146 name : str 147 Full path name of the GRIB2 file. 148 save_index : bool 149 Whether to save a pickle-based index file for the GRIB2 file. Default is `True`. 150 size : int 151 Size of the file in units of bytes. 152 use_index 153 Whether to use an existing pickle-based index file for the GRIB2 file. Default is `True`. 154 variables : tuple 155 Tuple containing a unique list of variable short names (i.e. GRIB2 156 abbreviation names). 157 """ 158 159 __slots__ = ('_fileid', '_filehandle', '_hasindex', '_index', 160 '_pos', 'closed', 'current_message', 'messages', 'mode', 161 'name', 'size', 'save_index', 'use_index', '_msgs') 162 163 def __init__( 164 self, 165 filename: Union[bytes, str, Path], 166 mode: Literal["r", "w", "x"] = "r", 167 *, 168 save_index = True, 169 use_index = True, 170 _xarray_backend = False, 171 **kwargs, 172 ): 173 """ 174 Initialize GRIB2 File object instance. 175 176 Parameters 177 ---------- 178 filename 179 File path containing GRIB2 messages OR bytes. 180 mode 181 File access mode where "r" opens the files for reading only; "w" 182 opens the file for overwriting and "x" for writing to a new file. 183 save_index 184 Whether to save a pickle-based index file for the GRIB2 file. Default is True. 185 186 .. versionadded:: 2.6.0 187 use_index 188 Whether to use an existing pickle-based index file for the GRIB2 file. Default is True. 189 190 .. versionadded:: 2.6.0 191 """ 192 193 # All write modes are read/write. 194 # All modes are binary. 195 if mode in ("a", "x", "w"): 196 mode += "+" 197 mode = mode + "b" 198 199 self.mode = mode 200 self.save_index = save_index 201 self.use_index = use_index 202 203 if isinstance(filename, bytes): 204 if 'r' not in self.mode: 205 raise ValueError("bytes grib2 can only be opened as read") 206 self.current_message = 0 207 if filename[:2] == _GZIP_HEADER: 208 filename = gzip.decompress(filename) 209 if filename[:4]+filename[-4:] != b'GRIB7777': 210 raise ValueError("Invalid GRIB bytes") 211 self._filehandle = BytesIO(filename) 212 self.name = "<in-memory-file>" 213 self.size = len(filename) 214 self._fileid = hashlib.sha1((self.name+str(self.size)).encode('ASCII')).hexdigest() 215 self._index = build_index(self._filehandle) 216 self._msgs = msgs_from_index(self._index, filehandle=self._filehandle) 217 self.messages = len(self._msgs) 218 219 else: 220 self.current_message = 0 221 if 'r' in mode: 222 self._filehandle = builtins.open(filename, mode=mode) 223 # Some GRIB2 files are gzipped, so check for that here, but 224 # raise error when using xarray backend. 225 # Gzip files contain a 2-byte header b'\x1f\x8b'. 226 if self._filehandle.read(2) == _GZIP_HEADER: 227 self._filehandle.close() 228 if _xarray_backend: 229 raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.') 230 self._filehandle = gzip.open(filename, mode=mode) 231 else: 232 self._filehandle = builtins.open(filename, mode=mode) 233 else: 234 self._filehandle = builtins.open(filename, mode=mode) 235 self.name = os.path.abspath(filename) 236 self.name = os.path.abspath(filename) 237 fstat = os.stat(self.name) 238 self.size = fstat.st_size 239 self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+ 240 str(self.size)).encode('ASCII')).hexdigest() 241 242 if 'r' in self.mode: 243 244 indexfile = f"{self.name}_{self._fileid}.grib2ioidx" 245 if self.use_index and os.path.exists(indexfile): 246 try: 247 with builtins.open(indexfile, 'rb') as file: 248 index = pickle.load(file) 249 self._index = index 250 except Exception as e: 251 print(f"found indexfile: {indexfile}, but unable to load it: {e}") 252 print(f"re-forming index from grib2file, but not writing indexfile") 253 self._index = build_index(self._filehandle) 254 else: 255 self._index = build_index(self._filehandle) 256 if self.save_index: 257 try: 258 serialize_index(self._index, indexfile) 259 except Exception as e: 260 print(f"index was not serialized for future use: {e}") 261 262 self._msgs = msgs_from_index(self._index, filehandle=self._filehandle) 263 264 self.messages = len(self._msgs) 265 elif 'w' or 'x' in self.mode: 266 self.messages = 0 267 self.current_message = None 268 269 self.closed = self._filehandle.closed 270 271 272 def __delete__(self, instance): 273 self.close() 274 del self._index 275 276 277 def __enter__(self): 278 return self 279 280 281 def __exit__(self, atype, value, traceback): 282 self.close() 283 284 285 def __iter__(self): 286 yield from self._msgs 287 288 289 def __len__(self): 290 return self.messages 291 292 293 def __repr__(self): 294 strings = [] 295 for k in self.__slots__: 296 if k.startswith('_'): continue 297 strings.append('%s = %s\n'%(k,eval('self.'+k))) 298 return ''.join(strings) 299 300 301 def __getitem__(self, key): 302 if isinstance(key,int): 303 if abs(key) >= len(self._msgs): 304 raise IndexError("index out of range") 305 else: 306 return self._msgs[key] 307 elif isinstance(key,str): 308 return self.select(shortName=key) 309 elif isinstance(key,slice): 310 return self._msgs[key] 311 else: 312 raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.') 313 314 315 316 @property 317 def levels(self): 318 """Provides a unique tuple of level strings.""" 319 if self._hasindex: 320 return tuple(sorted(set([msg.level for msg in self._msgs]))) 321 else: 322 return None 323 324 325 @property 326 def variables(self): 327 """Provides a unique tuple of variable shortName strings.""" 328 if self._hasindex: 329 return tuple(sorted(set([msg.shortName for msg in self._msgs]))) 330 else: 331 return None 332 333 334 def close(self): 335 """Close the file handle.""" 336 if not self._filehandle.closed: 337 self.messages = 0 338 self.current_message = 0 339 self._filehandle.close() 340 self.closed = self._filehandle.closed 341 342 343 def read(self, size: Optional[int]=None): 344 """ 345 Read size amount of GRIB2 messages from the current position. 346 347 If no argument is given, then size is None and all messages are returned 348 from the current position in the file. This read method follows the 349 behavior of Python's builtin open() function, but whereas that operates 350 on units of bytes, we operate on units of GRIB2 messages. 351 352 Parameters 353 ---------- 354 size: default=None 355 The number of GRIB2 messages to read from the current position. If 356 no argument is give, the default value is None and remainder of 357 the file is read. 358 359 Returns 360 ------- 361 read 362 ``Grib2Message`` object when size = 1 or a list of Grib2Messages 363 when size > 1. 364 """ 365 if size is not None and size < 0: 366 size = None 367 if size is None or size > 1: 368 start = self.tell() 369 stop = self.messages if size is None else start+size 370 if size is None: 371 self.current_message = self.messages-1 372 else: 373 self.current_message += size 374 return self._msgs[slice(start,stop,1)] 375 elif size == 1: 376 self.current_message += 1 377 return self._msgs[self.current_message] 378 else: 379 None 380 381 382 def seek(self, pos: int): 383 """ 384 Set the position within the file in units of GRIB2 messages. 385 386 Parameters 387 ---------- 388 pos 389 The GRIB2 Message number to set the file pointer to. 390 """ 391 if self._hasindex: 392 self._filehandle.seek(self._index['sectionOffset'][0][pos]) 393 self.current_message = pos 394 395 396 def tell(self): 397 """Returns the position of the file in units of GRIB2 Messages.""" 398 return self.current_message 399 400 401 def select(self, **kwargs): 402 """Select GRIB2 messages by `Grib2Message` attributes.""" 403 # TODO: Added ability to process multiple values for each keyword (attribute) 404 idxs = [] 405 nkeys = len(kwargs.keys()) 406 for k,v in kwargs.items(): 407 for m in self._msgs: 408 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 409 idxs = np.array(idxs,dtype=np.int32) 410 return [self._msgs[i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]] 411 412 413 def write(self, msg): 414 """ 415 Writes GRIB2 message object to file. 416 417 Parameters 418 ---------- 419 msg 420 GRIB2 message objects to write to file. 421 """ 422 if isinstance(msg,list): 423 for m in msg: 424 self.write(m) 425 return 426 427 if issubclass(msg.__class__,_Grib2Message): 428 # TODO: We can consider letting pack return packed bytes instead of associating with message object 429 if hasattr(msg,'_msg'): 430 # write already packed bytes 431 self._filehandle.write(msg._msg) 432 else: 433 if msg._signature == msg._generate_signature() and \ 434 msg._data is None and \ 435 hasattr(msg._ondiskarray,'filehandle'): 436 # write unchanged message from input 437 offset = msg._ondiskarray.filehandle.tell() 438 msg._ondiskarray.filehandle.seek(msg._ondiskarray.offset) 439 self._filehandle.write(msg._ondiskarray.filehandle.read(msg.section0[-1])) 440 msg._ondiskarray.filehandle.seek(offset) 441 else: 442 msg.pack() 443 self._filehandle.write(msg._msg) 444 self.size = os.path.getsize(self.name) 445 self.messages += 1 446 else: 447 raise TypeError("msg must be a Grib2Message object.") 448 return 449 450 451 def flush(self): 452 """Flush the file object buffer.""" 453 self._filehandle.flush() 454 455 456 def levels_by_var(self, name: str): 457 """ 458 Return a list of level strings given a variable shortName. 459 460 Parameters 461 ---------- 462 name 463 Grib2Message variable shortName 464 465 Returns 466 ------- 467 levels_by_var 468 A list of unique level strings. 469 """ 470 return list(sorted(set([msg.level for msg in self.select(shortName=name)]))) 471 472 473 def vars_by_level(self, level: str): 474 """ 475 Return a list of variable shortName strings given a level. 476 477 Parameters 478 ---------- 479 level 480 Grib2Message variable level 481 482 Returns 483 ------- 484 vars_by_level 485 A list of unique variable shortName strings. 486 """ 487 return list(sorted(set([msg.shortName for msg in self.select(level=level)])))
GRIB2 File Object.
This class can accommodate a physical file with one or more GRIB2 messages or a bytes object containing a GRIB2 messages.
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):
Trueis file handle is close;Falseotherwise. - current_message (int): Current position of the file in units of GRIB2 Messages. (read only)
- 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.
- save_index (bool):
Whether to save a pickle-based index file for the GRIB2 file. Default is
True. - size (int): Size of the file in units of bytes.
- use_index: Whether to use an existing pickle-based index file for the GRIB2 file. Default is
True. - variables (tuple): Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
163 def __init__( 164 self, 165 filename: Union[bytes, str, Path], 166 mode: Literal["r", "w", "x"] = "r", 167 *, 168 save_index = True, 169 use_index = True, 170 _xarray_backend = False, 171 **kwargs, 172 ): 173 """ 174 Initialize GRIB2 File object instance. 175 176 Parameters 177 ---------- 178 filename 179 File path containing GRIB2 messages OR bytes. 180 mode 181 File access mode where "r" opens the files for reading only; "w" 182 opens the file for overwriting and "x" for writing to a new file. 183 save_index 184 Whether to save a pickle-based index file for the GRIB2 file. Default is True. 185 186 .. versionadded:: 2.6.0 187 use_index 188 Whether to use an existing pickle-based index file for the GRIB2 file. Default is True. 189 190 .. versionadded:: 2.6.0 191 """ 192 193 # All write modes are read/write. 194 # All modes are binary. 195 if mode in ("a", "x", "w"): 196 mode += "+" 197 mode = mode + "b" 198 199 self.mode = mode 200 self.save_index = save_index 201 self.use_index = use_index 202 203 if isinstance(filename, bytes): 204 if 'r' not in self.mode: 205 raise ValueError("bytes grib2 can only be opened as read") 206 self.current_message = 0 207 if filename[:2] == _GZIP_HEADER: 208 filename = gzip.decompress(filename) 209 if filename[:4]+filename[-4:] != b'GRIB7777': 210 raise ValueError("Invalid GRIB bytes") 211 self._filehandle = BytesIO(filename) 212 self.name = "<in-memory-file>" 213 self.size = len(filename) 214 self._fileid = hashlib.sha1((self.name+str(self.size)).encode('ASCII')).hexdigest() 215 self._index = build_index(self._filehandle) 216 self._msgs = msgs_from_index(self._index, filehandle=self._filehandle) 217 self.messages = len(self._msgs) 218 219 else: 220 self.current_message = 0 221 if 'r' in mode: 222 self._filehandle = builtins.open(filename, mode=mode) 223 # Some GRIB2 files are gzipped, so check for that here, but 224 # raise error when using xarray backend. 225 # Gzip files contain a 2-byte header b'\x1f\x8b'. 226 if self._filehandle.read(2) == _GZIP_HEADER: 227 self._filehandle.close() 228 if _xarray_backend: 229 raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.') 230 self._filehandle = gzip.open(filename, mode=mode) 231 else: 232 self._filehandle = builtins.open(filename, mode=mode) 233 else: 234 self._filehandle = builtins.open(filename, mode=mode) 235 self.name = os.path.abspath(filename) 236 self.name = os.path.abspath(filename) 237 fstat = os.stat(self.name) 238 self.size = fstat.st_size 239 self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+ 240 str(self.size)).encode('ASCII')).hexdigest() 241 242 if 'r' in self.mode: 243 244 indexfile = f"{self.name}_{self._fileid}.grib2ioidx" 245 if self.use_index and os.path.exists(indexfile): 246 try: 247 with builtins.open(indexfile, 'rb') as file: 248 index = pickle.load(file) 249 self._index = index 250 except Exception as e: 251 print(f"found indexfile: {indexfile}, but unable to load it: {e}") 252 print(f"re-forming index from grib2file, but not writing indexfile") 253 self._index = build_index(self._filehandle) 254 else: 255 self._index = build_index(self._filehandle) 256 if self.save_index: 257 try: 258 serialize_index(self._index, indexfile) 259 except Exception as e: 260 print(f"index was not serialized for future use: {e}") 261 262 self._msgs = msgs_from_index(self._index, filehandle=self._filehandle) 263 264 self.messages = len(self._msgs) 265 elif 'w' or 'x' in self.mode: 266 self.messages = 0 267 self.current_message = None 268 269 self.closed = self._filehandle.closed
Initialize GRIB2 File object instance.
Parameters
- filename: File path containing GRIB2 messages OR bytes.
- mode: File access mode where "r" opens the files for reading only; "w" opens the file for overwriting and "x" for writing to a new file.
- save_index: Whether to save a pickle-based index file for the GRIB2 file. Default is True.
New in version 2.6.0.
- use_index: Whether to use an existing pickle-based index file for the GRIB2 file. Default is True.
New in version 2.6.0.
316 @property 317 def levels(self): 318 """Provides a unique tuple of level strings.""" 319 if self._hasindex: 320 return tuple(sorted(set([msg.level for msg in self._msgs]))) 321 else: 322 return None
Provides a unique tuple of level strings.
325 @property 326 def variables(self): 327 """Provides a unique tuple of variable shortName strings.""" 328 if self._hasindex: 329 return tuple(sorted(set([msg.shortName for msg in self._msgs]))) 330 else: 331 return None
Provides a unique tuple of variable shortName strings.
334 def close(self): 335 """Close the file handle.""" 336 if not self._filehandle.closed: 337 self.messages = 0 338 self.current_message = 0 339 self._filehandle.close() 340 self.closed = self._filehandle.closed
Close the file handle.
343 def read(self, size: Optional[int]=None): 344 """ 345 Read size amount of GRIB2 messages from the current position. 346 347 If no argument is given, then size is None and all messages are returned 348 from the current position in the file. This read method follows the 349 behavior of Python's builtin open() function, but whereas that operates 350 on units of bytes, we operate on units of GRIB2 messages. 351 352 Parameters 353 ---------- 354 size: default=None 355 The number of GRIB2 messages to read from the current position. If 356 no argument is give, the default value is None and remainder of 357 the file is read. 358 359 Returns 360 ------- 361 read 362 ``Grib2Message`` object when size = 1 or a list of Grib2Messages 363 when size > 1. 364 """ 365 if size is not None and size < 0: 366 size = None 367 if size is None or size > 1: 368 start = self.tell() 369 stop = self.messages if size is None else start+size 370 if size is None: 371 self.current_message = self.messages-1 372 else: 373 self.current_message += size 374 return self._msgs[slice(start,stop,1)] 375 elif size == 1: 376 self.current_message += 1 377 return self._msgs[self.current_message] 378 else: 379 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:
Grib2Messageobject when size = 1 or a list of Grib2Messages when size > 1.
382 def seek(self, pos: int): 383 """ 384 Set the position within the file in units of GRIB2 messages. 385 386 Parameters 387 ---------- 388 pos 389 The GRIB2 Message number to set the file pointer to. 390 """ 391 if self._hasindex: 392 self._filehandle.seek(self._index['sectionOffset'][0][pos]) 393 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.
396 def tell(self): 397 """Returns the position of the file in units of GRIB2 Messages.""" 398 return self.current_message
Returns the position of the file in units of GRIB2 Messages.
401 def select(self, **kwargs): 402 """Select GRIB2 messages by `Grib2Message` attributes.""" 403 # TODO: Added ability to process multiple values for each keyword (attribute) 404 idxs = [] 405 nkeys = len(kwargs.keys()) 406 for k,v in kwargs.items(): 407 for m in self._msgs: 408 if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum) 409 idxs = np.array(idxs,dtype=np.int32) 410 return [self._msgs[i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]
Select GRIB2 messages by Grib2Message attributes.
413 def write(self, msg): 414 """ 415 Writes GRIB2 message object to file. 416 417 Parameters 418 ---------- 419 msg 420 GRIB2 message objects to write to file. 421 """ 422 if isinstance(msg,list): 423 for m in msg: 424 self.write(m) 425 return 426 427 if issubclass(msg.__class__,_Grib2Message): 428 # TODO: We can consider letting pack return packed bytes instead of associating with message object 429 if hasattr(msg,'_msg'): 430 # write already packed bytes 431 self._filehandle.write(msg._msg) 432 else: 433 if msg._signature == msg._generate_signature() and \ 434 msg._data is None and \ 435 hasattr(msg._ondiskarray,'filehandle'): 436 # write unchanged message from input 437 offset = msg._ondiskarray.filehandle.tell() 438 msg._ondiskarray.filehandle.seek(msg._ondiskarray.offset) 439 self._filehandle.write(msg._ondiskarray.filehandle.read(msg.section0[-1])) 440 msg._ondiskarray.filehandle.seek(offset) 441 else: 442 msg.pack() 443 self._filehandle.write(msg._msg) 444 self.size = os.path.getsize(self.name) 445 self.messages += 1 446 else: 447 raise TypeError("msg must be a Grib2Message object.") 448 return
Writes GRIB2 message object to file.
Parameters
- msg: GRIB2 message objects to write to file.
456 def levels_by_var(self, name: str): 457 """ 458 Return a list of level strings given a variable shortName. 459 460 Parameters 461 ---------- 462 name 463 Grib2Message variable shortName 464 465 Returns 466 ------- 467 levels_by_var 468 A list of unique level strings. 469 """ 470 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.
473 def vars_by_level(self, level: str): 474 """ 475 Return a list of variable shortName strings given a level. 476 477 Parameters 478 ---------- 479 level 480 Grib2Message variable level 481 482 Returns 483 ------- 484 vars_by_level 485 A list of unique variable shortName strings. 486 """ 487 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.
34def show_config(): 35 """Print grib2io build configuration information.""" 36 print(f'grib2io version {__version__} Configuration:') 37 print(f'') 38 print(f'NCEPLIBS-g2c library version: {__g2clib_version__}') 39 print(f'\tStatic library: {g2c_static}') 40 print(f'\tJPEG compression support: {has_jpeg_support}') 41 print(f'\tPNG compression support: {has_png_support}') 42 print(f'\tAEC compression support: {has_aec_support}') 43 print(f'') 44 print(f'NCEPLIPS-ip support: {has_interpolation}') 45 print(f'\tStatic library: {ip_static}') 46 print(f'\tOpenMP support: {has_openmp_support}') 47 print(f'') 48 print(f'Static libs:') 49 for lib in extra_objects: 50 print(f'\t{lib}') 51 print(f'') 52 print(f'NCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
Print grib2io build configuration information.
2030def interpolate(a, 2031 method: Union[int, str], 2032 grid_def_in, 2033 grid_def_out, 2034 method_options=None, 2035 num_threads=1, 2036): 2037 """ 2038 This is the module-level interpolation function. 2039 2040 This interfaces with the 4-byte (32-bit float) [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip) 2041 through grib2io's internal iplib Cython extension module. 2042 2043 Parameters 2044 ---------- 2045 a : numpy.ndarray or tuple 2046 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 2047 performed. If `a` is a `tuple`, then vector interpolation will be 2048 performed with the assumption that u = a[0] and v = a[1] and are both 2049 `numpy.ndarray`. 2050 2051 These data are expected to be in 2-dimensional form with shape (ny, nx) 2052 or 3-dimensional (:, ny, nx) where the 1st dimension represents another 2053 spatial, temporal, or classification (i.e. ensemble members) dimension. 2054 The function will properly flatten the (ny,nx) dimensions into (nx * ny) 2055 acceptable for input into the interpolation subroutines. If needed, these 2056 data will be converted to `np.float32`. 2057 method 2058 Interpolate method to use. This can either be an integer or string using 2059 the following mapping: 2060 2061 | Interpolate Scheme | Integer Value | 2062 | :---: | :---: | 2063 | 'bilinear' | 0 | 2064 | 'bicubic' | 1 | 2065 | 'neighbor' | 2 | 2066 | 'budget' | 3 | 2067 | 'spectral' | 4 | 2068 | 'neighbor-budget' | 6 | 2069 2070 grid_def_in : grib2io.Grib2GridDef 2071 Grib2GridDef object for the input grid. 2072 grid_def_out : grib2io.Grib2GridDef 2073 Grib2GridDef object for the output grid or station points. 2074 method_options : list of ints, optional 2075 Interpolation options. See the NCEPLIBS-ip documentation for 2076 more information on how these are used. 2077 num_threads : int, optional 2078 Number of OpenMP threads to use for interpolation. The default 2079 value is 1. If NCEPLIBS-ip and grib2io's iplib extension module 2080 was not built with OpenMP, then this keyword argument and value 2081 will have no impact. 2082 2083 Returns 2084 ------- 2085 interpolate 2086 Returns a `numpy.ndarray` of dtype `np.float32` when scalar interpolation 2087 is performed or a `tuple` of `numpy.ndarray`s when vector interpolation is 2088 performed with the assumptions that 0-index is the interpolated u and 2089 1-index is the interpolated v. 2090 """ 2091 2092 from . import iplib 2093 2094 prev_num_threads = 1 2095 try: 2096 prev_num_threads = iplib.openmp_get_num_threads() 2097 iplib.openmp_set_num_threads(num_threads) 2098 except(AttributeError): 2099 pass 2100 2101 print(f"grib2io.interpolate thread report: OpenMP num threads = {iplib.openmp_get_num_threads()}") 2102 2103 if isinstance(method,int) and method not in _interp_schemes.values(): 2104 raise ValueError('Invalid interpolation method.') 2105 elif isinstance(method,str): 2106 if method in _interp_schemes.keys(): 2107 method = _interp_schemes[method] 2108 else: 2109 raise ValueError('Invalid interpolation method.') 2110 2111 if method_options is None: 2112 method_options = np.zeros((20),dtype=np.int32) 2113 if method in {3,6}: 2114 method_options[0:2] = -1 2115 2116 mi = grid_def_in.npoints 2117 mo = grid_def_out.npoints 2118 2119 # Adjust shape of input array(s) 2120 a, newshp = _adjust_array_shape_for_interp(a, grid_def_in, grid_def_out) 2121 2122 # Call interpolation subroutines according to type of a. 2123 if isinstance(a,np.ndarray): 2124 # Scalar 2125 km = a.shape[0] 2126 if np.any(np.isnan(a)): 2127 ibi = np.ones((km), dtype=np.int32) 2128 li = np.where(np.isnan(a),0,1).astype(np.uint8) 2129 else: 2130 ibi = np.zeros((km), dtype=np.int32) 2131 li = np.zeros(a.shape,dtype=np.uint8) 2132 no, rlat, rlon, ibo, lo, go, iret = iplib.interpolate_scalar( 2133 method, 2134 method_options, 2135 grid_def_in.gdtn, 2136 np.array(grid_def_in.gdt, dtype=np.int32), 2137 grid_def_out.gdtn, 2138 np.array(grid_def_out.gdt, dtype=np.int32), 2139 mi, 2140 mo, 2141 km, 2142 ibi, 2143 li, 2144 a, 2145 ) 2146 out = np.where(lo==0,np.nan,go).reshape(newshp) 2147 elif isinstance(a,tuple): 2148 # Vector 2149 km = a[0].shape[0] 2150 if np.any(np.isnan(a)): 2151 ibi = np.ones((km), dtype=np.int32) 2152 li = np.where(np.isnan(a),0,1).astype(np.uint8) 2153 else: 2154 ibi = np.zeros((km), dtype=np.int32) 2155 li = np.zeros(a[0].shape,dtype=np.uint8) 2156 no, rlat, rlon, crot, srot, ibo, lo, uo, vo, iret = iplib.interpolate_vector( 2157 method, 2158 method_options, 2159 grid_def_in.gdtn, 2160 np.array(grid_def_in.gdt, dtype=np.int32), 2161 grid_def_out.gdtn, 2162 np.array(grid_def_out.gdt, dtype=np.int32), 2163 mi, 2164 mo, 2165 km, 2166 ibi, 2167 li, 2168 a[0], 2169 a[1], 2170 ) 2171 uo = np.where(lo==0,np.nan,uo).reshape(newshp) 2172 vo = np.where(lo==0,np.nan,vo).reshape(newshp) 2173 out = (uo,vo) 2174 2175 try: 2176 iplib.openmp_set_num_threads(prev_num_threads) 2177 except(AttributeError): 2178 pass 2179 2180 return out
This is the module-level interpolation function.
This interfaces with the 4-byte (32-bit float) NCEPLIBS-ip library through grib2io's internal iplib Cython extension module.
Parameters
a (numpy.ndarray or tuple): Input data. If
ais anumpy.ndarray, scalar interpolation will be performed. Ifais 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. If needed, these data will be converted to
np.float32.- 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 NCEPLIBS-ip and grib2io's iplib extension module was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
- interpolate: Returns a
numpy.ndarrayof dtypenp.float32when scalar interpolation is performed or atupleofnumpy.ndarrays when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
2183def interpolate_to_stations( 2184 a, 2185 method: Union[int, str], 2186 grid_def_in, 2187 lats, 2188 lons, 2189 method_options=None, 2190 num_threads=1 2191): 2192 """ 2193 Module-level interpolation function for interpolation to stations. 2194 2195 Interfaces with the 4-byte (32-bit float) [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip) 2196 via grib2io's iplib Cython exntension module. It supports scalar and 2197 vector interpolation according to the type of object a. 2198 2199 Parameters 2200 ---------- 2201 a : numpy.ndarray or tuple 2202 Input data. If `a` is a `numpy.ndarray`, scalar interpolation will be 2203 performed. If `a` is a `tuple`, then vector interpolation will be 2204 performed with the assumption that u = a[0] and v = a[1] and are both 2205 `numpy.ndarray`. 2206 2207 These data are expected to be in 2-dimensional form with shape (ny, nx) 2208 or 3-dimensional (:, ny, nx) where the 1st dimension represents another 2209 spatial, temporal, or classification (i.e. ensemble members) dimension. 2210 The function will properly flatten the (ny,nx) dimensions into (nx * ny) 2211 acceptable for input into the interpolation subroutines. If needed, these 2212 data will be converted to `np.float32`. 2213 method 2214 Interpolate method to use. This can either be an integer or string using 2215 the following mapping: 2216 2217 | Interpolate Scheme | Integer Value | 2218 | :---: | :---: | 2219 | 'bilinear' | 0 | 2220 | 'bicubic' | 1 | 2221 | 'neighbor' | 2 | 2222 | 'budget' | 3 | 2223 | 'spectral' | 4 | 2224 | 'neighbor-budget' | 6 | 2225 2226 grid_def_in : grib2io.Grib2GridDef 2227 Grib2GridDef object for the input grid. 2228 lats : numpy.ndarray or list 2229 Latitudes for station points 2230 lons : numpy.ndarray or list 2231 Longitudes for station points 2232 method_options : list of ints, optional 2233 Interpolation options. See the NCEPLIBS-ip documentation for 2234 more information on how these are used. 2235 num_threads : int, optional 2236 Number of OpenMP threads to use for interpolation. The default 2237 value is 1. If NCEPLIBS-ip and grib2io's iplib extension module 2238 was not built with OpenMP, then this keyword argument and value 2239 will have no impact. 2240 2241 Returns 2242 ------- 2243 interpolate_to_stations 2244 Returns a `numpy.ndarray` of dtype `np.float32` when scalar 2245 interpolation is performed or a `tuple` of `numpy.ndarray`s 2246 when vector interpolation is performed with the assumptions 2247 that 0-index is the interpolated u and 1-index is the 2248 interpolated v. 2249 """ 2250 from . import iplib 2251 2252 prev_num_threads = 1 2253 try: 2254 prev_num_threads = iplib.openmp_get_num_threads() 2255 iplib.openmp_set_num_threads(num_threads) 2256 except(AttributeError): 2257 pass 2258 2259 if isinstance(method,int) and method not in _interp_schemes.values(): 2260 raise ValueError('Invalid interpolation method.') 2261 elif isinstance(method,str): 2262 if method in _interp_schemes.keys(): 2263 method = _interp_schemes[method] 2264 else: 2265 raise ValueError('Invalid interpolation method.') 2266 2267 if method_options is None: 2268 method_options = np.zeros((20),dtype=np.int32) 2269 if method in {3,6}: 2270 method_options[0:2] = -1 2271 2272 # Check lats and lons 2273 if isinstance(lats,list): 2274 nlats = len(lats) 2275 elif isinstance(lats,np.ndarray) and len(lats.shape) == 1: 2276 nlats = lats.shape[0] 2277 else: 2278 raise ValueError('Station latitudes must be a list or 1-D NumPy array.') 2279 if isinstance(lons,list): 2280 nlons = len(lons) 2281 elif isinstance(lons,np.ndarray) and len(lons.shape) == 1: 2282 nlons = lons.shape[0] 2283 else: 2284 raise ValueError('Station longitudes must be a list or 1-D NumPy array.') 2285 if nlats != nlons: 2286 raise ValueError('Station lats and lons must be same size.') 2287 2288 mi = grid_def_in.npoints 2289 mo = nlats 2290 2291 # Adjust shape of input array(s) 2292 a, newshp = _adjust_array_shape_for_interp_stations(a, grid_def_in, mo) 2293 2294 # Use gdtn = -1 for stations and an empty template array 2295 gdtn = -1 2296 gdt = np.zeros((200),dtype=np.int32) 2297 2298 # Call interpolation subroutines according to type of a. 2299 if isinstance(a,np.ndarray): 2300 # Scalar 2301 km = a.shape[0] 2302 ibi = np.zeros((km), dtype=np.int32) 2303 li = np.zeros(a.shape,dtype=np.uint8) 2304 no, rlat, rlon, ibo, lo, go, iret = iplib.interpolate_scalar( 2305 method, 2306 method_options, 2307 grid_def_in.gdtn, 2308 np.array(grid_def_in.gdt, dtype=np.int32), 2309 gdtn, 2310 gdt, 2311 mi, 2312 mo, 2313 km, 2314 ibi, 2315 li, 2316 a, 2317 lats=np.array(lats, dtype=np.float32), 2318 lons=np.array(lons, dtype=np.float32), 2319 ) 2320 out = go.reshape(newshp) 2321 2322 elif isinstance(a,tuple): 2323 # Vector 2324 km = a[0].shape[0] 2325 ibi = np.zeros((km), dtype=np.int32) 2326 li = np.zeros(a[0].shape,dtype=np.uint8) 2327 no, rlat, rlon, crot, srot, ibo, lo, uo, vo, iret = iplib.interpolate_vector( 2328 method, 2329 method_options, 2330 grid_def_in.gdtn, 2331 np.array(grid_def_in.gdt, dtype=np.int32), 2332 gdtn, 2333 gdt, 2334 mi, 2335 mo, 2336 km, 2337 ibi, 2338 li, 2339 a[0], 2340 a[1], 2341 lats=np.array(lats, dtype=np.float32), 2342 lons=np.array(lons, dtype=np.float32), 2343 ) 2344 out = (uo.reshape(newshp),vo.reshape(newshp)) 2345 2346 try: 2347 iplib.openmp_set_num_threads(prev_num_threads) 2348 except(AttributeError): 2349 pass 2350 2351 return out
Module-level interpolation function for interpolation to stations.
Interfaces with the 4-byte (32-bit float) NCEPLIBS-ip library via grib2io's iplib Cython exntension module. It supports scalar and vector interpolation according to the type of object a.
Parameters
a (numpy.ndarray or tuple): Input data. If
ais anumpy.ndarray, scalar interpolation will be performed. Ifais 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. If needed, these data will be converted to
np.float32.- 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 NCEPLIBS-ip and grib2io's iplib extension module was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
- interpolate_to_stations: Returns a
numpy.ndarrayof dtypenp.float32when scalar interpolation is performed or atupleofnumpy.ndarrays when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
834class Grib2Message: 835 """ 836 Creation class for a GRIB2 message. 837 838 This class returns a dynamically-created Grib2Message object that 839 inherits from `_Grib2Message` and grid, product, data representation 840 template classes according to the template numbers for the respective 841 sections. If `section3`, `section4`, or `section5` are omitted, then 842 the appropriate keyword arguments for the template number `gdtn=`, 843 `pdtn=`, or `drtn=` must be provided. 844 845 Parameters 846 ---------- 847 section0 848 GRIB2 section 0 array. 849 section1 850 GRIB2 section 1 array. 851 section2 852 Local Use section data. 853 section3 854 GRIB2 section 3 array. 855 section4 856 GRIB2 section 4 array. 857 section5 858 GRIB2 section 5 array. 859 860 Returns 861 ------- 862 Msg 863 A dynamically-create Grib2Message object that inherits from 864 _Grib2Message, a grid definition template class, product 865 definition template class, and a data representation template 866 class. 867 """ 868 def __new__(self, section0: NDArray = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]), 869 section1: NDArray = np.zeros((13),dtype=np.int64), 870 section2: Optional[bytes] = None, 871 section3: Optional[NDArray] = None, 872 section4: Optional[NDArray] = None, 873 section5: Optional[NDArray] = None, *args, **kwargs): 874 875 if np.all(section1==0): 876 try: 877 # Python >= 3.10 878 section1[5:11] = datetime.datetime.fromtimestamp(0, datetime.UTC).timetuple()[:6] 879 except(AttributeError): 880 # Python < 3.10 881 section1[5:11] = datetime.datetime.utcfromtimestamp(0).timetuple()[:6] 882 883 bases = list() 884 if section3 is None: 885 if 'gdtn' in kwargs.keys(): 886 gdtn = kwargs['gdtn'] 887 Gdt = templates.gdt_class_by_gdtn(gdtn) 888 bases.append(Gdt) 889 section3 = np.zeros((Gdt._len+5),dtype=np.int64) 890 section3[4] = gdtn 891 else: 892 raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array") 893 else: 894 gdtn = section3[4] 895 Gdt = templates.gdt_class_by_gdtn(gdtn) 896 bases.append(Gdt) 897 898 if section4 is None: 899 if 'pdtn' in kwargs.keys(): 900 pdtn = kwargs['pdtn'] 901 Pdt = templates.pdt_class_by_pdtn(pdtn) 902 bases.append(Pdt) 903 section4 = np.zeros((Pdt._len+2),dtype=np.int64) 904 section4[1] = pdtn 905 else: 906 raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array") 907 else: 908 pdtn = section4[1] 909 Pdt = templates.pdt_class_by_pdtn(pdtn) 910 bases.append(Pdt) 911 912 if section5 is None: 913 if 'drtn' in kwargs.keys(): 914 drtn = kwargs['drtn'] 915 Drt = templates.drt_class_by_drtn(drtn) 916 bases.append(Drt) 917 section5 = np.zeros((Drt._len+2),dtype=np.int64) 918 section5[1] = drtn 919 else: 920 raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array") 921 else: 922 drtn = section5[1] 923 Drt = templates.drt_class_by_drtn(drtn) 924 bases.append(Drt) 925 926 # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo 927 try: 928 Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] 929 except KeyError: 930 @dataclass(init=False, repr=False) 931 class Msg(_Grib2Message, *bases): 932 pass 933 _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg 934 935 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.
938@dataclass 939class _Grib2Message: 940 """ 941 GRIB2 Message base class. 942 """ 943 # GRIB2 Sections 944 section0: NDArray = field(init=True,repr=False) 945 section1: NDArray = field(init=True,repr=False) 946 section2: bytes = field(init=True,repr=False) 947 section3: NDArray = field(init=True,repr=False) 948 section4: NDArray = field(init=True,repr=False) 949 section5: NDArray = field(init=True,repr=False) 950 bitMapFlag: templates.Grib2Metadata = field(init=True,repr=False,default=255) 951 952 # Section 0 looked up attributes 953 indicatorSection: NDArray = field(init=False,repr=False,default=templates.IndicatorSection()) 954 discipline: templates.Grib2Metadata = field(init=False,repr=False,default=templates.Discipline()) 955 956 # Section 1 looked up attributes 957 identificationSection: NDArray = field(init=False,repr=False,default=templates.IdentificationSection()) 958 originatingCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingCenter()) 959 originatingSubCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingSubCenter()) 960 masterTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.MasterTableInfo()) 961 localTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.LocalTableInfo()) 962 significanceOfReferenceTime: templates.Grib2Metadata = field(init=False,repr=False,default=templates.SignificanceOfReferenceTime()) 963 year: int = field(init=False,repr=False,default=templates.Year()) 964 month: int = field(init=False,repr=False,default=templates.Month()) 965 day: int = field(init=False,repr=False,default=templates.Day()) 966 hour: int = field(init=False,repr=False,default=templates.Hour()) 967 minute: int = field(init=False,repr=False,default=templates.Minute()) 968 second: int = field(init=False,repr=False,default=templates.Second()) 969 refDate: datetime.datetime = field(init=False,repr=False,default=templates.RefDate()) 970 productionStatus: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductionStatus()) 971 typeOfData: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfData()) 972 973 # Section 3 looked up common attributes. Other looked up attributes are available according 974 # to the Grid Definition Template. 975 gridDefinitionSection: NDArray = field(init=False,repr=False,default=templates.GridDefinitionSection()) 976 sourceOfGridDefinition: int = field(init=False,repr=False,default=templates.SourceOfGridDefinition()) 977 numberOfDataPoints: int = field(init=False,repr=False,default=templates.NumberOfDataPoints()) 978 interpretationOfListOfNumbers: templates.Grib2Metadata = field(init=False,repr=False,default=templates.InterpretationOfListOfNumbers()) 979 gridDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.GridDefinitionTemplateNumber()) 980 gridDefinitionTemplate: list = field(init=False,repr=False,default=templates.GridDefinitionTemplate()) 981 _earthparams: dict = field(init=False,repr=False,default=templates.EarthParams()) 982 _dxsign: float = field(init=False,repr=False,default=templates.DxSign()) 983 _dysign: float = field(init=False,repr=False,default=templates.DySign()) 984 _llscalefactor: float = field(init=False,repr=False,default=templates.LLScaleFactor()) 985 _lldivisor: float = field(init=False,repr=False,default=templates.LLDivisor()) 986 _xydivisor: float = field(init=False,repr=False,default=templates.XYDivisor()) 987 shapeOfEarth: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ShapeOfEarth()) 988 earthShape: str = field(init=False,repr=False,default=templates.EarthShape()) 989 earthRadius: float = field(init=False,repr=False,default=templates.EarthRadius()) 990 earthMajorAxis: float = field(init=False,repr=False,default=templates.EarthMajorAxis()) 991 earthMinorAxis: float = field(init=False,repr=False,default=templates.EarthMinorAxis()) 992 resolutionAndComponentFlags: list = field(init=False,repr=False,default=templates.ResolutionAndComponentFlags()) 993 ny: int = field(init=False,repr=False,default=templates.Ny()) 994 nx: int = field(init=False,repr=False,default=templates.Nx()) 995 scanModeFlags: list = field(init=False,repr=False,default=templates.ScanModeFlags()) 996 projParameters: dict = field(init=False,repr=False,default=templates.ProjParameters()) 997 998 # Section 4 999 productDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductDefinitionTemplateNumber()) 1000 productDefinitionTemplate: NDArray = field(init=False,repr=False,default=templates.ProductDefinitionTemplate()) 1001 1002 # Section 5 looked up common attributes. Other looked up attributes are 1003 # available according to the Data Representation Template. 1004 numberOfPackedValues: int = field(init=False,repr=False,default=templates.NumberOfPackedValues()) 1005 dataRepresentationTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.DataRepresentationTemplateNumber()) 1006 dataRepresentationTemplate: list = field(init=False,repr=False,default=templates.DataRepresentationTemplate()) 1007 typeOfValues: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfValues()) 1008 1009 def __copy__(self): 1010 """Shallow copy""" 1011 new = Grib2Message( 1012 self.section0, 1013 self.section1, 1014 self.section2, 1015 self.section3, 1016 self.section4, 1017 drtn=self.drtn, 1018 ) 1019 return new 1020 1021 def __deepcopy__(self, memo): 1022 """Deep copy""" 1023 new = Grib2Message( 1024 np.copy(self.section0), 1025 np.copy(self.section1), 1026 copy.deepcopy(self.section2), 1027 np.copy(self.section3), 1028 np.copy(self.section4), 1029 np.copy(self.section5), 1030 ) 1031 memo[id(self)] = new 1032 new.data = np.copy(self.data) 1033 new.bitmap = None if self.bitmap is None else np.copy(self.bitmap) 1034 return new 1035 1036 def __post_init__(self): 1037 """Set some attributes after init.""" 1038 self._auto_nans = _AUTO_NANS 1039 self._coordlist = np.zeros((0), dtype=np.float32) 1040 self._data = None 1041 self._deflist = np.zeros((0), dtype=np.int64) 1042 self._msgnum = -1 1043 self._ondiskarray = None 1044 self._orig_section5 = np.copy(self.section5) 1045 self._signature = self._generate_signature() 1046 try: 1047 self._sha1_section3 = hashlib.sha1(self.section3).hexdigest() 1048 except(TypeError): 1049 pass 1050 self.bitMapFlag = templates.Grib2Metadata(self.bitMapFlag,table='6.0') 1051 self.bitmap = None 1052 1053 @property 1054 def _isNDFD(self): 1055 """Check if GRIB2 message is from NWS NDFD""" 1056 return np.all(self.section1[0:2]==[8,65535]) 1057 1058 @property 1059 def _isAerosol(self): 1060 """Check if GRIB2 message contains aerosol data""" 1061 is_aero_template = self.productDefinitionTemplateNumber.value in tables.AEROSOL_PDTNS 1062 is_aero_param = ((str(self.parameterCategory) == '13') | 1063 (str(self.parameterCategory) == '20')) and str(self.parameterNumber) in tables.AEROSOL_PARAMS 1064 # Check table 4.205 aerosol presence 1065 is_aero_type = (str(self.parameterCategory) == '205' and 1066 str(self.parameterNumber) == '1') 1067 return is_aero_template or is_aero_param or is_aero_type 1068 1069 @property 1070 def gdtn(self): 1071 """Return Grid Definition Template Number""" 1072 return self.section3[4] 1073 1074 1075 @property 1076 def gdt(self): 1077 """Return Grid Definition Template.""" 1078 return self.gridDefinitionTemplate 1079 1080 1081 @property 1082 def pdtn(self): 1083 """Return Product Definition Template Number.""" 1084 return self.section4[1] 1085 1086 1087 @property 1088 def pdt(self): 1089 """Return Product Definition Template.""" 1090 return self.productDefinitionTemplate 1091 1092 1093 @property 1094 def drtn(self): 1095 """Return Data Representation Template Number.""" 1096 return self.section5[1] 1097 1098 1099 @property 1100 def drt(self): 1101 """Return Data Representation Template.""" 1102 return self.dataRepresentationTemplate 1103 1104 1105 @property 1106 def pdy(self): 1107 """Return the PDY ('YYYYMMDD').""" 1108 return ''.join([str(i) for i in self.section1[5:8]]) 1109 1110 1111 @property 1112 def griddef(self): 1113 """Return a Grib2GridDef instance for a GRIB2 message.""" 1114 return Grib2GridDef.from_section3(self.section3) 1115 1116 1117 @property 1118 def lats(self): 1119 """Return grid latitudes.""" 1120 return self.latlons()[0] 1121 1122 1123 @property 1124 def lons(self): 1125 """Return grid longitudes.""" 1126 return self.latlons()[1] 1127 1128 @property 1129 def min(self): 1130 """Return minimum value of data.""" 1131 return np.nanmin(self.data) 1132 1133 1134 @property 1135 def max(self): 1136 """Return maximum value of data.""" 1137 return np.nanmax(self.data) 1138 1139 1140 @property 1141 def mean(self): 1142 """Return mean value of data.""" 1143 return np.nanmean(self.data) 1144 1145 1146 @property 1147 def median(self): 1148 """Return median value of data.""" 1149 return np.nanmedian(self.data) 1150 1151 1152 @property 1153 def shape(self): 1154 """Return shape of data.""" 1155 return self.griddef.shape 1156 1157 1158 def __repr__(self): 1159 """ 1160 Return an unambiguous string representation of the object. 1161 1162 Returns 1163 ------- 1164 repr 1165 A string representation of the object, including information from 1166 sections 0, 1, 3, 4, 5, and 6. 1167 """ 1168 info = '' 1169 for sect in [0,1,3,4,5,6]: 1170 for k,v in self.attrs_by_section(sect,values=True).items(): 1171 info += f'Section {sect}: {k} = {v}\n' 1172 return info 1173 1174 def __str__(self): 1175 """ 1176 Return a readable string representation of the object. 1177 1178 Returns 1179 ------- 1180 str 1181 A formatted string representation of the object, including 1182 selected attributes. 1183 """ 1184 return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:' 1185 f'{self.fullName} ({self.units}):{self.level}:' 1186 f'{self.leadTime}') 1187 1188 1189 def _generate_signature(self): 1190 """Generature SHA-1 hash string from GRIB2 integer sections.""" 1191 return hashlib.sha1(np.concatenate((self.section0,self.section1, 1192 self.section3,self.section4, 1193 self.section5))).hexdigest() 1194 1195 1196 def attrs_by_section(self, sect: int, values: bool=False): 1197 """ 1198 Provide a tuple of attribute names for the given GRIB2 section. 1199 1200 Parameters 1201 ---------- 1202 sect 1203 The GRIB2 section number. 1204 values 1205 Optional (default is `False`) argument to return attributes values. 1206 1207 Returns 1208 ------- 1209 attrs_by_section 1210 A list of attribute names or dict of name:value pairs if `values = 1211 True`. 1212 """ 1213 if sect in {0,1,6}: 1214 attrs = templates._section_attrs[sect] 1215 elif sect in {3,4,5}: 1216 def _find_class_index(n): 1217 _key = {3:'Grid', 4:'Product', 5:'Data'} 1218 for i,c in enumerate(self.__class__.__mro__): 1219 if _key[n] in c.__name__: 1220 return i 1221 else: 1222 return [] 1223 if sys.version_info.minor <= 8: 1224 attrs = templates._section_attrs[sect]+\ 1225 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 1226 else: 1227 attrs = templates._section_attrs[sect]+\ 1228 self.__class__.__mro__[_find_class_index(sect)]._attrs() 1229 else: 1230 attrs = [] 1231 if values: 1232 return {k:getattr(self,k) for k in attrs} 1233 else: 1234 return attrs 1235 1236 1237 def copy(self, deep: bool = True): 1238 """Returns a copy of this Grib2Message. 1239 1240 When `deep=True`, a copy is made of each of the GRIB2 section arrays and 1241 the data are unpacked from the source object and copied into the new 1242 object. Otherwise, a shallow copy of each array is performed and no data 1243 are copied. 1244 1245 Parameters 1246 ---------- 1247 deep : bool, default: True 1248 Whether each GRIB2 section array and data are copied onto 1249 the new object. Default is True. 1250 1251 Returns 1252 ------- 1253 object : Grib2Message 1254 New Grib2Message object. 1255 1256 .. versionadded:: 2.6.0 1257 """ 1258 return copy.deepcopy(self) if deep else copy.copy(self) 1259 1260 1261 def pack(self): 1262 """ 1263 Pack GRIB2 section data into a binary message. 1264 1265 It is the user's responsibility to populate the GRIB2 section 1266 information with appropriate metadata. 1267 """ 1268 # Create beginning of packed binary message with section 0 and 1 data. 1269 self._sections = [] 1270 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 1271 self._sections += [0,1] 1272 1273 # Add section 2 if present. 1274 if isinstance(self.section2,bytes) and len(self.section2) > 0: 1275 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 1276 self._sections.append(2) 1277 1278 # Add section 3. 1279 self.section3[1] = self.nx * self.ny 1280 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 1281 self.gridDefinitionTemplate, 1282 self._deflist) 1283 self._sections.append(3) 1284 1285 # Prepare data. 1286 if self._data is None: 1287 if self._ondiskarray is None: 1288 raise ValueError("Grib2Message object has no data, thus it cannot be packed.") 1289 field = np.copy(self.data) 1290 if self.scanModeFlags is not None: 1291 if self.scanModeFlags[3]: 1292 fieldsave = field.astype('f') # Casting makes a copy 1293 field[1::2,:] = fieldsave[1::2,::-1] 1294 fld = field.astype('f') 1295 1296 # Prepare bitmap, if necessary 1297 bitmapflag = self.bitMapFlag.value 1298 if bitmapflag == 0: 1299 if self.bitmap is not None: 1300 bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT) 1301 else: 1302 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 1303 else: 1304 bmap = None 1305 1306 # Prepare data for packing if nans are present 1307 fld = np.ravel(fld) 1308 if bitmapflag in {0,254}: 1309 fld = np.where(np.isnan(fld),0,fld) 1310 else: 1311 if np.isnan(fld).any(): 1312 if hasattr(self,'priMissingValue'): 1313 fld = np.where(np.isnan(fld),self.priMissingValue,fld) 1314 if hasattr(self,'_missvalmap'): 1315 if hasattr(self,'priMissingValue'): 1316 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 1317 if hasattr(self,'secMissingValue'): 1318 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 1319 1320 # Add sections 4, 5, 6, and 7. 1321 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 1322 self.productDefinitionTemplate, 1323 self._coordlist, 1324 self.drtn, 1325 self.dataRepresentationTemplate, 1326 fld, 1327 bitmapflag, 1328 bmap) 1329 self._sections.append(4) 1330 self._sections.append(5) 1331 self._sections.append(6) 1332 self._sections.append(7) 1333 1334 # Finalize GRIB2 message with section 8. 1335 self._msg, self._pos = g2clib.grib2_end(self._msg) 1336 self._sections.append(8) 1337 self.section0[-1] = len(self._msg) 1338 1339 1340 @property 1341 def data(self) -> np.array: 1342 """Access the unpacked data values.""" 1343 if self._data is None: 1344 if self._auto_nans != _AUTO_NANS: 1345 self._data = self._ondiskarray 1346 self._data = np.asarray(self._ondiskarray) 1347 return self._data 1348 1349 1350 @data.setter 1351 def data(self, arr): 1352 """ 1353 Set the internal data array, enforcing shape (ny, nx) and dtype float32. 1354 1355 If the Grid Definition Section (section 3) of Grib2Message object is 1356 not fully formed (i.e. nx, ny = 0, 0), then the shape of the data array 1357 will be used to set nx and ny of the Grib2Message object. It will be the 1358 responsibility of the user to populate the rest of the Grid Definition 1359 Section attributes. 1360 1361 Parameters 1362 ---------- 1363 arr : array_like 1364 A 2D array whose shape must match ``(self.ny, self.nx)``. 1365 It will be converted to ``float32`` and C-contiguous if needed. 1366 1367 Raises 1368 ------ 1369 ValueError 1370 If the shape of `arr` does not match the expected dimensions. 1371 """ 1372 if not isinstance(arr, np.ndarray): 1373 raise ValueError(f"Grib2Message data only supports numpy arrays") 1374 if self.nx == 0 and self.ny == 0: 1375 self.ny = arr.shape[0] 1376 self.nx = arr.shape[1] 1377 if arr.shape != (self.ny, self.nx): 1378 raise ValueError( 1379 f"Data shape mismatch: expected ({self.ny}, {self.nx}), " 1380 f"got {arr.shape}" 1381 ) 1382 # Ensure contiguous memory layout (important for C interoperability) 1383 if not arr.flags["C_CONTIGUOUS"]: 1384 arr = np.ascontiguousarray(arr, dtype=np.float32) 1385 self._data = arr 1386 1387 1388 def flush_data(self): 1389 """ 1390 Flush the unpacked data values from the Grib2Message object. 1391 1392 Notes 1393 ----- 1394 If the Grib2Message object was constructed from "scratch" (i.e. 1395 not read from file), this method will remove the data array from 1396 the object and it cannot be recovered. 1397 """ 1398 self._data = None 1399 self.bitmap = None 1400 1401 1402 def __getitem__(self, item): 1403 return self.data[item] 1404 1405 1406 def __setitem__(self, item): 1407 raise NotImplementedError('assignment of data not supported via setitem') 1408 1409 1410 def latlons(self, *args, **kwrgs): 1411 """Alias for `grib2io.Grib2Message.grid` method.""" 1412 return self.grid(*args, **kwrgs) 1413 1414 1415 def grid(self, unrotate: bool=True): 1416 """ 1417 Return lats,lons (in degrees) of grid. 1418 1419 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, 1420 stereographic, lambert conformal, albers equal-area, space-view and 1421 azimuthal equidistant grids. 1422 1423 Parameters 1424 ---------- 1425 unrotate 1426 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the 1427 grid, otherwise `False`, do not. 1428 1429 Returns 1430 ------- 1431 lats, lons : numpy.ndarray 1432 Returns two numpy.ndarrays with dtype=numpy.float32 of grid 1433 latitudes and longitudes in units of degrees. 1434 """ 1435 if self._sha1_section3 in _latlon_datastore.keys(): 1436 return (_latlon_datastore[self._sha1_section3]['latitude'], 1437 _latlon_datastore[self._sha1_section3]['longitude']) 1438 gdtn = self.gridDefinitionTemplateNumber.value 1439 gdtmpl = self.gridDefinitionTemplate 1440 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 1441 if gdtn == 0: 1442 # Regular lat/lon grid 1443 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1444 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1445 dlon = self.gridlengthXDirection 1446 dlat = self.gridlengthYDirection 1447 if lon2 < lon1 and dlon < 0: lon1 = -lon1 1448 lats = np.linspace(lat1,lat2,self.ny) 1449 if reggrid: 1450 lons = np.linspace(lon1,lon2,self.nx) 1451 else: 1452 lons = np.linspace(lon1,lon2,self.ny*2) 1453 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1454 elif gdtn == 1: # Rotated Lat/Lon grid 1455 pj = pyproj.Proj(self.projParameters) 1456 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 1457 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 1458 if lon1 > 180.0: lon1 -= 360.0 1459 if lon2 > 180.0: lon2 -= 360.0 1460 lats = np.linspace(lat1,lat2,self.ny) 1461 lons = np.linspace(lon1,lon2,self.nx) 1462 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1463 if unrotate: 1464 from grib2io.utils import rotated_grid 1465 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 1466 self.latitudeSouthernPole, 1467 self.longitudeSouthernPole) 1468 elif gdtn == 40: # Gaussian grid (only works for global!) 1469 from grib2io.utils.gauss_grid import gaussian_latitudes 1470 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1471 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1472 nlats = self.ny 1473 if not reggrid: # Reduced Gaussian grid. 1474 nlons = 2*nlats 1475 dlon = 360./nlons 1476 else: 1477 nlons = self.nx 1478 dlon = self.gridlengthXDirection 1479 lons = np.linspace(lon1,lon2,nlons) 1480 # Compute Gaussian lats (north to south) 1481 lats = gaussian_latitudes(nlats) 1482 if lat1 < lat2: # reverse them if necessary 1483 lats = lats[::-1] 1484 lons,lats = np.meshgrid(lons,lats) 1485 elif gdtn in {10,20,30,31,110}: 1486 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, 1487 # Azimuthal Equidistant 1488 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 1489 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1490 pj = pyproj.Proj(self.projParameters) 1491 llcrnrx, llcrnry = pj(lon1,lat1) 1492 x = llcrnrx+dx*np.arange(self.nx) 1493 y = llcrnry+dy*np.arange(self.ny) 1494 x,y = np.meshgrid(x, y) 1495 lons,lats = pj(x, y, inverse=True) 1496 elif gdtn == 90: 1497 # Satellite Projection 1498 dx = self.gridlengthXDirection 1499 dy = self.gridlengthYDirection 1500 pj = pyproj.Proj(self.projParameters) 1501 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 1502 x -= 0.5*x.max() 1503 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 1504 y -= 0.5*y.max() 1505 lons,lats = pj(x,y,inverse=True) 1506 # Set lons,lats to 1.e30 where undefined 1507 abslons = np.fabs(lons) 1508 abslats = np.fabs(lats) 1509 lons = np.where(abslons < 1.e20, lons, 1.e30) 1510 lats = np.where(abslats < 1.e20, lats, 1.e30) 1511 elif gdtn == 32769: 1512 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 1513 from grib2io.utils import arakawa_rotated_grid 1514 from grib2io.utils.rotated_grid import DEG2RAD 1515 di, dj = 0.0, 0.0 1516 do_180 = False 1517 idir = 1 if self.scanModeFlags[0] == 0 else -1 1518 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1519 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1520 do_rot = 1 if grid_oriented == 1 else 0 1521 la1 = self.latitudeFirstGridpoint 1522 lo1 = self.longitudeFirstGridpoint 1523 clon = self.longitudeCenterGridpoint 1524 clat = self.latitudeCenterGridpoint 1525 lasp = clat - 90.0 1526 losp = clon 1527 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1528 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1529 rlat = -llat 1530 rlon = -llon 1531 if self.nx == 1: 1532 di = 0.0 1533 elif idir == 1: 1534 ti = rlon 1535 while ti < llon: 1536 ti += 360.0 1537 di = (ti - llon)/float(self.nx-1) 1538 else: 1539 ti = llon 1540 while ti < rlon: 1541 ti += 360.0 1542 di = (ti - rlon)/float(self.nx-1) 1543 if self.ny == 1: 1544 dj = 0.0 1545 else: 1546 dj = (rlat - llat)/float(self.ny-1) 1547 if dj < 0.0: 1548 dj = -dj 1549 if idir == 1: 1550 if llon > rlon: 1551 llon -= 360.0 1552 if llon < 0 and rlon > 0: 1553 do_180 = True 1554 else: 1555 if rlon > llon: 1556 rlon -= 360.0 1557 if rlon < 0 and llon > 0: 1558 do_180 = True 1559 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1560 xlon1d = llon + (np.arange(self.nx)*idir*di) 1561 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1562 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1563 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1564 if do_180: 1565 lons = np.where(lons>180.0,lons-360.0,lons) 1566 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1567 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1568 del xlat1d, xlon1d, xlats, xlons 1569 else: 1570 raise ValueError('Unsupported grid') 1571 1572 _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons) 1573 try: 1574 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1575 except(NameError): 1576 pass 1577 1578 return lats, lons 1579 1580 1581 def map_keys(self): 1582 """ 1583 Unpack data grid replacing integer values with strings. 1584 1585 These types of fields are categorical or classifications where data 1586 values do not represent an observable or predictable physical quantity. 1587 An example of such a field would be [Dominant Precipitation Type - 1588 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1589 1590 Returns 1591 ------- 1592 map_keys 1593 numpy.ndarray of string values per element. 1594 """ 1595 hold_auto_nans = _AUTO_NANS 1596 set_auto_nans(False) 1597 1598 if (np.all(self.section1[0:2] == [7, 14]) and self.shortName == "PWTHER") or ( 1599 self._isNDFD and self.shortName in {"WX", "WWA"} 1600 ): 1601 keys = utils.decode_wx_strings(self.section2) 1602 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1603 keys[int(self.priMissingValue)] = 'Missing' 1604 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1605 keys[int(self.secMissingValue)] = 'Missing' 1606 u,inv = np.unique(self.data,return_inverse=True) 1607 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1608 else: 1609 # For data whose units are defined in a code table (i.e. classification or mask) 1610 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1611 fld = self.data.astype(np.int32).astype(str) 1612 tbl = tables.get_table(tblname,expand=True) 1613 for val in np.unique(fld): 1614 fld = np.where(fld==val,tbl[val],fld) 1615 set_auto_nans(hold_auto_nans) 1616 return fld 1617 1618 1619 def to_bytes(self, validate: bool=True): 1620 """ 1621 Return packed GRIB2 message in bytes format. 1622 1623 This will be useful for exporting data in non-file formats. For example, 1624 can be used to output grib data directly to S3 using the boto3 client 1625 without the need to write a temporary file to upload first. 1626 1627 Parameters 1628 ---------- 1629 validate: default=True 1630 If `True` (DEFAULT), validates first/last four bytes for proper 1631 formatting, else returns None. If `False`, message is output as is. 1632 1633 Returns 1634 ------- 1635 to_bytes 1636 Returns GRIB2 formatted message as bytes. 1637 """ 1638 if hasattr(self,'_msg'): 1639 if validate: 1640 if self.validate(): 1641 return self._msg 1642 else: 1643 return None 1644 else: 1645 return self._msg 1646 else: 1647 return None 1648 1649 1650 def interpolate(self, method, grid_def_out, method_options=None, drtn=None, 1651 num_threads=1): 1652 """ 1653 Grib2Message Interpolator 1654 1655 Performs spatial interpolation via the [NCEPLIBS-ip 1656 library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate 1657 method only supports scalar interpolation. If you need to perform 1658 vector interpolation, use the module-level `grib2io.interpolate` 1659 function. 1660 1661 Parameters 1662 ---------- 1663 method 1664 Interpolate method to use. This can either be an integer or string 1665 using the following mapping: 1666 1667 | Interpolate Scheme | Integer Value | 1668 | :---: | :---: | 1669 | 'bilinear' | 0 | 1670 | 'bicubic' | 1 | 1671 | 'neighbor' | 2 | 1672 | 'budget' | 3 | 1673 | 'spectral' | 4 | 1674 | 'neighbor-budget' | 6 | 1675 1676 grid_def_out : grib2io.Grib2GridDef 1677 Grib2GridDef object of the output grid. 1678 method_options : list of ints, optional 1679 Interpolation options. See the NCEPLIBS-ip documentation for 1680 more information on how these are used. 1681 drtn 1682 Data Representation Template to be used for the returned 1683 interpolated GRIB2 message. When `None`, the data representation 1684 template of the source GRIB2 message is used. Once again, it is the 1685 user's responsibility to properly set the Data Representation 1686 Template attributes. 1687 num_threads : int, optional 1688 Number of OpenMP threads to use for interpolation. The default 1689 value is 1. If NCEPLIBS-ip and grib2io's iplib extension module 1690 was not built with OpenMP, then this keyword argument and value 1691 will have no impact. 1692 1693 Returns 1694 ------- 1695 interpolate 1696 If interpolating to a grid, a new Grib2Message object is returned. 1697 The GRIB2 metadata of the new Grib2Message object is identical to 1698 the input except where required to be different because of the new 1699 grid specs and possibly a new data representation template. 1700 1701 If interpolating to station points, the interpolated data values are 1702 returned as a numpy.ndarray. 1703 """ 1704 section0 = self.section0 1705 section0[-1] = 0 1706 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1707 section3 = np.concatenate((gds,grid_def_out.gdt)) 1708 drtn = self.drtn if drtn is None else drtn 1709 1710 msg = Grib2Message(section0,self.section1,self.section2,section3, 1711 self.section4,None,self.bitMapFlag.value,drtn=drtn) 1712 1713 msg._msgnum = -1 1714 msg._deflist = self._deflist 1715 msg._coordlist = self._coordlist 1716 shape = (msg.ny,msg.nx) 1717 ndim = 2 1718 if msg.typeOfValues == 0: 1719 dtype = 'float32' 1720 elif msg.typeOfValues == 1: 1721 dtype = 'int32' 1722 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1723 method_options=method_options,num_threads=num_threads).reshape(msg.ny,msg.nx) 1724 msg.section5[0] = grid_def_out.npoints 1725 return msg 1726 1727 1728 def subset(self, lats, lons): 1729 """ 1730 Return a spatial subset. 1731 1732 Currently only supports regular grids of the following types: 1733 1734 | Grid Type | gdtn | 1735 | :---: | :---: | 1736 | Latitude/Longitude, Equidistant Cylindrical, or Plate Carree | 0 | 1737 | Rotated Latitude/Longitude | 1 | 1738 | Mercator | 10 | 1739 | Polar Stereographic | 20 | 1740 | Lambert Conformal | 30 | 1741 | Albers Equal-Area | 31 | 1742 | Gaussian Latitude/Longitude | 40 | 1743 | Equatorial Azimuthal Equidistant Projection | 110 | 1744 1745 Parameters 1746 ---------- 1747 lats 1748 List or tuple of latitudes. The minimum and maximum latitudes will 1749 be used to define the southern and northern boundaries. 1750 1751 The order of the latitudes is not important. The function will 1752 determine which is the minimum and maximum. 1753 1754 The latitudes should be in decimal degrees with 0.0 at the equator, 1755 positive values in the northern hemisphere increasing to 90, and 1756 negative values in the southern hemisphere decreasing to -90. 1757 lons 1758 List or tuple of longitudes. The minimum and maximum longitudes 1759 will be used to define the western and eastern boundaries. 1760 1761 The order of the longitudes is not important. The function will 1762 determine which is the minimum and maximum. 1763 1764 GRIB2 longitudes should be in decimal degrees with 0.0 at the prime 1765 meridian, positive values increasing eastward to 360. There are no 1766 negative GRIB2 longitudes. 1767 1768 The typical west longitudes that start at 0.0 at the prime meridian 1769 and decrease to -180 westward, are converted to GRIB2 longitudes by 1770 '360 - (absolute value of the west longitude)' where typical 1771 eastern longitudes are unchanged as GRIB2 longitudes. 1772 1773 Returns 1774 ------- 1775 subset 1776 A spatial subset of a GRIB2 message. 1777 """ 1778 if self.gdtn not in [0, 1, 10, 20, 30, 31, 40, 110]: 1779 raise ValueError( 1780 """ 1781 1782Subset only works for 1783 Latitude/Longitude, Equidistant Cylindrical, or Plate Carree (gdtn=0) 1784 Rotated Latitude/Longitude (gdtn=1) 1785 Mercator (gdtn=10) 1786 Polar Stereographic (gdtn=20) 1787 Lambert Conformal (gdtn=30) 1788 Albers Equal-Area (gdtn=31) 1789 Gaussian Latitude/Longitude (gdtn=40) 1790 Equatorial Azimuthal Equidistant Projection (gdtn=110) 1791 1792""" 1793 ) 1794 1795 if self.nx == 0 or self.ny == 0: 1796 raise ValueError( 1797 """ 1798 1799Subset only works for regular grids. 1800 1801""" 1802 ) 1803 1804 newmsg = Grib2Message( 1805 np.copy(self.section0), 1806 np.copy(self.section1), 1807 np.copy(self.section2), 1808 np.copy(self.section3), 1809 np.copy(self.section4), 1810 np.copy(self.section5), 1811 ) 1812 1813 msglats, msglons = self.grid() 1814 1815 la1 = np.max(lats) 1816 lo1 = np.min(lons) 1817 la2 = np.min(lats) 1818 lo2 = np.max(lons) 1819 1820 # Find the indices of the first and last grid points to the nearest 1821 # lat/lon values in the grid. 1822 first_lat = np.abs(msglats - la1) 1823 first_lon = np.abs(msglons - lo1) 1824 max_idx = np.maximum(first_lat, first_lon) 1825 first_j, first_i = np.where(max_idx == np.min(max_idx)) 1826 1827 last_lat = np.abs(msglats - la2) 1828 last_lon = np.abs(msglons - lo2) 1829 max_idx = np.maximum(last_lat, last_lon) 1830 last_j, last_i = np.where(max_idx == np.min(max_idx)) 1831 1832 setattr(newmsg, "latitudeFirstGridpoint", msglats[first_j[0], first_i[0]]) 1833 setattr(newmsg, "longitudeFirstGridpoint", msglons[first_j[0], first_i[0]]) 1834 setattr(newmsg, "nx", np.abs(first_i[0] - last_i[0])) 1835 setattr(newmsg, "ny", np.abs(first_j[0] - last_j[0])) 1836 1837 # Set *LastGridpoint attributes even if only used for gdtn=[0, 1, 40]. 1838 # This information is used to subset xarray datasets and even though 1839 # unnecessary for some supported grid types, it won't affect a grib2io 1840 # message to set them. 1841 setattr(newmsg, "latitudeLastGridpoint", msglats[last_j[0], last_i[0]]) 1842 setattr(newmsg, "longitudeLastGridpoint", msglons[last_j[0], last_i[0]]) 1843 1844 setattr( 1845 newmsg, 1846 "data", 1847 self.data[ 1848 min(first_j[0], last_j[0]) : max(first_j[0], last_j[0]), 1849 min(first_i[0], last_i[0]) : max(first_i[0], last_i[0]), 1850 ].copy(), 1851 ) 1852 1853 # Need to set the newmsg._sha1_section3 to a blank string so the grid 1854 # method ignores the cached lat/lon values. This will force the grid 1855 # method to recompute the lat/lon values for the subsetted grid. 1856 newmsg._sha1_section3 = "" 1857 newmsg.grid() 1858 1859 return newmsg 1860 1861 def validate(self): 1862 """ 1863 Validate a complete GRIB2 message. 1864 1865 The g2c library does its own internal validation when g2_gribend() is called, but 1866 we will check in grib2io also. The validation checks if the first 4 bytes in 1867 self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in 1868 section 0 equals the length of the packed message. 1869 1870 Returns 1871 ------- 1872 `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise. 1873 """ 1874 valid = False 1875 if hasattr(self,'_msg'): 1876 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1877 if self.section0[-1] == len(self._msg): 1878 valid = True 1879 return valid
GRIB2 Message base class.
GRIB2 Section 1, Identification Section
GRIB2 Section 3, Grid Definition Section
Product Definition Template
1069 @property 1070 def gdtn(self): 1071 """Return Grid Definition Template Number""" 1072 return self.section3[4]
Return Grid Definition Template Number
1075 @property 1076 def gdt(self): 1077 """Return Grid Definition Template.""" 1078 return self.gridDefinitionTemplate
Return Grid Definition Template.
1081 @property 1082 def pdtn(self): 1083 """Return Product Definition Template Number.""" 1084 return self.section4[1]
Return Product Definition Template Number.
1087 @property 1088 def pdt(self): 1089 """Return Product Definition Template.""" 1090 return self.productDefinitionTemplate
Return Product Definition Template.
1093 @property 1094 def drtn(self): 1095 """Return Data Representation Template Number.""" 1096 return self.section5[1]
Return Data Representation Template Number.
1099 @property 1100 def drt(self): 1101 """Return Data Representation Template.""" 1102 return self.dataRepresentationTemplate
Return Data Representation Template.
1105 @property 1106 def pdy(self): 1107 """Return the PDY ('YYYYMMDD').""" 1108 return ''.join([str(i) for i in self.section1[5:8]])
Return the PDY ('YYYYMMDD').
1111 @property 1112 def griddef(self): 1113 """Return a Grib2GridDef instance for a GRIB2 message.""" 1114 return Grib2GridDef.from_section3(self.section3)
Return a Grib2GridDef instance for a GRIB2 message.
1123 @property 1124 def lons(self): 1125 """Return grid longitudes.""" 1126 return self.latlons()[1]
Return grid longitudes.
1128 @property 1129 def min(self): 1130 """Return minimum value of data.""" 1131 return np.nanmin(self.data)
Return minimum value of data.
1134 @property 1135 def max(self): 1136 """Return maximum value of data.""" 1137 return np.nanmax(self.data)
Return maximum value of data.
1140 @property 1141 def mean(self): 1142 """Return mean value of data.""" 1143 return np.nanmean(self.data)
Return mean value of data.
1146 @property 1147 def median(self): 1148 """Return median value of data.""" 1149 return np.nanmedian(self.data)
Return median value of data.
1152 @property 1153 def shape(self): 1154 """Return shape of data.""" 1155 return self.griddef.shape
Return shape of data.
1196 def attrs_by_section(self, sect: int, values: bool=False): 1197 """ 1198 Provide a tuple of attribute names for the given GRIB2 section. 1199 1200 Parameters 1201 ---------- 1202 sect 1203 The GRIB2 section number. 1204 values 1205 Optional (default is `False`) argument to return attributes values. 1206 1207 Returns 1208 ------- 1209 attrs_by_section 1210 A list of attribute names or dict of name:value pairs if `values = 1211 True`. 1212 """ 1213 if sect in {0,1,6}: 1214 attrs = templates._section_attrs[sect] 1215 elif sect in {3,4,5}: 1216 def _find_class_index(n): 1217 _key = {3:'Grid', 4:'Product', 5:'Data'} 1218 for i,c in enumerate(self.__class__.__mro__): 1219 if _key[n] in c.__name__: 1220 return i 1221 else: 1222 return [] 1223 if sys.version_info.minor <= 8: 1224 attrs = templates._section_attrs[sect]+\ 1225 [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')] 1226 else: 1227 attrs = templates._section_attrs[sect]+\ 1228 self.__class__.__mro__[_find_class_index(sect)]._attrs() 1229 else: 1230 attrs = [] 1231 if values: 1232 return {k:getattr(self,k) for k in attrs} 1233 else: 1234 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.
1237 def copy(self, deep: bool = True): 1238 """Returns a copy of this Grib2Message. 1239 1240 When `deep=True`, a copy is made of each of the GRIB2 section arrays and 1241 the data are unpacked from the source object and copied into the new 1242 object. Otherwise, a shallow copy of each array is performed and no data 1243 are copied. 1244 1245 Parameters 1246 ---------- 1247 deep : bool, default: True 1248 Whether each GRIB2 section array and data are copied onto 1249 the new object. Default is True. 1250 1251 Returns 1252 ------- 1253 object : Grib2Message 1254 New Grib2Message object. 1255 1256 .. versionadded:: 2.6.0 1257 """ 1258 return copy.deepcopy(self) if deep else copy.copy(self)
Returns a copy of this Grib2Message.
When deep=True, a copy is made of each of the GRIB2 section arrays and
the data are unpacked from the source object and copied into the new
object. Otherwise, a shallow copy of each array is performed and no data
are copied.
Parameters
- deep : bool, default (True): Whether each GRIB2 section array and data are copied onto the new object. Default is True.
Returns
object (Grib2Message): New Grib2Message object.
New in version 2.6.0.
1261 def pack(self): 1262 """ 1263 Pack GRIB2 section data into a binary message. 1264 1265 It is the user's responsibility to populate the GRIB2 section 1266 information with appropriate metadata. 1267 """ 1268 # Create beginning of packed binary message with section 0 and 1 data. 1269 self._sections = [] 1270 self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection) 1271 self._sections += [0,1] 1272 1273 # Add section 2 if present. 1274 if isinstance(self.section2,bytes) and len(self.section2) > 0: 1275 self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2) 1276 self._sections.append(2) 1277 1278 # Add section 3. 1279 self.section3[1] = self.nx * self.ny 1280 self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection, 1281 self.gridDefinitionTemplate, 1282 self._deflist) 1283 self._sections.append(3) 1284 1285 # Prepare data. 1286 if self._data is None: 1287 if self._ondiskarray is None: 1288 raise ValueError("Grib2Message object has no data, thus it cannot be packed.") 1289 field = np.copy(self.data) 1290 if self.scanModeFlags is not None: 1291 if self.scanModeFlags[3]: 1292 fieldsave = field.astype('f') # Casting makes a copy 1293 field[1::2,:] = fieldsave[1::2,::-1] 1294 fld = field.astype('f') 1295 1296 # Prepare bitmap, if necessary 1297 bitmapflag = self.bitMapFlag.value 1298 if bitmapflag == 0: 1299 if self.bitmap is not None: 1300 bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT) 1301 else: 1302 bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT) 1303 else: 1304 bmap = None 1305 1306 # Prepare data for packing if nans are present 1307 fld = np.ravel(fld) 1308 if bitmapflag in {0,254}: 1309 fld = np.where(np.isnan(fld),0,fld) 1310 else: 1311 if np.isnan(fld).any(): 1312 if hasattr(self,'priMissingValue'): 1313 fld = np.where(np.isnan(fld),self.priMissingValue,fld) 1314 if hasattr(self,'_missvalmap'): 1315 if hasattr(self,'priMissingValue'): 1316 fld = np.where(self._missvalmap==1,self.priMissingValue,fld) 1317 if hasattr(self,'secMissingValue'): 1318 fld = np.where(self._missvalmap==2,self.secMissingValue,fld) 1319 1320 # Add sections 4, 5, 6, and 7. 1321 self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn, 1322 self.productDefinitionTemplate, 1323 self._coordlist, 1324 self.drtn, 1325 self.dataRepresentationTemplate, 1326 fld, 1327 bitmapflag, 1328 bmap) 1329 self._sections.append(4) 1330 self._sections.append(5) 1331 self._sections.append(6) 1332 self._sections.append(7) 1333 1334 # Finalize GRIB2 message with section 8. 1335 self._msg, self._pos = g2clib.grib2_end(self._msg) 1336 self._sections.append(8) 1337 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.
1340 @property 1341 def data(self) -> np.array: 1342 """Access the unpacked data values.""" 1343 if self._data is None: 1344 if self._auto_nans != _AUTO_NANS: 1345 self._data = self._ondiskarray 1346 self._data = np.asarray(self._ondiskarray) 1347 return self._data
Access the unpacked data values.
1388 def flush_data(self): 1389 """ 1390 Flush the unpacked data values from the Grib2Message object. 1391 1392 Notes 1393 ----- 1394 If the Grib2Message object was constructed from "scratch" (i.e. 1395 not read from file), this method will remove the data array from 1396 the object and it cannot be recovered. 1397 """ 1398 self._data = None 1399 self.bitmap = None
Flush the unpacked data values from the Grib2Message object.
Notes
If the Grib2Message object was constructed from "scratch" (i.e. not read from file), this method will remove the data array from the object and it cannot be recovered.
1410 def latlons(self, *args, **kwrgs): 1411 """Alias for `grib2io.Grib2Message.grid` method.""" 1412 return self.grid(*args, **kwrgs)
Alias for grib2io.Grib2Message.grid method.
1415 def grid(self, unrotate: bool=True): 1416 """ 1417 Return lats,lons (in degrees) of grid. 1418 1419 Currently can handle reg. lat/lon,cglobal Gaussian, mercator, 1420 stereographic, lambert conformal, albers equal-area, space-view and 1421 azimuthal equidistant grids. 1422 1423 Parameters 1424 ---------- 1425 unrotate 1426 If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the 1427 grid, otherwise `False`, do not. 1428 1429 Returns 1430 ------- 1431 lats, lons : numpy.ndarray 1432 Returns two numpy.ndarrays with dtype=numpy.float32 of grid 1433 latitudes and longitudes in units of degrees. 1434 """ 1435 if self._sha1_section3 in _latlon_datastore.keys(): 1436 return (_latlon_datastore[self._sha1_section3]['latitude'], 1437 _latlon_datastore[self._sha1_section3]['longitude']) 1438 gdtn = self.gridDefinitionTemplateNumber.value 1439 gdtmpl = self.gridDefinitionTemplate 1440 reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid 1441 if gdtn == 0: 1442 # Regular lat/lon grid 1443 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1444 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1445 dlon = self.gridlengthXDirection 1446 dlat = self.gridlengthYDirection 1447 if lon2 < lon1 and dlon < 0: lon1 = -lon1 1448 lats = np.linspace(lat1,lat2,self.ny) 1449 if reggrid: 1450 lons = np.linspace(lon1,lon2,self.nx) 1451 else: 1452 lons = np.linspace(lon1,lon2,self.ny*2) 1453 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1454 elif gdtn == 1: # Rotated Lat/Lon grid 1455 pj = pyproj.Proj(self.projParameters) 1456 lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint 1457 lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint 1458 if lon1 > 180.0: lon1 -= 360.0 1459 if lon2 > 180.0: lon2 -= 360.0 1460 lats = np.linspace(lat1,lat2,self.ny) 1461 lons = np.linspace(lon1,lon2,self.nx) 1462 lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays. 1463 if unrotate: 1464 from grib2io.utils import rotated_grid 1465 lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation, 1466 self.latitudeSouthernPole, 1467 self.longitudeSouthernPole) 1468 elif gdtn == 40: # Gaussian grid (only works for global!) 1469 from grib2io.utils.gauss_grid import gaussian_latitudes 1470 lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1471 lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint 1472 nlats = self.ny 1473 if not reggrid: # Reduced Gaussian grid. 1474 nlons = 2*nlats 1475 dlon = 360./nlons 1476 else: 1477 nlons = self.nx 1478 dlon = self.gridlengthXDirection 1479 lons = np.linspace(lon1,lon2,nlons) 1480 # Compute Gaussian lats (north to south) 1481 lats = gaussian_latitudes(nlats) 1482 if lat1 < lat2: # reverse them if necessary 1483 lats = lats[::-1] 1484 lons,lats = np.meshgrid(lons,lats) 1485 elif gdtn in {10,20,30,31,110}: 1486 # Mercator, Lambert Conformal, Stereographic, Albers Equal Area, 1487 # Azimuthal Equidistant 1488 dx,dy = self.gridlengthXDirection, self.gridlengthYDirection 1489 lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint 1490 pj = pyproj.Proj(self.projParameters) 1491 llcrnrx, llcrnry = pj(lon1,lat1) 1492 x = llcrnrx+dx*np.arange(self.nx) 1493 y = llcrnry+dy*np.arange(self.ny) 1494 x,y = np.meshgrid(x, y) 1495 lons,lats = pj(x, y, inverse=True) 1496 elif gdtn == 90: 1497 # Satellite Projection 1498 dx = self.gridlengthXDirection 1499 dy = self.gridlengthYDirection 1500 pj = pyproj.Proj(self.projParameters) 1501 x = dx*np.indices((self.ny,self.nx),'f')[1,:,:] 1502 x -= 0.5*x.max() 1503 y = dy*np.indices((self.ny,self.nx),'f')[0,:,:] 1504 y -= 0.5*y.max() 1505 lons,lats = pj(x,y,inverse=True) 1506 # Set lons,lats to 1.e30 where undefined 1507 abslons = np.fabs(lons) 1508 abslats = np.fabs(lats) 1509 lons = np.where(abslons < 1.e20, lons, 1.e30) 1510 lats = np.where(abslats < 1.e20, lats, 1.e30) 1511 elif gdtn == 32769: 1512 # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered) 1513 from grib2io.utils import arakawa_rotated_grid 1514 from grib2io.utils.rotated_grid import DEG2RAD 1515 di, dj = 0.0, 0.0 1516 do_180 = False 1517 idir = 1 if self.scanModeFlags[0] == 0 else -1 1518 jdir = -1 if self.scanModeFlags[1] == 0 else 1 1519 grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1 1520 do_rot = 1 if grid_oriented == 1 else 0 1521 la1 = self.latitudeFirstGridpoint 1522 lo1 = self.longitudeFirstGridpoint 1523 clon = self.longitudeCenterGridpoint 1524 clat = self.latitudeCenterGridpoint 1525 lasp = clat - 90.0 1526 losp = clon 1527 llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp) 1528 la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp) 1529 rlat = -llat 1530 rlon = -llon 1531 if self.nx == 1: 1532 di = 0.0 1533 elif idir == 1: 1534 ti = rlon 1535 while ti < llon: 1536 ti += 360.0 1537 di = (ti - llon)/float(self.nx-1) 1538 else: 1539 ti = llon 1540 while ti < rlon: 1541 ti += 360.0 1542 di = (ti - rlon)/float(self.nx-1) 1543 if self.ny == 1: 1544 dj = 0.0 1545 else: 1546 dj = (rlat - llat)/float(self.ny-1) 1547 if dj < 0.0: 1548 dj = -dj 1549 if idir == 1: 1550 if llon > rlon: 1551 llon -= 360.0 1552 if llon < 0 and rlon > 0: 1553 do_180 = True 1554 else: 1555 if rlon > llon: 1556 rlon -= 360.0 1557 if rlon < 0 and llon > 0: 1558 do_180 = True 1559 xlat1d = llat + (np.arange(self.ny)*jdir*dj) 1560 xlon1d = llon + (np.arange(self.nx)*idir*di) 1561 xlons, xlats = np.meshgrid(xlon1d,xlat1d) 1562 rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll) 1563 lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp) 1564 if do_180: 1565 lons = np.where(lons>180.0,lons-360.0,lons) 1566 vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles) 1567 rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats) 1568 del xlat1d, xlon1d, xlats, xlons 1569 else: 1570 raise ValueError('Unsupported grid') 1571 1572 _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons) 1573 try: 1574 _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots 1575 except(NameError): 1576 pass 1577 1578 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.
1581 def map_keys(self): 1582 """ 1583 Unpack data grid replacing integer values with strings. 1584 1585 These types of fields are categorical or classifications where data 1586 values do not represent an observable or predictable physical quantity. 1587 An example of such a field would be [Dominant Precipitation Type - 1588 DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml) 1589 1590 Returns 1591 ------- 1592 map_keys 1593 numpy.ndarray of string values per element. 1594 """ 1595 hold_auto_nans = _AUTO_NANS 1596 set_auto_nans(False) 1597 1598 if (np.all(self.section1[0:2] == [7, 14]) and self.shortName == "PWTHER") or ( 1599 self._isNDFD and self.shortName in {"WX", "WWA"} 1600 ): 1601 keys = utils.decode_wx_strings(self.section2) 1602 if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]: 1603 keys[int(self.priMissingValue)] = 'Missing' 1604 if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]: 1605 keys[int(self.secMissingValue)] = 'Missing' 1606 u,inv = np.unique(self.data,return_inverse=True) 1607 fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape) 1608 else: 1609 # For data whose units are defined in a code table (i.e. classification or mask) 1610 tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0] 1611 fld = self.data.astype(np.int32).astype(str) 1612 tbl = tables.get_table(tblname,expand=True) 1613 for val in np.unique(fld): 1614 fld = np.where(fld==val,tbl[val],fld) 1615 set_auto_nans(hold_auto_nans) 1616 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.
1619 def to_bytes(self, validate: bool=True): 1620 """ 1621 Return packed GRIB2 message in bytes format. 1622 1623 This will be useful for exporting data in non-file formats. For example, 1624 can be used to output grib data directly to S3 using the boto3 client 1625 without the need to write a temporary file to upload first. 1626 1627 Parameters 1628 ---------- 1629 validate: default=True 1630 If `True` (DEFAULT), validates first/last four bytes for proper 1631 formatting, else returns None. If `False`, message is output as is. 1632 1633 Returns 1634 ------- 1635 to_bytes 1636 Returns GRIB2 formatted message as bytes. 1637 """ 1638 if hasattr(self,'_msg'): 1639 if validate: 1640 if self.validate(): 1641 return self._msg 1642 else: 1643 return None 1644 else: 1645 return self._msg 1646 else: 1647 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.
1650 def interpolate(self, method, grid_def_out, method_options=None, drtn=None, 1651 num_threads=1): 1652 """ 1653 Grib2Message Interpolator 1654 1655 Performs spatial interpolation via the [NCEPLIBS-ip 1656 library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate 1657 method only supports scalar interpolation. If you need to perform 1658 vector interpolation, use the module-level `grib2io.interpolate` 1659 function. 1660 1661 Parameters 1662 ---------- 1663 method 1664 Interpolate method to use. This can either be an integer or string 1665 using the following mapping: 1666 1667 | Interpolate Scheme | Integer Value | 1668 | :---: | :---: | 1669 | 'bilinear' | 0 | 1670 | 'bicubic' | 1 | 1671 | 'neighbor' | 2 | 1672 | 'budget' | 3 | 1673 | 'spectral' | 4 | 1674 | 'neighbor-budget' | 6 | 1675 1676 grid_def_out : grib2io.Grib2GridDef 1677 Grib2GridDef object of the output grid. 1678 method_options : list of ints, optional 1679 Interpolation options. See the NCEPLIBS-ip documentation for 1680 more information on how these are used. 1681 drtn 1682 Data Representation Template to be used for the returned 1683 interpolated GRIB2 message. When `None`, the data representation 1684 template of the source GRIB2 message is used. Once again, it is the 1685 user's responsibility to properly set the Data Representation 1686 Template attributes. 1687 num_threads : int, optional 1688 Number of OpenMP threads to use for interpolation. The default 1689 value is 1. If NCEPLIBS-ip and grib2io's iplib extension module 1690 was not built with OpenMP, then this keyword argument and value 1691 will have no impact. 1692 1693 Returns 1694 ------- 1695 interpolate 1696 If interpolating to a grid, a new Grib2Message object is returned. 1697 The GRIB2 metadata of the new Grib2Message object is identical to 1698 the input except where required to be different because of the new 1699 grid specs and possibly a new data representation template. 1700 1701 If interpolating to station points, the interpolated data values are 1702 returned as a numpy.ndarray. 1703 """ 1704 section0 = self.section0 1705 section0[-1] = 0 1706 gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn] 1707 section3 = np.concatenate((gds,grid_def_out.gdt)) 1708 drtn = self.drtn if drtn is None else drtn 1709 1710 msg = Grib2Message(section0,self.section1,self.section2,section3, 1711 self.section4,None,self.bitMapFlag.value,drtn=drtn) 1712 1713 msg._msgnum = -1 1714 msg._deflist = self._deflist 1715 msg._coordlist = self._coordlist 1716 shape = (msg.ny,msg.nx) 1717 ndim = 2 1718 if msg.typeOfValues == 0: 1719 dtype = 'float32' 1720 elif msg.typeOfValues == 1: 1721 dtype = 'int32' 1722 msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out, 1723 method_options=method_options,num_threads=num_threads).reshape(msg.ny,msg.nx) 1724 msg.section5[0] = grid_def_out.npoints 1725 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 NCEPLIBS-ip and grib2io's iplib extension module 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.
1728 def subset(self, lats, lons): 1729 """ 1730 Return a spatial subset. 1731 1732 Currently only supports regular grids of the following types: 1733 1734 | Grid Type | gdtn | 1735 | :---: | :---: | 1736 | Latitude/Longitude, Equidistant Cylindrical, or Plate Carree | 0 | 1737 | Rotated Latitude/Longitude | 1 | 1738 | Mercator | 10 | 1739 | Polar Stereographic | 20 | 1740 | Lambert Conformal | 30 | 1741 | Albers Equal-Area | 31 | 1742 | Gaussian Latitude/Longitude | 40 | 1743 | Equatorial Azimuthal Equidistant Projection | 110 | 1744 1745 Parameters 1746 ---------- 1747 lats 1748 List or tuple of latitudes. The minimum and maximum latitudes will 1749 be used to define the southern and northern boundaries. 1750 1751 The order of the latitudes is not important. The function will 1752 determine which is the minimum and maximum. 1753 1754 The latitudes should be in decimal degrees with 0.0 at the equator, 1755 positive values in the northern hemisphere increasing to 90, and 1756 negative values in the southern hemisphere decreasing to -90. 1757 lons 1758 List or tuple of longitudes. The minimum and maximum longitudes 1759 will be used to define the western and eastern boundaries. 1760 1761 The order of the longitudes is not important. The function will 1762 determine which is the minimum and maximum. 1763 1764 GRIB2 longitudes should be in decimal degrees with 0.0 at the prime 1765 meridian, positive values increasing eastward to 360. There are no 1766 negative GRIB2 longitudes. 1767 1768 The typical west longitudes that start at 0.0 at the prime meridian 1769 and decrease to -180 westward, are converted to GRIB2 longitudes by 1770 '360 - (absolute value of the west longitude)' where typical 1771 eastern longitudes are unchanged as GRIB2 longitudes. 1772 1773 Returns 1774 ------- 1775 subset 1776 A spatial subset of a GRIB2 message. 1777 """ 1778 if self.gdtn not in [0, 1, 10, 20, 30, 31, 40, 110]: 1779 raise ValueError( 1780 """ 1781 1782Subset only works for 1783 Latitude/Longitude, Equidistant Cylindrical, or Plate Carree (gdtn=0) 1784 Rotated Latitude/Longitude (gdtn=1) 1785 Mercator (gdtn=10) 1786 Polar Stereographic (gdtn=20) 1787 Lambert Conformal (gdtn=30) 1788 Albers Equal-Area (gdtn=31) 1789 Gaussian Latitude/Longitude (gdtn=40) 1790 Equatorial Azimuthal Equidistant Projection (gdtn=110) 1791 1792""" 1793 ) 1794 1795 if self.nx == 0 or self.ny == 0: 1796 raise ValueError( 1797 """ 1798 1799Subset only works for regular grids. 1800 1801""" 1802 ) 1803 1804 newmsg = Grib2Message( 1805 np.copy(self.section0), 1806 np.copy(self.section1), 1807 np.copy(self.section2), 1808 np.copy(self.section3), 1809 np.copy(self.section4), 1810 np.copy(self.section5), 1811 ) 1812 1813 msglats, msglons = self.grid() 1814 1815 la1 = np.max(lats) 1816 lo1 = np.min(lons) 1817 la2 = np.min(lats) 1818 lo2 = np.max(lons) 1819 1820 # Find the indices of the first and last grid points to the nearest 1821 # lat/lon values in the grid. 1822 first_lat = np.abs(msglats - la1) 1823 first_lon = np.abs(msglons - lo1) 1824 max_idx = np.maximum(first_lat, first_lon) 1825 first_j, first_i = np.where(max_idx == np.min(max_idx)) 1826 1827 last_lat = np.abs(msglats - la2) 1828 last_lon = np.abs(msglons - lo2) 1829 max_idx = np.maximum(last_lat, last_lon) 1830 last_j, last_i = np.where(max_idx == np.min(max_idx)) 1831 1832 setattr(newmsg, "latitudeFirstGridpoint", msglats[first_j[0], first_i[0]]) 1833 setattr(newmsg, "longitudeFirstGridpoint", msglons[first_j[0], first_i[0]]) 1834 setattr(newmsg, "nx", np.abs(first_i[0] - last_i[0])) 1835 setattr(newmsg, "ny", np.abs(first_j[0] - last_j[0])) 1836 1837 # Set *LastGridpoint attributes even if only used for gdtn=[0, 1, 40]. 1838 # This information is used to subset xarray datasets and even though 1839 # unnecessary for some supported grid types, it won't affect a grib2io 1840 # message to set them. 1841 setattr(newmsg, "latitudeLastGridpoint", msglats[last_j[0], last_i[0]]) 1842 setattr(newmsg, "longitudeLastGridpoint", msglons[last_j[0], last_i[0]]) 1843 1844 setattr( 1845 newmsg, 1846 "data", 1847 self.data[ 1848 min(first_j[0], last_j[0]) : max(first_j[0], last_j[0]), 1849 min(first_i[0], last_i[0]) : max(first_i[0], last_i[0]), 1850 ].copy(), 1851 ) 1852 1853 # Need to set the newmsg._sha1_section3 to a blank string so the grid 1854 # method ignores the cached lat/lon values. This will force the grid 1855 # method to recompute the lat/lon values for the subsetted grid. 1856 newmsg._sha1_section3 = "" 1857 newmsg.grid() 1858 1859 return newmsg
Return a spatial subset.
Currently only supports regular grids of the following types:
| Grid Type | gdtn |
|---|---|
| Latitude/Longitude, Equidistant Cylindrical, or Plate Carree | 0 |
| Rotated Latitude/Longitude | 1 |
| Mercator | 10 |
| Polar Stereographic | 20 |
| Lambert Conformal | 30 |
| Albers Equal-Area | 31 |
| Gaussian Latitude/Longitude | 40 |
| Equatorial Azimuthal Equidistant Projection | 110 |
Parameters
- lats: List or tuple of latitudes. The minimum and maximum latitudes will be used to define the southern and northern boundaries.
The order of the latitudes is not important. The function will determine which is the minimum and maximum.
The latitudes should be in decimal degrees with 0.0 at the equator, positive values in the northern hemisphere increasing to 90, and negative values in the southern hemisphere decreasing to -90.
- lons: List or tuple of longitudes. The minimum and maximum longitudes will be used to define the western and eastern boundaries.
The order of the longitudes is not important. The function will determine which is the minimum and maximum.
GRIB2 longitudes should be in decimal degrees with 0.0 at the prime meridian, positive values increasing eastward to 360. There are no negative GRIB2 longitudes.
The typical west longitudes that start at 0.0 at the prime meridian and decrease to -180 westward, are converted to GRIB2 longitudes by '360 - (absolute value of the west longitude)' where typical eastern longitudes are unchanged as GRIB2 longitudes.
Returns
- subset: A spatial subset of a GRIB2 message.
1861 def validate(self): 1862 """ 1863 Validate a complete GRIB2 message. 1864 1865 The g2c library does its own internal validation when g2_gribend() is called, but 1866 we will check in grib2io also. The validation checks if the first 4 bytes in 1867 self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in 1868 section 0 equals the length of the packed message. 1869 1870 Returns 1871 ------- 1872 `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise. 1873 """ 1874 valid = False 1875 if hasattr(self,'_msg'): 1876 if self._msg[0:4]+self._msg[-4:] == b'GRIB7777': 1877 if self.section0[-1] == len(self._msg): 1878 valid = True 1879 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
Trueif the packed GRIB2 message is complete and well-formed,Falseotherwise.
2354@dataclass 2355class Grib2GridDef: 2356 """ 2357 Class for Grid Definition Template Number and Template as attributes. 2358 2359 This allows for cleaner looking code when passing these metadata around. 2360 For example, the `grib2io._Grib2Message.interpolate` method and 2361 `grib2io.interpolate` function accepts these objects. 2362 """ 2363 gdtn: int 2364 gdt: NDArray 2365 2366 @classmethod 2367 def from_section3(cls, section3): 2368 return cls(section3[4],section3[5:]) 2369 2370 @property 2371 def nx(self): 2372 """Number of grid points in x-direction.""" 2373 return int(self.gdt[7]) 2374 2375 @property 2376 def ny(self): 2377 """Number of grid points in y-direction.""" 2378 return int(self.gdt[8]) 2379 2380 @property 2381 def npoints(self): 2382 """Total number of grid points.""" 2383 return int(self.gdt[7] * self.gdt[8]) 2384 2385 @property 2386 def shape(self): 2387 """Shape of the grid.""" 2388 return (int(self.ny), int(self.nx)) 2389 2390 def to_section3(self): 2391 """Return a full GRIB2 section3 array.""" 2392 return np.array( 2393 [0, self.npoints, 0, 0, self.gdtn] + list(self.gdt) 2394 ).astype(np.int64)
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.
2370 @property 2371 def nx(self): 2372 """Number of grid points in x-direction.""" 2373 return int(self.gdt[7])
Number of grid points in x-direction.
2375 @property 2376 def ny(self): 2377 """Number of grid points in y-direction.""" 2378 return int(self.gdt[8])
Number of grid points in y-direction.
2380 @property 2381 def npoints(self): 2382 """Total number of grid points.""" 2383 return int(self.gdt[7] * self.gdt[8])
Total number of grid points.
769def msgs_from_index(index: dict, filehandle=None): 770 """ 771 Construct a list of Grib2Message objects from an index dictionary. 772 773 This function reconstructs a sequence of `Grib2Message` instances using 774 metadata sections stored in an index dictionary. If an open file handle is 775 provided, each message is linked to its on-disk binary data through a 776 `Grib2MessageOnDiskArray`, allowing deferred reading of the actual data 777 values from the GRIB2 file. 778 779 Parameters 780 ---------- 781 index : dict 782 Dictionary containing parsed GRIB2 index information, including 783 section data arrays such as ``section0`` through ``section5``, 784 ``sectionOffset``, ``offset``, and ``bmapflag``. 785 filehandle : file-like object, optional 786 An open binary file handle to the GRIB2 file corresponding to the index. 787 If provided, the returned messages can access on-disk data arrays via 788 memory offsets. If not provided, only metadata will be available. 789 790 Returns 791 ------- 792 list of Grib2Message 793 List of reconstructed `Grib2Message` objects built from the provided 794 index. Each message contains metadata, and if `filehandle` is given, 795 also references to on-disk data through a `Grib2MessageOnDiskArray`. 796 797 Notes 798 ----- 799 - Each message is constructed by zipping the corresponding section entries 800 (sections 0–5 and bitmap flags). 801 - When a file handle is supplied, each message’s `_ondiskarray` attribute is 802 initialized to allow direct access to GRIB2 data values without loading 803 them fully into memory. 804 - The `_msgnum` attribute of each message is assigned sequentially to 805 preserve message order. 806 """ 807 zipped = zip( 808 index["section0"], 809 index["section1"], 810 index["section2"], 811 index["section3"], 812 index["section4"], 813 index["section5"], 814 index["bmapflag"] 815 ) 816 msgs = [Grib2Message(*sections) for sections in zipped] 817 818 if filehandle is not None: 819 for n, (msg, offset, secpos) in enumerate(zip(msgs, index["offset"], index["sectionOffset"])): 820 msg._ondiskarray = Grib2MessageOnDiskArray( 821 shape=(msg.ny, msg.nx), 822 ndim=2, 823 dtype=TYPE_OF_VALUES_DTYPE[msg.typeOfValues], 824 filehandle=filehandle, 825 msg=msg, 826 offset=offset, 827 bitmap_offset=secpos[6], 828 data_offset=secpos[7] 829 ) 830 msg._msgnum = n 831 return msgs
Construct a list of Grib2Message objects from an index dictionary.
This function reconstructs a sequence of Grib2Message instances using
metadata sections stored in an index dictionary. If an open file handle is
provided, each message is linked to its on-disk binary data through a
Grib2MessageOnDiskArray, allowing deferred reading of the actual data
values from the GRIB2 file.
Parameters
- index (dict):
Dictionary containing parsed GRIB2 index information, including
section data arrays such as
section0throughsection5,sectionOffset,offset, andbmapflag. - filehandle (file-like object, optional): An open binary file handle to the GRIB2 file corresponding to the index. If provided, the returned messages can access on-disk data arrays via memory offsets. If not provided, only metadata will be available.
Returns
- list of Grib2Message: List of reconstructed
Grib2Messageobjects built from the provided index. Each message contains metadata, and iffilehandleis given, also references to on-disk data through aGrib2MessageOnDiskArray.
Notes
- Each message is constructed by zipping the corresponding section entries (sections 0–5 and bitmap flags).
- When a file handle is supplied, each message’s
_ondiskarrayattribute is initialized to allow direct access to GRIB2 data values without loading them fully into memory. - The
_msgnumattribute of each message is assigned sequentially to preserve message order.