Source code for herbie.wgrib2

## Brian Blaylock
## January 28, 2023

"""
==================
Wrapper for wgrib2
==================

The ``wgrib2`` utility has some useful features. However, it can not
be installed on windows (at least, not very easily).

Usage
-----

.. code:: python

    from herbie.wgrib2 import wgrib2

    # Get path to wgrib2 executable
    print(wgrib2.wgrib2)

    # Get Inventory for a GRIB2 file as string
    wgrib2.inventory("/path/to/file.grib2")

    # Create .idx files for GRIB2 file
    wgrib2.create_inventory_file("/path/to/file.grib2")

"""

from herbie import Path
from shutil import which
import subprocess


def run_command(cmd):
    p = subprocess.run(
        cmd,
        shell=True,
        capture_output=True,
        encoding="utf-8",
        check=True,
    )
    return p.stdout


[docs] class _WGRIB2: """Wrapper for wgrib2 program.""" # PATH to wgrib2 executable wgrib2 = which("wgrib2")
[docs] def inventory(self, FILE): """Return wgrib2-style inventory of GRIB2 file.""" cmd = f"{self.wgrib2} -s {Path(FILE).expand()}" return run_command(cmd)
[docs] def create_inventory_file(self, path, suffix=".grib2"): """Create and save wgrib2 inventory files for GRIB2 file/files. Note that this will overwrite any existing inventory file. Parameters ---------- path : pathlib.path If path is a file, then make inventory file for that file. If path is a directory, then make inventory files for all files with the indicated suffix. suffix : {".grib2", ".grib", ".grb", etc.} If path specified is a directory, then this is the suffix to look for GRIB2 files. """ path = Path(path).expand() if path.is_dir(): # List all GRIB2 files in the directory files = list(path.rglob(f"*{suffix}")) elif path.is_file(): # The path is a single file files = [path] if not files: raise ValueError(f"No grib2 files were found in {path}") idx_files = [] for f in files: f_idx = Path(str(f) + ".idx") idx_files.append(f_idx) index_data = self.inventory(Path(f)) with open(f_idx, "w+") as out_idx: out_idx.write(index_data) if len(idx_files) == 1: return idx_files[0] else: return idx_files
[docs] def region(self, path, extent, *, name="region", suffix=".grib2", create_idx=True): """Subset a GRIB2 file by geographical region. See https://www.cpc.ncep.noaa.gov/products/wesley/wgrib2/small_grib.html Parameters ---------- path : path-like Path to the grib2 file you wish to subset into a region. If path is a file, then make region subset for that file. If path is a directory, then make region subset for all files with the indicated suffix. extent : 4-item tuple or list Longitude and Latitude bounds representing the region of interest. (lon_min, lon_max, lat_min, lat_max) : float name : str Name of the region. Output grib will be saved to a new file with the ``name`` prepended to the filename. suffix : {".grib2", ".grib", ".grb", etc.} If path specified is a directory, then this is the suffix to look for GRIB2 files. create_idx : bool If True, then make an inventory file for the GRIB2 region subest. """ path = Path(path).expand() if path.is_dir(): # List all GRIB2 files in the directory files = list(path.rglob(f"*{suffix}")) elif path.is_file(): # The path is a single file files = [path] if not files: raise ValueError(f"No grib2 files were found in {path}") if len(extent) != 4: raise TypeError( "Region extent must be (lon_min, lon_max, lat_min, lat_max)" ) lon_min, lon_max, lat_min, lat_max = extent OUTFILES = [] for f in files: OUTFILE = path.parent / f"{name}_{path.name}" cmd = f"{self.wgrib2} {Path(path).expand()} -small_grib {lon_min}:{lon_max} {lat_min}:{lat_max} {OUTFILE} -set_grib_type same" run_command(cmd) if name is None: OUTFILE.rename(f) self.create_inventory_file(f) OUTFILES.append(f) else: self.create_inventory_file(OUTFILE) OUTFILES.append(OUTFILE) if len(OUTFILES) == 1: return OUTFILES[0] else: return OUTFILES
[docs] def vector_relative(self, path): """ Check if vector quantities are "grid relative" or "earth relative" See "What are Earth and Grid Relative Winds" on https://www.cpc.ncep.noaa.gov/products/wesley/wgrib2/new_grid_intro.html Read my thought on the subject https://github.com/blaylockbk/pyBKB_v2/blob/master/demos/HRRR_earthRelative_vs_gridRelative_winds.ipynb Parameters ---------- path : path-like Path to the grib2 file. """ cmd = f"{self.wgrib2} -vector_dir {Path(path).expand()}" out = run_command(cmd) relative = {i.split(":")[-1] for i in out.split()} if relative == {"winds(grid)"}: print("All winds are grid-relative winds.") elif relative == {"winds(earth)"}: print("All winds are earth-relative winds.") else: print("Mixed vector relative winds; pay attention to output.") return relative
wgrib2 = _WGRIB2()