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}")
class open:
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): True is file handle is close; False otherwise.
  • 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).
open( filename: Union[bytes, str, pathlib.Path, IO], mode: Literal['r', 'w', 'x'] = 'r', *, save_index=True, use_index=True, _xarray_backend=False, **kwargs)
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.

indexfile
mode
save_index
size
use_index
closed
levels
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.

variables
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.

def close(self):
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.

def read(self, size: Optional[int] = None):
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: Grib2Message object when size = 1 or a list of Grib2Messages when size > 1.
def seek(self, pos: int):
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.
def tell(self):
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.

def select(self, **kwargs):
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.

def write(self, msg):
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.
def flush(self):
492    def flush(self):
493        """Flush the file object buffer."""
494        self._filehandle.flush()

Flush the file object buffer.

def levels_by_var(self, name: str):
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.
def vars_by_level(self, level: str):
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.
current_message
messages
name
def show_config():
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.

def interpolate( a, method: Union[int, str], grid_def_in, grid_def_out, method_options=None, num_threads=1):
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 a is a numpy.ndarray, scalar interpolation will be performed. If a is a tuple, then vector interpolation will be performed with the assumption that u = a[0] and v = a[1] and are both numpy.ndarray.

    These data are expected to be in 2-dimensional form with shape (ny, nx) or 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, temporal, or classification (i.e. ensemble members) dimension. The function will properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into the interpolation subroutines. 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.ndarray of dtype np.float32 when scalar interpolation is performed or a tuple of numpy.ndarrays when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
def interpolate_to_stations( a, method: Union[int, str], grid_def_in, lats, lons, method_options=None, num_threads=1):
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 a is a numpy.ndarray, scalar interpolation will be performed. If a is a tuple, then vector interpolation will be performed with the assumption that u = a[0] and v = a[1] and are both numpy.ndarray.

    These data are expected to be in 2-dimensional form with shape (ny, nx) or 3-dimensional (:, ny, nx) where the 1st dimension represents another spatial, temporal, or classification (i.e. ensemble members) dimension. The function will properly flatten the (ny,nx) dimensions into (nx * ny) acceptable for input into the interpolation subroutines. 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.ndarray of dtype np.float32 when scalar interpolation is performed or a tuple of numpy.ndarrays when vector interpolation is performed with the assumptions that 0-index is the interpolated u and 1-index is the interpolated v.
class Grib2Message:
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.
@dataclass
class _Grib2Message:
 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.

_Grib2Message( section0: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]], section1: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]], section2: bytes, section3: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]], section4: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]], section5: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]], bitMapFlag: grib2io.templates.Grib2Metadata = 255)
section0: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
section1: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
section2: bytes
section3: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
section4: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
section5: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
indicatorSection: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
identificationSection: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]

GRIB2 Section 1, Identification Section

year: int

Year of reference time

month: int

Month of reference time

day: int

Day of reference time

hour: int

Hour of reference time

minute: int

Minute of reference time

second: int

Second of reference time

refDate: datetime.datetime

Reference Date. NOTE: This is a datetime.datetime object.

gridDefinitionSection: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]

GRIB2 Section 3, Grid Definition Section

sourceOfGridDefinition: int
numberOfDataPoints: int

Number of Data Points

interpretationOfListOfNumbers: grib2io.templates.Grib2Metadata

Interpretation of List of Numbers

gridDefinitionTemplate: list

Grid definition template

earthShape: str

Description of the shape of the Earth

earthRadius: float

Radius of the Earth (Assumes "spherical")

earthMajorAxis: float

Major Axis of the Earth (Assumes "oblate spheroid" or "ellipsoid")

earthMinorAxis: float

Minor Axis of the Earth (Assumes "oblate spheroid" or "ellipsoid")

resolutionAndComponentFlags: list
ny: int

Number of grid points in the Y-direction (generally North-South)

nx: int

Number of grid points in the X-direction (generally East-West)

scanModeFlags: list
projParameters: dict

PROJ Parameters to define the reference system

productDefinitionTemplate: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]

Product Definition Template

numberOfPackedValues: int

