diff --git a/.github/workflows/tests-conda.yml b/.github/workflows/tests-conda.yml
index db323cff..b263c1cc 100644
--- a/.github/workflows/tests-conda.yml
+++ b/.github/workflows/tests-conda.yml
@@ -38,59 +38,69 @@ jobs:
with:
fetch-depth: 2
- - name: CACHING - Anaconda packages
- uses: actions/cache@v3
- id: cache-pkg
- with:
- path: ~/conda_pkgs_dir
- key:
- conda-pkg-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{
- env.CACHE_NUMBER }}-${{ hashFiles('environment-test.yml') }}
- env:
- # Increase this value if `environment-test.yml` has not changed,
- # but you still want to reset the cache.
- CACHE_NUMBER: 0
+ #- name: CACHING - Anaconda packages
+ # uses: actions/cache@v3
+ # id: cache-pkg
+ # with:
+ # path: ~/conda_pkgs_dir
+ # key:
+ # conda-pkg-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{
+ # env.CACHE_NUMBER }}-${{ hashFiles('ci/environment.yml') }}
+ # env:
+ # # Increase this value if `environment.yml` has not changed,
+ # # but you still want to reset the cache.
+ # CACHE_NUMBER: 0
- - name: INSTALL - Anaconda setup (Mambaforge)
- uses: conda-incubator/setup-miniconda@v2
+ - name: INSTALL - Conda/Mamba setup (Miniforge)
+ uses: conda-incubator/setup-miniconda@v3
with:
+ auto-update-conda: true
python-version: ${{ matrix.python-version }}
- miniforge-variant: Mambaforge
miniforge-version: latest
mamba-version: "*"
use-mamba: true
- channels: conda-forge,defaults
+ channels: conda-forge
+ conda-remove-defaults: "true"
channel-priority: true
activate-environment: herbie-test
+ environment-file: ci/environment.yml
auto-activate-base: false
- use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly!
+ #use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly!
+
+ #- name: CACHING - Anaconda environment
+ # uses: actions/cache@v3
+ # id: cache-env
+ # with:
+ # path: ${{ env.CONDA }}/envs
+ # key:
+ # conda-env-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{
+ # env.CACHE_NUMBER }}-${{ hashFiles('ci/environment.yml') }}
+ # env:
+ # # Increase this value if `ci/environment.yml` has not changed,
+ # # but you still want to reset the cache.
+ # CACHE_NUMBER: 0
+
+ - name: DEBUG - mamba info
+ run: |
+ mamba --version
+ mamba info
+
+ - name: DEBUG - mamba list
+ run: mamba list
+
+ - name: DEBUG - mamba configuration
+ run: mamba config --show
- - name: CACHING - Anaconda environment
- uses: actions/cache@v3
- id: cache-env
- with:
- path: ${{ env.CONDA }}/envs
- key:
- conda-env-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{
- env.CACHE_NUMBER }}-${{ hashFiles('environment-test.yml') }}
- env:
- # Increase this value if `environment-test.yml` has not changed,
- # but you still want to reset the cache.
- CACHE_NUMBER: 0
-
- - name: DEBUG - Anaconda info
- run: conda info
- - name: DEBUG - Anaconda configuration
- run: conda config --show
- name: DEBUG - Environment variables
run: printenv | sort
+
- name: DEBUG - Program paths
run: |
command -v conda
command -v mamba
- - name: INSTALL - Update Anaconda environment
- run: mamba env update --name herbie-test --file environment-test.yml
+ - name: INSTALL - Update Mamba environment
+ run: mamba env update --name herbie-test --file ci/environment.yml
if: steps.cache-env.outputs.cache-hit != 'true'
- name: INSTALL - Project
diff --git a/check_pygrib_vs_herbie_crs_extraction.ipynb b/check_pygrib_vs_herbie_crs_extraction.ipynb
new file mode 100644
index 00000000..625724a0
--- /dev/null
+++ b/check_pygrib_vs_herbie_crs_extraction.ipynb
@@ -0,0 +1,5809 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Check CRS extraction\n",
+ "\n",
+ "I was having issues with pygrib running into segmentation faults in my GitHub Actions after it was updated to support Numpy v2+. I was only using pygrib to extract coordinate reference system (CRS) data, and I would like to remove it as a dependency.\n",
+ "\n",
+ "Here I am comparing the CRS proj parameters extracted by pygrib and what is extracted by Herbie (from the keys read by cfgrib). \n",
+ "\n",
+ "**Since pygrib doesn't work in GitHub actions for me for whatever reason, I should run this notebook before any release to manually test the values between pygrib and Herbie agree.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from herbie import Herbie\n",
+ "import pygrib\n",
+ "from pyproj import CRS\n",
+ "from herbie.crs import get_cf_crs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Model: HRRR\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: HRRRAK\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 225.0, 'proj': 'stere'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 90, 'lat_ts': 60.0, 'lon_0': 225.0, 'proj': 'stere'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: GFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "👨🏻🏭 Created directory: [/home/blaylock/data/graphcast/20250101]\n",
+ "\n",
+ "Model: GRAPHCAST\n",
+ " pygrib {'a': 4326.0, 'b': 4326.0, 'proj': 'longlat'}\n",
+ " Herbie {'a': 4326.0, 'b': 4326.0, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: CFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: IFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: AIFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: GEFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: GEFS\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: HAFSA\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: HREF\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: NAM\n",
+ " pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}\n",
+ " Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: URMA\n",
+ " pygrib {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " Herbie {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " equal= True\n",
+ "\n",
+ "Model: RTMA\n",
+ " pygrib {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " Herbie {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}\n",
+ " equal= True\n"
+ ]
+ }
+ ],
+ "source": [
+ "for model, search in [\n",
+ " (dict(model=\"hrrr\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"hrrrak\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"gfs\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"graphcast\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"cfs\", member=1, product=\"6_hourly\", kind=\"flxf\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"ifs\"), \":2t:\"),\n",
+ " (dict(model=\"aifs\"), \":2t:\"),\n",
+ " (dict(model=\"gefs\", member=1), \":TMP:2 m above\"),\n",
+ " (dict(model=\"gefs\", product=\"wave\", member=1), \":WIND:surf\"),\n",
+ " (dict(model=\"hafsa\", product=\"storm.atm\", storm=\"07s\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"href\", products=\"mean\", domain=\"conus\", fxx=1), \":TMP:2 m above\"),\n",
+ " (dict(model=\"nam\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"urma\"), \":TMP:2 m above\"),\n",
+ " (dict(model=\"rtma\"), \":TMP:2 m above\"),\n",
+ "]:\n",
+ " if model[\"model\"] in (\"hafsa\", \"href\"): # only on nomads\n",
+ " date = \"2025-01-16 06:00\"\n",
+ " else:\n",
+ " date = \"2025-01-01\"\n",
+ "\n",
+ " ds = Herbie(date, verbose=False, **model).xarray(\n",
+ " search, remove_grib=False, _use_pygrib_for_crs=False\n",
+ " )\n",
+ "\n",
+ " print()\n",
+ " print(f\"Model: {model['model'].upper()}\")\n",
+ " with pygrib.open(str(ds.local_grib)) as grb:\n",
+ " msg = grb.message(1)\n",
+ " projparams_dict_pygrib = dict(sorted(msg.projparams.items()))\n",
+ " print(\" pygrib\", projparams_dict_pygrib)\n",
+ "\n",
+ " projparams_dict_herbie = dict(\n",
+ " sorted(get_cf_crs(ds, _return_projparams=True).items())\n",
+ " )\n",
+ " print(\" Herbie\", projparams_dict_herbie)\n",
+ " print(\" equal=\", projparams_dict_herbie == projparams_dict_pygrib)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Check the crs accessor"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "✅ Found ┊ model=hrrr ┊ \u001b[3mproduct=sfc\u001b[0m ┊ \u001b[38;2;41;130;13m2025-Jan-01 00:00 UTC\u001b[92m F00\u001b[0m ┊ \u001b[38;2;255;153;0m\u001b[3mGRIB2 @ aws\u001b[0m ┊ \u001b[38;2;255;153;0m\u001b[3mIDX @ aws\u001b[0m\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/blaylock/GITHUB/Herbie/herbie/core.py:1117: UserWarning: Will not remove GRIB file because it previously existed.\n",
+ " warnings.warn(\"Will not remove GRIB file because it previously existed.\")\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "
<cartopy.crs.LambertConformal object at 0x7f42b9af7cd0>
"
+ ],
+ "text/plain": [
+ "\n",
+ "Name: unknown\n",
+ "Axis Info [cartesian]:\n",
+ "- E[east]: Easting (metre)\n",
+ "- N[north]: Northing (metre)\n",
+ "Area of Use:\n",
+ "- undefined\n",
+ "Coordinate Operation:\n",
+ "- name: unknown\n",
+ "- method: Lambert Conic Conformal (2SP)\n",
+ "Datum: unknown\n",
+ "- Ellipsoid: unknown\n",
+ "- Prime Meridian: Greenwich"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ds = Herbie(\"2025-1-1\").xarray(\":TMP:2 m ab\")\n",
+ "ds.herbie.crs"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "herbie-dev",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/ci/environment.yml b/ci/environment.yml
index c882e0ee..ff24f016 100644
--- a/ci/environment.yml
+++ b/ci/environment.yml
@@ -1,18 +1,30 @@
-name: herbie
+# Used to for Herbie tests on Windows
+# .github/workflows/tests-conda.yml
+
+name: herbie-test
channels:
- conda-forge
dependencies:
+ # Binaries
- curl
- - cfgrib>=0.9.10.4
- eccodes
- - matplotlib>=3.8.2
+ - geos
+ - proj
+ - python
+
+ # Python requirements
+ - cartopy
+ - cfgrib
+ - matplotlib
- metpy
- - numpy>=1.26.2
- - pandas>=2.1.4
- - pygrib>=2.1.6
- - pytest
- - requests>=2.31.0
+ - numpy
+ - pandas
+ - pyproj
+ - requests
- scikit-learn
- toml
- - xarray>=2023.12.0
- - flake8
+ - xarray
+
+ # Testing
+ - pytest
+ - pytest-cov
diff --git a/ci/requirements.txt b/ci/requirements.txt
index 3ce36f2a..75d314c4 100644
--- a/ci/requirements.txt
+++ b/ci/requirements.txt
@@ -2,7 +2,7 @@ cfgrib>=0.9.15
metpy>=1.3.0
numpy>=1.22.3
pandas>=2.2.3
-pygrib>=2.1.6
+pyproj>=3.7.0
requests>=2.32.3
toml>=0.10.2
xarray>=2025.1.1
diff --git a/environment-test.yml b/environment-test.yml
index 1c15cda5..f58140bf 100644
--- a/environment-test.yml
+++ b/environment-test.yml
@@ -1,10 +1,10 @@
-## Used to test Herbie install from PyPI
+## Used to test Herbie GitHub Actions for windows
name: herbie-test
channels:
- conda-forge
dependencies:
- - python>=3.8
+ - python>=3.12
- pip
#==============
diff --git a/herbie/__init__.py b/herbie/__init__.py
index 07277612..053e416e 100644
--- a/herbie/__init__.py
+++ b/herbie/__init__.py
@@ -205,3 +205,4 @@ def template(self):
from herbie.fast import FastHerbie
from herbie.latest import HerbieLatest, HerbieWait
from herbie.wgrib2 import wgrib2
+from herbie.accessors import HerbieAccessor
diff --git a/herbie/accessors.py b/herbie/accessors.py
index f8f0f4eb..3ccb972f 100644
--- a/herbie/accessors.py
+++ b/herbie/accessors.py
@@ -18,7 +18,6 @@
import numpy as np
import pandas as pd
-import pygrib
import xarray as xr
from pyproj import CRS
@@ -56,6 +55,10 @@
def add_proj_info(ds: xr.Dataset):
"""Add projection info to a Dataset."""
+ raise NotImplementedError("This function `add_proj_info` is not yet implemented.")
+
+ # TODO: remove pyproj dependency
+
match = re.search(r'"source": "(.*?)"', ds.history)
FILE = Path(match.group(1))
diff --git a/herbie/core.py b/herbie/core.py
index d8ae9996..c78f7c73 100644
--- a/herbie/core.py
+++ b/herbie/core.py
@@ -18,17 +18,17 @@
from datetime import datetime, timedelta
from io import StringIO
from shutil import which
-from typing import Union, Optional, Literal
+from typing import Literal, Optional, Union
import cfgrib
import pandas as pd
-import pygrib
import requests
import xarray as xr
from pyproj import CRS
import herbie.models as model_templates
from herbie import Path, config
+from herbie.crs import get_cf_crs
from herbie.help import _search_help
from herbie.misc import ANSI
@@ -38,17 +38,6 @@
# from the file ${HOME}/.config/herbie/config.toml
# Path is imported from __init__ because it has my custom methods.
-try:
- # Load custom xarray accessors
- import herbie.accessors # noqa: F401
-except Exception:
- warnings.warn(
- "herbie xarray accessors could not be imported."
- "Probably missing a dependency like MetPy."
- "If you want to use these functions, try"
- "`pip install metpy`"
- )
-
log = logging.getLogger(__name__)
# Location of wgrib2 command, if it exists. Required to make missing idx files.
@@ -1067,6 +1056,7 @@ def xarray(
searchString=None,
backend_kwargs: dict = {},
remove_grib: bool = True,
+ _use_pygrib_for_crs: bool = False,
**download_kwargs,
) -> xr.Dataset:
"""
@@ -1079,6 +1069,10 @@ def xarray(
remove_grib : bool
If True, grib file will be removed ONLY IF it didn't exist
before we downloaded it.
+ _use_pygrib_for_crs : bool
+ If you have pygrib, you can use it to extract the CRS
+ information instead of using values extracted from cfgrib
+ by Herbie.
"""
# TODO: Remove this eventually
if searchString is not None:
@@ -1128,7 +1122,19 @@ def xarray(
backend_kwargs.setdefault("indexpath", "")
backend_kwargs.setdefault(
"read_keys",
- ["parameterName", "parameterUnits", "stepRange", "uvRelativeToGrid"],
+ [
+ "parameterName",
+ "parameterUnits",
+ "stepRange",
+ "uvRelativeToGrid",
+ "shapeOfTheEarth",
+ "orientationOfTheGridInDegrees",
+ "southPoleOnProjectionPlane",
+ "LaDInDegrees",
+ "LoVInDegrees",
+ "Latin1InDegrees",
+ "Latin2InDegrees",
+ ],
)
backend_kwargs.setdefault("errors", "raise")
@@ -1139,20 +1145,20 @@ def xarray(
backend_kwargs=backend_kwargs,
)
- # Get CF grid projection information with pygrib and pyproj because
- # this is something cfgrib doesn't do (https://github.com/ecmwf/cfgrib/issues/251)
- # NOTE: Assumes the projection is the same for all variables
- # TODO: Issues with pygrib in tests. Segmentation Fault. Is it Numpy 2???
- use_pygrib = False
- if use_pygrib:
- with pygrib.open(str(local_file)) as grb:
- msg = grb.message(1)
- cf_params = CRS(msg.projparams).to_cf()
+ for ds in Hxr:
+ # Need model attribute before using get_cf_crs
+ ds.attrs["model"] = str(self.model)
- #grb = pygrib.open(str(local_file))
- #msg = grb.message(1)
- #cf_params = CRS(msg.projparams).to_cf()
- #grb.close()
+ # Get CF convention coordinate reference system (crs) information.
+ # NOTE: Assumes the projection is the same for all variables.
+ if _use_pygrib_for_crs:
+ # Get CF grid projection information with pygrib and pyproj because
+ # this is something cfgrib doesn't do (https://github.com/ecmwf/cfgrib/issues/251)
+ import pygrib
+
+ with pygrib.open(str(local_file)) as grb:
+ msg = grb.message(1)
+ cf_params = CRS(msg.projparams).to_cf()
# Funny stuff with polar stereographic (https://github.com/pyproj4/pyproj/issues/856)
# TODO: Is there a better way to handle this? What about south pole?
@@ -1161,7 +1167,7 @@ def xarray(
"latitude_of_projection_origin", 90
)
else:
- cf_params = {}
+ cf_params = get_cf_crs(Hxr[0])
# Here I'm looping over each dataset in the list returned by cfgrib
for ds in Hxr:
diff --git a/herbie/crs.py b/herbie/crs.py
new file mode 100644
index 00000000..b02e9d64
--- /dev/null
+++ b/herbie/crs.py
@@ -0,0 +1,172 @@
+"""CF convention coordinate reference system (CRS).
+
+Check out the notebook `check_pygrib_vs_herbie_crs_extraction.ipynb`
+to test how Herbie and pygrib are extracting the CRS information.
+"""
+
+from typing import Any
+
+from pyproj import CRS
+
+import xarray as xr
+
+
+def get_cf_crs(
+ ds: "xr.Dataset", variable: str | None = None, _return_projparams=False
+) -> dict[str, Any]:
+ """
+ Extract the CF coordinate reference system (CRS) from a cfgrib xarray dataset.
+
+ Note:
+ I originally used pygrib to do this, but it was hard to maintain an
+ additional grib package dependency. I had issues with pygrib after
+ it was updated to support Numpy version 2, so thought it would be
+ best to code this in Herbie. This may be incomplete.
+ """
+ # Assume the first variable in the Dataset has the same grid crs
+ # as all other variables in the Dataset.
+ if variable is None:
+ variable = next(iter(ds.data_vars))
+ da = ds[variable]
+
+ # Shape of the Earth reference system
+ # https://codes.ecmwf.int/grib/format/grib2/ctables/3/2/
+ shapeOfTheEarth = da.GRIB_shapeOfTheEarth
+ if shapeOfTheEarth == 0:
+ # Earth assumed spherical with radius = 6 367 470.0 m
+ a = 6_367_470
+ b = 6_367_470
+ elif shapeOfTheEarth == 1 and ds.attrs["model"] == "graphcast":
+ # Earth assumed spherical with radius specified (in m) by data producer
+ # TODO: Why is model='graphcast' using this value?
+ a = 4326.0
+ b = 4326.0
+ elif shapeOfTheEarth == 1 and ds.attrs["model"] in ["urma", "rtma"]:
+ # Earth assumed spherical with radius specified (in m) by data producer
+ # TODO: Why is urma and rtma using this value?
+ a = 6371200.0
+ b = 6371200.0
+ elif shapeOfTheEarth == 6:
+ # Earth assumed spherical with radius of 6,371,229.0 m
+ a = 6_371_229
+ b = 6_371_229
+
+ # Grid type definition
+ # https://codes.ecmwf.int/grib/format/grib2/ctables/3/1/
+ if da.GRIB_gridType == "lambert":
+ projparams = {"proj": "lcc"}
+ projparams["a"] = a
+ projparams["b"] = b
+ projparams["lon_0"] = da.GRIB_LoVInDegrees
+ projparams["lat_0"] = da.GRIB_LaDInDegrees
+ projparams["lat_1"] = da.GRIB_Latin1InDegrees
+ projparams["lat_2"] = da.GRIB_Latin2InDegrees
+
+ elif da.GRIB_gridType == "regular_ll":
+ projparams = {"proj": "longlat"}
+ projparams["a"] = a
+ projparams["b"] = b
+
+ elif da.GRIB_gridType == "regular_gg":
+ projparams = {"proj": "longlat"}
+ projparams["a"] = a
+ projparams["b"] = b
+
+ elif da.GRIB_gridType == "polar_stereographic":
+ projparams = {"proj": "stere"}
+ projparams["a"] = a
+ projparams["b"] = b
+ projparams["lat_ts"] = da.GRIB_LaDInDegrees
+ projparams["lat_0"] = 90
+ projparams["lon_0"] = da.GRIB_orientationOfTheGridInDegrees
+
+ else:
+ raise NotImplementedError(f"gridType {da.GRIB_gridType} is not implemented.")
+
+ if _return_projparams:
+ return projparams
+ else:
+ return CRS(projparams).to_cf()
+
+
+"""
+Look at how pygrib parses with this...
+with pygrib.open(str(ds.local_grib)) as grb:
+ msg = grb.message(1)
+ print(msg.projparams)
+
+Also, look for clues by dumping all keys with:
+grib_dump -j > filedump.json
+
+-----------------------------------------
+
+Model: HRRR
+ pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}
+ Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}
+ equal= True
+
+Model: HRRRAK
+ pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 225.0, 'proj': 'stere'}
+ Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 90, 'lat_ts': 60.0, 'lon_0': 225.0, 'proj': 'stere'}
+ equal= True
+
+Model: GFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: GRAPHCAST
+ pygrib {'a': 4326.0, 'b': 4326.0, 'proj': 'longlat'}
+ Herbie {'a': 4326.0, 'b': 4326.0, 'proj': 'longlat'}
+ equal= True
+
+Model: CFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: IFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: AIFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: GEFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: GEFS
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: HAFSA
+ pygrib {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ Herbie {'a': 6371229, 'b': 6371229, 'proj': 'longlat'}
+ equal= True
+
+Model: HREF
+ pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ equal= True
+
+Model: NAM
+ pygrib {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}
+ Herbie {'a': 6371229, 'b': 6371229, 'lat_0': 38.5, 'lat_1': 38.5, 'lat_2': 38.5, 'lon_0': 262.5, 'proj': 'lcc'}
+ equal= True
+
+Model: URMA
+ pygrib {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ Herbie {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ equal= True
+
+Model: RTMA
+ pygrib {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ Herbie {'a': 6371200.0, 'b': 6371200.0, 'lat_0': 25.0, 'lat_1': 25.0, 'lat_2': 25.0, 'lon_0': 265.0, 'proj': 'lcc'}
+ equal= True
+"""
diff --git a/herbie/models/cfs.py b/herbie/models/cfs.py
index 6fa4ea90..2c0319d9 100644
--- a/herbie/models/cfs.py
+++ b/herbie/models/cfs.py
@@ -136,10 +136,6 @@
class cfs:
def template(self):
- warnings.warn(
- "Herbie's CFS templates are and subject to major changes. PRs are welcome to improve it."
- )
-
self.DESCRIPTION = "Climate Forecast System"
self.DETAILS = {
"NOMADS product description": "https://www.nco.ncep.noaa.gov/pmb/products/cfs/",
diff --git a/herbie/models/gdps.py b/herbie/models/gdps.py
index a8f8c927..2788fc86 100644
--- a/herbie/models/gdps.py
+++ b/herbie/models/gdps.py
@@ -2,7 +2,7 @@
## April 9, 2024
"""
-A Herbie template for the GEM Global or Global Deterministic Prediction System (GDPS)
+A Herbie template for the GEM Global or Global Deterministic Prediction System (GDPS).
Meteorological Service of Canada (MSC)
The GDPS is Canada's 15 km deterministic global model
diff --git a/herbie/models/hrdps.py b/herbie/models/hrdps.py
index e3ec2de6..5c05d5aa 100644
--- a/herbie/models/hrdps.py
+++ b/herbie/models/hrdps.py
@@ -4,7 +4,7 @@
## April 9, 2024
"""
-A Herbie template for the High Resolution Deterministic Prediction System (HRDPS)
+A Herbie template for the High Resolution Deterministic Prediction System (HRDPS).
Meteorological Service of Canada (MSC)
The HRDPS is Canada's 2.5 km deterministic model
diff --git a/herbie/models/href.py b/herbie/models/href.py
index 7ab56c52..31d6fa92 100644
--- a/herbie/models/href.py
+++ b/herbie/models/href.py
@@ -1,7 +1,7 @@
## Added by Karl Schneider (June 2024)
"""
-A Herbie template for the The High Resolution Ensemble Forecast (HREF)
+A Herbie template for the The High Resolution Ensemble Forecast (HREF).
Description
-----------
@@ -66,4 +66,4 @@ def template(self):
"nomads": f"https://nomads.ncep.noaa.gov/pub/data/nccf/com/href/prod/href.{self.date:%Y%m%d}/ensprod/href.t{self.date:%H}z.{self.domain}.{self.product}.f{self.fxx:02d}.grib2"
}
self.IDX_SUFFIX = [".grib2.idx", ".idx"]
- self.LOCALFILE = f"{self.get_remoteFileName}"
\ No newline at end of file
+ self.LOCALFILE = f"{self.get_remoteFileName}"
diff --git a/herbie/models/rdps.py b/herbie/models/rdps.py
index 61acf553..132367c6 100644
--- a/herbie/models/rdps.py
+++ b/herbie/models/rdps.py
@@ -2,7 +2,7 @@
## April 9, 2024
"""
-A Herbie template for the GEM Regional or Regional Deterministic Prediction System (RDPS)
+A Herbie template for the GEM Regional or Regional Deterministic Prediction System (RDPS).
Meteorological Service of Canada (MSC)
The RDPS is Canada's 10 km deterministic regional model
diff --git a/pyproject.toml b/pyproject.toml
index 3517d18f..6b8bac6b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,10 +9,10 @@ maintainers = [{ name = "Brian K. Blaylock", email = "blaylockbk@gmail.com" }]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
@@ -33,9 +33,9 @@ dependencies = [
"cfgrib>=0.9.15",
"numpy>=2.2.1",
"pandas>=2.2.3",
- "pygrib==2.1.6",
+ "pyproj>=3.7.0",
"requests>=2.23.3",
- "toml>=0.10.2",
+ "toml>=0.10.2", # TODO: Drop in favor of tomllib when Python >=3.11 is required.
"xarray>=2025.1.1",
]
dynamic = ["version"]
@@ -49,6 +49,8 @@ dynamic = ["version"]
[project.optional-dependencies]
extras = ["cartopy", "metpy", "scikit-learn"]
+test = ["pytest", "pytest-cov", "ruff"]
+pygrib = ["pygrib"]
docs = [
"autodocsumm",
"ipython",
@@ -65,7 +67,6 @@ docs = [
"sphinx-markdown-tables",
"sphinxcontrib-mermaid",
]
-test = ["pytest", "pytest-cov", "ruff"]
[build-system]
requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
diff --git a/requirements.txt b/requirements.txt
index 2932dfdb..7cbfa381 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,7 @@ matplotlib
metpy
numpy
pandas
-pygrib
+pyproj
scikit-learn
toml
xarray