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