Number of Packed Values

dataRepresentationTemplate: list

Data Representation Template

gdtn
1128    @property
1129    def gdtn(self):
1130        """Return Grid Definition Template Number"""
1131        return self.section3[4]

Return Grid Definition Template Number

gdt
1133    @property
1134    def gdt(self):
1135        """Return Grid Definition Template."""
1136        return self.gridDefinitionTemplate

Return Grid Definition Template.

pdtn
1138    @property
1139    def pdtn(self):
1140        """Return Product Definition Template Number."""
1141        return self.section4[1]

Return Product Definition Template Number.

pdt
1143    @property
1144    def pdt(self):
1145        """Return Product Definition Template."""
1146        return self.productDefinitionTemplate

Return Product Definition Template.

drtn
1148    @property
1149    def drtn(self):
1150        """Return Data Representation Template Number."""
1151        return self.section5[1]

Return Data Representation Template Number.

drt
1153    @property
1154    def drt(self):
1155        """Return Data Representation Template."""
1156        return self.dataRepresentationTemplate

Return Data Representation Template.

pdy
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').

griddef
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.

lats
1168    @property
1169    def lats(self):
1170        """Return grid latitudes."""
1171        return self.latlons()[0]

Return grid latitudes.

lons
1173    @property
1174    def lons(self):
1175        """Return grid longitudes."""
1176        return self.latlons()[1]

Return grid longitudes.

min
1178    @property
1179    def min(self):
1180        """Return minimum value of data."""
1181        return np.nanmin(self.data)

Return minimum value of data.

max
1183    @property
1184    def max(self):
1185        """Return maximum value of data."""
1186        return np.nanmax(self.data)

Return maximum value of data.

mean
1188    @property
1189    def mean(self):
1190        """Return mean value of data."""
1191        return np.nanmean(self.data)

Return mean value of data.

median
1193    @property
1194    def median(self):
1195        """Return median value of data."""
1196        return np.nanmedian(self.data)

Return median value of data.

shape
1198    @property
1199    def shape(self):
1200        """Return shape of data."""
1201        return self.griddef.shape

Return shape of data.

def attrs_by_section(self, sect: int, values: bool = False):
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.
def copy(self, deep: bool = 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.

def pack(self):
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.

data: <built-in function array>
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.

def flush_data(self):
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.

def latlons(self, *args, **kwrgs):
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.

def grid(self, unrotate: bool = True):
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, otherwise False, do not.
Returns
  • lats, lons (numpy.ndarray): Returns two numpy.ndarrays with dtype=numpy.float32 of grid latitudes and longitudes in units of degrees.
def map_keys(self):
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.
def to_bytes(self, validate: bool = True):
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. If False, message is output as is.
Returns
  • to_bytes: Returns GRIB2 formatted message as bytes.
def interpolate( self, method, grid_def_out, method_options=None, drtn=None, num_threads=1):
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.

def subset(self, lats, lons):
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.
def validate(self):
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
  • True if the packed GRIB2 message is complete and well-formed, False otherwise.
@dataclass
class Grib2GridDef:
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.

Grib2GridDef( gdtn: int, gdt: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]])
gdtn: int
gdt: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]
@classmethod
def from_section3(cls, section3):
2482    @classmethod
2483    def from_section3(cls, section3):
2484        return cls(section3[4], section3[5:])
nx
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.

ny
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.

npoints
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.

shape
2501    @property
2502    def shape(self):
2503        """Shape of the grid."""
2504        return (int(self.ny), int(self.nx))

Shape of the grid.

def to_section3(self):
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)

Return a full GRIB2 section3 array.

def msgs_from_index(index: dict, filehandle=None):
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 section0 through section5, sectionOffset, offset, and bmapflag.
  • 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 Grib2Message objects built from the provided index. Each message contains metadata, and if filehandle is given, also references to on-disk data through a Grib2MessageOnDiskArray.
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 _ondiskarray attribute is initialized to allow direct access to GRIB2 data values without loading them fully into memory.
  • The _msgnum attribute of each message is assigned sequentially to preserve message order.