grib2io

Introduction

grib2io is a Python package that provides an interface to the NCEP GRIB2 C (g2c) library for the purpose of reading and writing WMO GRIdded Binary, Edition 2 (GRIB2) messages. A physical file can contain one or more GRIB2 messages.

GRIB2 file IO is performed directly in Python. The unpacking/packing of GRIB2 integer, coded metadata and data sections is performed by the g2c library functions via the g2clib Cython extension module. The decoding/encoding of GRIB2 metadata is translated into more descriptive, plain language metadata by providing a mapping of the integer coded metadata to the appropriate GRIB2 code tables. The NCEP GRIB2 code tables are a part of the grib2io package.

Index File

As of v2.6.0, grib2io provides the capability to create (and read) an index file for a GRIB2 file. The index file contents are specific to Python and grib2io in that pickle is used to dump the index dictionary to file. Using index files can dramatically improve performance in situations where the same file will be read multiple times. The index file name is the original GRIB2 file name with a hash string appended, followed by the file extension, .grib2ioidx. The hash string is the SHA-1 of the GRIB2 file name and the file size. For example, GRIB2 file,

gfs.t00z.pgrb2.1p00.f024

when opened, grib2io will generate an index file with the following name,

gfs.t00z.pgrb2.1p00.f024_0422a93bfd6d095bd0a942ba5e9fe42e76050123.grib2ioidx

By default, grib2io will generate an index file or use an existing one. The generation and usage of a grib2io index file can be turned off by providing kwargs use_index=False and/or save_index=False in grib2io.open().

Interpolation

As of v2.4.0, spatial interpolation via NCEPLIPS-ip Fortran library is now a part of the grib2io package. Previously, interpolation was handled by a separate component package, grib2io-interp, which is now deprecated. grib2io-interp provided interpolation via a F2PY-generated interface to NCEPLIBS-ip, which has become difficult since the removal of distutils from Python 3.12+.

NCEPLIBS-ip interpolation Fortran subroutines contain the BIND(C) attribute which provides an equivalent C-interface. grib2io now provides a Cython-based interface, iplib, to these Fortran subroutines via their C-interface. If NCEPLIBS-ip was built with OpenMP support, iplib will provide functions for getting and setting the number of OpenMP threads.

Xarray Backend

grib2io provides a Xarray backend engine so that many GRIB2 messages can be represented as N-dimensional DataArray objects and collected along common coordinates as Datasets or DataTrees. The Xarray backend engine API is experimental and is subject to change without backward compatibility.

Tutorials

The following Jupyter Notebooks are available as tutorials:

 1from ._grib2io import *
 2from ._grib2io import __doc__
 3from ._grib2io import _Grib2Message
 4
 5__all__ = ['open', 'show_config', 'interpolate', 'interpolate_to_stations',
 6           'tables', 'templates', 'utils',
 7           'Grib2Message', '_Grib2Message', 'Grib2GridDef', 'msgs_from_index']
 8
 9try:
10    from . import __config__
11    __version__ = __config__.grib2io_version
12    has_interpolation = __config__.has_interpolation
13    has_openmp_support = __config__.has_openmp_support
14    g2c_static = __config__.g2c_static
15    ip_static = __config__.ip_static
16    extra_objects = __config__.extra_objects
17except(ImportError):
18    pass
19
20from .g2clib import __version__ as __g2clib_version__
21from .g2clib import _has_jpeg
22from .g2clib import _has_png
23from .g2clib import _has_aec
24
25has_jpeg_support = bool(_has_jpeg)
26has_png_support  = bool(_has_png)
27has_aec_support = bool(_has_aec)
28
29from .tables.originating_centers import _ncep_grib2_table_version
30ncep_grib2_table_version = _ncep_grib2_table_version
31g2c_version = __g2clib_version__
32
33def show_config():
34    """Print grib2io build configuration information."""
35    print(f'grib2io version {__version__} Configuration:')
36    print(f'')
37    print(f'NCEPLIBS-g2c library version: {__g2clib_version__}')
38    print(f'\tStatic library: {g2c_static}')
39    print(f'\tJPEG compression support: {has_jpeg_support}')
40    print(f'\tPNG compression support: {has_png_support}')
41    print(f'\tAEC compression support: {has_aec_support}')
42    print(f'')
43    print(f'NCEPLIPS-ip support: {has_interpolation}')
44    print(f'\tStatic library: {ip_static}')
45    print(f'\tOpenMP support: {has_openmp_support}')
46    print(f'')
47    print(f'Static libs:')
48    for lib in extra_objects:
49        print(f'\t{lib}')
50    print(f'')
51    print(f'NCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
class open:
116class open():
117    """
118    GRIB2 File Object.
119
120    This class can accommodate a physical file with one or more GRIB2
121    messages or a bytes object containing a GRIB2 messages.
122
123    A physical file can contain one or more GRIB2 messages.  When instantiated,
124    class `grib2io.open`, the file named `filename` is opened for reading (`mode
125    = 'r'`) and is automatically indexed.  The indexing procedure reads some of
126    the GRIB2 metadata for all GRIB2 Messages.  A GRIB2 Message may contain
127    submessages whereby Section 2-7 can be repeated.  grib2io accommodates for
128    this by flattening any GRIB2 submessages into multiple individual messages.
129
130    It is important to note that GRIB2 files from some Meteorological agencies
131    contain other data than GRIB2 messages.  GRIB2 files from ECMWF can contain
132    GRIB1 and GRIB2 messages.  grib2io checks for these and safely ignores them.
133
134    Attributes
135    ----------
136    closed : bool
137        `True` is file handle is close; `False` otherwise.
138    current_message : int
139        Current position of the file in units of GRIB2 Messages. (read only)
140    levels : tuple
141        Tuple containing a unique list of wgrib2-formatted level/layer strings.
142    messages : int
143        Count of GRIB2 Messages contained in the file.
144    mode : str
145        File IO mode of opening the file.
146    name : str
147        Full path name of the GRIB2 file.
148    save_index : bool
149        Whether to save a pickle-based index file for the GRIB2 file. Default is `True`.
150    size : int
151        Size of the file in units of bytes.
152    use_index
153        Whether to use an existing pickle-based index file for the GRIB2 file. Default is `True`.
154    variables : tuple
155        Tuple containing a unique list of variable short names (i.e. GRIB2
156        abbreviation names).
157    """
158
159    __slots__ = ('_fileid', '_filehandle', '_hasindex', '_index',
160                 '_pos', 'closed', 'current_message', 'messages', 'mode',
161                 'name', 'size', 'save_index', 'use_index', '_msgs')
162
163    def __init__(
164        self,
165        filename: Union[bytes, str, Path],
166        mode: Literal["r", "w", "x"] = "r",
167        *,
168        save_index = True,
169        use_index = True,
170        _xarray_backend = False,
171        **kwargs,
172        ):
173        """
174        Initialize GRIB2 File object instance.
175
176        Parameters
177        ----------
178        filename
179            File path containing GRIB2 messages OR bytes.
180        mode
181            File access mode where "r" opens the files for reading only; "w"
182            opens the file for overwriting and "x" for writing to a new file.
183        save_index
184            Whether to save a pickle-based index file for the GRIB2 file. Default is True.
185
186            .. versionadded:: 2.6.0
187        use_index
188            Whether to use an existing pickle-based index file for the GRIB2 file. Default is True.
189
190            .. versionadded:: 2.6.0
191        """
192
193        # All write modes are read/write.
194        # All modes are binary.
195        if mode in ("a", "x", "w"):
196            mode += "+"
197        mode = mode + "b"
198
199        self.mode = mode
200        self.save_index = save_index
201        self.use_index = use_index
202
203        if isinstance(filename, bytes):
204            if 'r' not in self.mode:
205                raise ValueError("bytes grib2 can only be opened as read")
206            self.current_message = 0
207            if filename[:2] == _GZIP_HEADER:
208                filename = gzip.decompress(filename)
209            if filename[:4]+filename[-4:] != b'GRIB7777':
210                raise ValueError("Invalid GRIB bytes")
211            self._filehandle = BytesIO(filename)
212            self.name = "<in-memory-file>"
213            self.size = len(filename)
214            self._fileid = hashlib.sha1((self.name+str(self.size)).encode('ASCII')).hexdigest()
215            self._index = build_index(self._filehandle)
216            self._msgs = msgs_from_index(self._index, filehandle=self._filehandle)
217            self.messages = len(self._msgs)
218
219        else:
220            self.current_message = 0
221            if 'r' in mode:
222                self._filehandle = builtins.open(filename, mode=mode)
223                # Some GRIB2 files are gzipped, so check for that here, but
224                # raise error when using xarray backend.
225                # Gzip files contain a 2-byte header b'\x1f\x8b'.
226                if self._filehandle.read(2) == _GZIP_HEADER:
227                    self._filehandle.close()
228                    if _xarray_backend:
229                        raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.')
230                    self._filehandle = gzip.open(filename, mode=mode)
231                else:
232                    self._filehandle = builtins.open(filename, mode=mode)
233            else:
234                self._filehandle = builtins.open(filename, mode=mode)
235                self.name = os.path.abspath(filename)
236            self.name = os.path.abspath(filename)
237            fstat = os.stat(self.name)
238            self.size = fstat.st_size
239            self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+
240                                     str(self.size)).encode('ASCII')).hexdigest()
241
242            if 'r' in self.mode:
243
244                indexfile = f"{self.name}_{self._fileid}.grib2ioidx"
245                if self.use_index and os.path.exists(indexfile):
246                    try:
247                        with builtins.open(indexfile, 'rb') as file:
248                            index = pickle.load(file)
249                        self._index = index
250                    except Exception as e:
251                        print(f"found indexfile: {indexfile}, but unable to load it: {e}")
252                        print(f"re-forming index from grib2file, but not writing indexfile")
253                        self._index = build_index(self._filehandle)
254                else:
255                    self._index = build_index(self._filehandle)
256                    if self.save_index:
257                        try:
258                            serialize_index(self._index, indexfile)
259                        except Exception as e:
260                            print(f"index was not serialized for future use: {e}")
261
262                self._msgs = msgs_from_index(self._index, filehandle=self._filehandle)
263
264                self.messages = len(self._msgs)
265            elif 'w' or 'x' in self.mode:
266                self.messages = 0
267                self.current_message = None
268
269        self.closed = self._filehandle.closed
270
271
272    def __delete__(self, instance):
273        self.close()
274        del self._index
275
276
277    def __enter__(self):
278        return self
279
280
281    def __exit__(self, atype, value, traceback):
282        self.close()
283
284
285    def __iter__(self):
286        yield from self._msgs
287
288
289    def __len__(self):
290        return self.messages
291
292
293    def __repr__(self):
294        strings = []
295        for k in self.__slots__:
296            if k.startswith('_'): continue
297            strings.append('%s = %s\n'%(k,eval('self.'+k)))
298        return ''.join(strings)
299
300
301    def __getitem__(self, key):
302        if isinstance(key,int):
303            if abs(key) >= len(self._msgs):
304                raise IndexError("index out of range")
305            else:
306                return self._msgs[key]
307        elif isinstance(key,str):
308            return self.select(shortName=key)
309        elif isinstance(key,slice):
310            return self._msgs[key]
311        else:
312            raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.')
313
314
315
316    @property
317    def levels(self):
318        """Provides a unique tuple of level strings."""
319        if self._hasindex:
320            return tuple(sorted(set([msg.level for msg in self._msgs])))
321        else:
322            return None
323
324
325    @property
326    def variables(self):
327        """Provides a unique tuple of variable shortName strings."""
328        if self._hasindex:
329            return tuple(sorted(set([msg.shortName for msg in self._msgs])))
330        else:
331            return None
332
333
334    def close(self):
335        """Close the file handle."""
336        if not self._filehandle.closed:
337            self.messages = 0
338            self.current_message = 0
339            self._filehandle.close()
340            self.closed = self._filehandle.closed
341
342
343    def read(self, size: Optional[int]=None):
344        """
345        Read size amount of GRIB2 messages from the current position.
346
347        If no argument is given, then size is None and all messages are returned
348        from the current position in the file. This read method follows the
349        behavior of Python's builtin open() function, but whereas that operates
350        on units of bytes, we operate on units of GRIB2 messages.
351
352        Parameters
353        ----------
354        size: default=None
355            The number of GRIB2 messages to read from the current position. If
356            no argument is give, the default value is None and remainder of
357            the file is read.
358
359        Returns
360        -------
361        read
362            ``Grib2Message`` object when size = 1 or a list of Grib2Messages
363            when size > 1.
364        """
365        if size is not None and size < 0:
366            size = None
367        if size is None or size > 1:
368            start = self.tell()
369            stop = self.messages if size is None else start+size
370            if size is None:
371                self.current_message = self.messages-1
372            else:
373                self.current_message += size
374            return self._msgs[slice(start,stop,1)]
375        elif size == 1:
376            self.current_message += 1
377            return self._msgs[self.current_message]
378        else:
379            None
380
381
382    def seek(self, pos: int):
383        """
384        Set the position within the file in units of GRIB2 messages.
385
386        Parameters
387        ----------
388        pos
389            The GRIB2 Message number to set the file pointer to.
390        """
391        if self._hasindex:
392            self._filehandle.seek(self._index['sectionOffset'][0][pos])
393            self.current_message = pos
394
395
396    def tell(self):
397        """Returns the position of the file in units of GRIB2 Messages."""
398        return self.current_message
399
400
401    def select(self, **kwargs):
402        """Select GRIB2 messages by `Grib2Message` attributes."""
403        # TODO: Added ability to process multiple values for each keyword (attribute)
404        idxs = []
405        nkeys = len(kwargs.keys())
406        for k,v in kwargs.items():
407            for m in self._msgs:
408                if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum)
409        idxs = np.array(idxs,dtype=np.int32)
410        return [self._msgs[i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]
411
412
413    def write(self, msg):
414        """
415        Writes GRIB2 message object to file.
416
417        Parameters
418        ----------
419        msg
420            GRIB2 message objects to write to file.
421        """
422        if isinstance(msg,list):
423            for m in msg:
424                self.write(m)
425            return
426
427        if issubclass(msg.__class__,_Grib2Message):
428            # TODO: We can consider letting pack return packed bytes instead of associating with message object
429            if hasattr(msg,'_msg'):
430                # write already packed bytes
431                self._filehandle.write(msg._msg)
432            else:
433                if msg._signature == msg._generate_signature() and \
434                   msg._data is None and \
435                   hasattr(msg._ondiskarray,'filehandle'):
436                   # write unchanged message from input
437                   offset = msg._ondiskarray.filehandle.tell()
438                   msg._ondiskarray.filehandle.seek(msg._ondiskarray.offset)
439                   self._filehandle.write(msg._ondiskarray.filehandle.read(msg.section0[-1]))
440                   msg._ondiskarray.filehandle.seek(offset)
441                else:
442                    msg.pack()
443                    self._filehandle.write(msg._msg)
444            self.size = os.path.getsize(self.name)
445            self.messages += 1
446        else:
447            raise TypeError("msg must be a Grib2Message object.")
448        return
449
450
451    def flush(self):
452        """Flush the file object buffer."""
453        self._filehandle.flush()
454
455
456    def levels_by_var(self, name: str):
457        """
458        Return a list of level strings given a variable shortName.
459
460        Parameters
461        ----------
462        name
463            Grib2Message variable shortName
464
465        Returns
466        -------
467        levels_by_var
468            A list of unique level strings.
469        """
470        return list(sorted(set([msg.level for msg in self.select(shortName=name)])))
471
472
473    def vars_by_level(self, level: str):
474        """
475        Return a list of variable shortName strings given a level.
476
477        Parameters
478        ----------
479        level
480            Grib2Message variable level
481
482        Returns
483        -------
484        vars_by_level
485            A list of unique variable shortName strings.
486        """
487        return list(sorted(set([msg.shortName for msg in self.select(level=level)])))

GRIB2 File Object.

This class can accommodate a physical file with one or more GRIB2 messages or a bytes object containing a GRIB2 messages.

A physical file can contain one or more GRIB2 messages. When instantiated, class grib2io.open, the file named filename is opened for reading (mode = 'r') and is automatically indexed. The indexing procedure reads some of the GRIB2 metadata for all GRIB2 Messages. A GRIB2 Message may contain submessages whereby Section 2-7 can be repeated. grib2io accommodates for this by flattening any GRIB2 submessages into multiple individual messages.

It is important to note that GRIB2 files from some Meteorological agencies contain other data than GRIB2 messages. GRIB2 files from ECMWF can contain GRIB1 and GRIB2 messages. grib2io checks for these and safely ignores them.

Attributes
  • closed (bool): True is file handle is close; False otherwise.
  • current_message (int): Current position of the file in units of GRIB2 Messages. (read only)
  • levels (tuple): Tuple containing a unique list of wgrib2-formatted level/layer strings.
  • messages (int): Count of GRIB2 Messages contained in the file.
  • mode (str): File IO mode of opening the file.
  • name (str): Full path name of the GRIB2 file.
  • save_index (bool): Whether to save a pickle-based index file for the GRIB2 file. Default is True.
  • size (int): Size of the file in units of bytes.
  • use_index: Whether to use an existing pickle-based index file for the GRIB2 file. Default is True.
  • variables (tuple): Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
open( filename: Union[bytes, str, pathlib.Path], mode: Literal['r', 'w', 'x'] = 'r', *, save_index=True, use_index=True, _xarray_backend=False, **kwargs)
163    def __init__(
164        self,
165        filename: Union[bytes, str, Path],
166        mode: Literal["r", "w", "x"] = "r",
167        *,
168        save_index = True,
169        use_index = True,
170        _xarray_backend = False,
171        **kwargs,
172        ):
173        """
174        Initialize GRIB2 File object instance.
175
176        Parameters
177        ----------
178        filename
179            File path containing GRIB2 messages OR bytes.
180        mode
181            File access mode where "r" opens the files for reading only; "w"
182            opens the file for overwriting and "x" for writing to a new file.
183        save_index
184            Whether to save a pickle-based index file for the GRIB2 file. Default is True.
185
186            .. versionadded:: 2.6.0
187        use_index
188            Whether to use an existing pickle-based index file for the GRIB2 file. Default is True.
189
190            .. versionadded:: 2.6.0
191        """
192
193        # All write modes are read/write.
194        # All modes are binary.
195        if mode in ("a", "x", "w"):
196            mode += "+"
197        mode = mode + "b"
198
199        self.mode = mode
200        self.save_index = save_index
201        self.use_index = use_index
202
203        if isinstance(filename, bytes):
204            if 'r' not in self.mode:
205                raise ValueError("bytes grib2 can only be opened as read")
206            self.current_message = 0
207            if filename[:2] == _GZIP_HEADER:
208                filename = gzip.decompress(filename)
209            if filename[:4]+filename[-4:] != b'GRIB7777':
210                raise ValueError("Invalid GRIB bytes")
211            self._filehandle = BytesIO(filename)
212            self.name = "<in-memory-file>"
213            self.size = len(filename)
214            self._fileid = hashlib.sha1((self.name+str(self.size)).encode('ASCII')).hexdigest()
215            self._index = build_index(self._filehandle)
216            self._msgs = msgs_from_index(self._index, filehandle=self._filehandle)
217            self.messages = len(self._msgs)
218
219        else:
220            self.current_message = 0
221            if 'r' in mode:
222                self._filehandle = builtins.open(filename, mode=mode)
223                # Some GRIB2 files are gzipped, so check for that here, but
224                # raise error when using xarray backend.
225                # Gzip files contain a 2-byte header b'\x1f\x8b'.
226                if self._filehandle.read(2) == _GZIP_HEADER:
227                    self._filehandle.close()
228                    if _xarray_backend:
229                        raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.')
230                    self._filehandle = gzip.open(filename, mode=mode)
231                else:
232                    self._filehandle = builtins.open(filename, mode=mode)
233            else:
234                self._filehandle = builtins.open(filename, mode=mode)
235                self.name = os.path.abspath(filename)
236            self.name = os.path.abspath(filename)
237            fstat = os.stat(self.name)
238            self.size = fstat.st_size
239            self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+
240                                     str(self.size)).encode('ASCII')).hexdigest()
241
242            if 'r' in self.mode:
243
244                indexfile = f"{self.name}_{self._fileid}.grib2ioidx"
245                if self.use_index and os.path.exists(indexfile):
246                    try:
247                        with builtins.open(indexfile, 'rb') as file:
248                            index = pickle.load(file)
249                        self._index = index
250                    except Exception as e:
251                        print(f"found indexfile: {indexfile}, but unable to load it: {e}")
252                        print(f"re-forming index from grib2file, but not writing indexfile")
253                        self._index = build_index(self._filehandle)
254                else:
255                    self._index = build_index(self._filehandle)
256                    if self.save_index:
257                        try:
258                            serialize_index(self._index, indexfile)
259                        except Exception as e:
260                            print(f"index was not serialized for future use: {e}")
261
262                self._msgs = msgs_from_index(self._index, filehandle=self._filehandle)
263
264                self.messages = len(self._msgs)
265            elif 'w' or 'x' in self.mode:
266                self.messages = 0
267                self.current_message = None
268
269        self.closed = self._filehandle.closed

Initialize GRIB2 File object instance.

Parameters
  • filename: File path containing GRIB2 messages OR bytes.
  • mode: File access mode where "r" opens the files for reading only; "w" opens the file for overwriting and "x" for writing to a new file.
  • save_index: Whether to save a pickle-based index file for the GRIB2 file. Default is True.

New in version 2.6.0.

  • use_index: Whether to use an existing pickle-based index file for the GRIB2 file. Default is True.

New in version 2.6.0.

mode
save_index
use_index
closed
levels
316    @property
317    def levels(self):
318        """Provides a unique tuple of level strings."""
319        if self._hasindex:
320            return tuple(sorted(set([msg.level for msg in self._msgs])))
321        else:
322            return None

Provides a unique tuple of level strings.

variables
325    @property
326    def variables(self):
327        """Provides a unique tuple of variable shortName strings."""
328        if self._hasindex:
329            return tuple(sorted(set([msg.shortName for msg in self._msgs])))
330        else:
331            return None

Provides a unique tuple of variable shortName strings.

def close(self):
334    def close(self):
335        """Close the file handle."""
336        if not self._filehandle.closed:
337            self.messages = 0
338            self.current_message = 0
339            self._filehandle.close()
340            self.closed = self._filehandle.closed

Close the file handle.

def read(self, size: Optional[int] = None):
343    def read(self, size: Optional[int]=None):
344        """
345        Read size amount of GRIB2 messages from the current position.
346
347        If no argument is given, then size is None and all messages are returned
348        from the current position in the file. This read method follows the
349        behavior of Python's builtin open() function, but whereas that operates
350        on units of bytes, we operate on units of GRIB2 messages.
351
352        Parameters
353        ----------
354        size: default=None
355            The number of GRIB2 messages to read from the current position. If
356            no argument is give, the default value is None and remainder of
357            the file is read.
358
359        Returns
360        -------
361        read
362            ``Grib2Message`` object when size = 1 or a list of Grib2Messages
363            when size > 1.
364        """
365        if size is not None and size < 0:
366            size = None
367        if size is None or size > 1:
368            start = self.tell()
369            stop = self.messages if size is None else start+size
370            if size is None:
371                self.current_message = self.messages-1
372            else:
373                self.current_message += size
374            return self._msgs[slice(start,stop,1)]
375        elif size == 1:
376            self.current_message += 1
377            return self._msgs[self.current_message]
378        else:
379            None

Read size amount of GRIB2 messages from the current position.

If no argument is given, then size is None and all messages are returned from the current position in the file. This read method follows the behavior of Python's builtin open() function, but whereas that operates on units of bytes, we operate on units of GRIB2 messages.

Parameters
  • size (default=None): The number of GRIB2 messages to read from the current position. If no argument is give, the default value is None and remainder of the file is read.
Returns
  • read: Grib2Message object when size = 1 or a list of Grib2Messages when size > 1.
def seek(self, pos: int):
382    def seek(self, pos: int):
383        """
384        Set the position within the file in units of GRIB2 messages.
385
386        Parameters
387        ----------
388        pos
389            The GRIB2 Message number to set the file pointer to.
390        """
391        if self._hasindex:
392            self._filehandle.seek(self._index['sectionOffset'][0][pos])
393            self.current_message = pos

Set the position within the file in units of GRIB2 messages.

Parameters
  • pos: The GRIB2 Message number to set the file pointer to.
def tell(self):
396    def tell(self):
397        """Returns the position of the file in units of GRIB2 Messages."""
398        return self.current_message

Returns the position of the file in units of GRIB2 Messages.

def select(self, **kwargs):
401    def select(self, **kwargs):
402        """Select GRIB2 messages by `Grib2Message` attributes."""
403        # TODO: Added ability to process multiple values for each keyword (attribute)
404        idxs = []
405        nkeys = len(kwargs.keys())
406        for k,v in kwargs.items():
407            for m in self._msgs:
408                if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum)
409        idxs = np.array(idxs,dtype=np.int32)
410        return [self._msgs[i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]

Select GRIB2 messages by Grib2Message attributes.

def write(self, msg):
413    def write(self, msg):
414        """
415        Writes GRIB2 message object to file.
416
417        Parameters
418        ----------
419        msg
420            GRIB2 message objects to write to file.
421        """
422        if isinstance(msg,list):
423            for m in msg:
424                self.write(m)
425            return
426
427        if issubclass(msg.__class__,_Grib2Message):
428            # TODO: We can consider letting pack return packed bytes instead of associating with message object
429            if hasattr(msg,'_msg'):
430                # write already packed bytes
431                self._filehandle.write(msg._msg)
432            else:
433                if msg._signature == msg._generate_signature() and \
434                   msg._data is None and \
435                   hasattr(msg._ondiskarray,'filehandle'):
436                   # write unchanged message from input
437                   offset = msg._ondiskarray.filehandle.tell()
438                   msg._ondiskarray.filehandle.seek(msg._ondiskarray.offset)
439                   self._filehandle.write(msg._ondiskarray.filehandle.read(msg.section0[-1]))
440                   msg._ondiskarray.filehandle.seek(offset)
441                else:
442                    msg.pack()
443                    self._filehandle.write(msg._msg)
444            self.size = os.path.getsize(self.name)
445            self.messages += 1
446        else:
447            raise TypeError("msg must be a Grib2Message object.")
448        return

Writes GRIB2 message object to file.

Parameters
  • msg: GRIB2 message objects to write to file.
def flush(self):
451    def flush(self):
452        """Flush the file object buffer."""
453        self._filehandle.flush()

Flush the file object buffer.

def levels_by_var(self, name: str):
456    def levels_by_var(self, name: str):
457        """
458        Return a list of level strings given a variable shortName.
459
460        Parameters
461        ----------
462        name
463            Grib2Message variable shortName
464
465        Returns
466        -------
467        levels_by_var
468            A list of unique level strings.
469        """
470        return list(sorted(set([msg.level for msg in self.select(shortName=name)])))

Return a list of level strings given a variable shortName.

Parameters
  • name: Grib2Message variable shortName
Returns
  • levels_by_var: A list of unique level strings.
def vars_by_level(self, level: str):
473    def vars_by_level(self, level: str):
474        """
475        Return a list of variable shortName strings given a level.
476
477        Parameters
478        ----------
479        level
480            Grib2Message variable level
481
482        Returns
483        -------
484        vars_by_level
485            A list of unique variable shortName strings.
486        """
487        return list(sorted(set([msg.shortName for msg in self.select(level=level)])))

Return a list of variable shortName strings given a level.

Parameters
  • level: Grib2Message variable level
Returns
  • vars_by_level: A list of unique variable shortName strings.
current_message
messages
name
size
def show_config():
34def show_config():
35    """Print grib2io build configuration information."""
36    print(f'grib2io version {__version__} Configuration:')
37    print(f'')
38    print(f'NCEPLIBS-g2c library version: {__g2clib_version__}')
39    print(f'\tStatic library: {g2c_static}')
40    print(f'\tJPEG compression support: {has_jpeg_support}')
41    print(f'\tPNG compression support: {has_png_support}')
42    print(f'\tAEC compression support: {has_aec_support}')
43    print(f'')
44    print(f'NCEPLIPS-ip support: {has_interpolation}')
45    print(f'\tStatic library: {ip_static}')
46    print(f'\tOpenMP support: {has_openmp_support}')
47    print(f'')
48    print(f'Static libs:')
49    for lib in extra_objects:
50        print(f'\t{lib}')
51    print(f'')
52    print(f'NCEP GRIB2 Table Version: {_ncep_grib2_table_version}')

Print grib2io build configuration information.

def interpolate( a, method: Union[int, str], grid_def_in, grid_def_out, method_options=None, num_threads=1):
2030def interpolate(a,
2031    method: Union[int, str],
2032    grid_def_in,
2033    grid_def_out,
2034    method_options=None,
2035    num_threads=1,
2036):
2037    """
2038    This is the module-level interpolation function.
2039
2040    This interfaces with the 4-byte (32-bit float) [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip)
2041    through grib2io's internal iplib Cython extension module.
2042
2043    Parameters
2044    ----------
2045    a : numpy.ndarray or tuple
2046        Input data.  If `a` is a `numpy.ndarray`, scalar interpolation will be
2047        performed.  If `a` is a `tuple`, then vector interpolation will be
2048        performed with the assumption that u = a[0] and v = a[1] and are both
2049        `numpy.ndarray`.
2050
2051        These data are expected to be in 2-dimensional form with shape (ny, nx)
2052        or 3-dimensional (:, ny, nx) where the 1st dimension represents another
2053        spatial, temporal, or classification (i.e. ensemble members) dimension.
2054        The function will properly flatten the (ny,nx) dimensions into (nx * ny)
2055        acceptable for input into the interpolation subroutines. If needed, these
2056        data will be converted to `np.float32`.
2057    method
2058        Interpolate method to use. This can either be an integer or string using
2059        the following mapping:
2060
2061        | Interpolate Scheme | Integer Value |
2062        | :---:              | :---:         |
2063        | 'bilinear'         | 0             |
2064        | 'bicubic'          | 1             |
2065        | 'neighbor'         | 2             |
2066        | 'budget'           | 3             |
2067        | 'spectral'         | 4             |
2068        | 'neighbor-budget'  | 6             |
2069
2070    grid_def_in : grib2io.Grib2GridDef
2071        Grib2GridDef object for the input grid.
2072    grid_def_out : grib2io.Grib2GridDef
2073        Grib2GridDef object for the output grid or station points.
2074    method_options : list of ints, optional
2075        Interpolation options. See the NCEPLIBS-ip documentation for
2076        more information on how these are used.
2077    num_threads : int, optional
2078        Number of OpenMP threads to use for interpolation. The default
2079        value is 1. If NCEPLIBS-ip and grib2io's iplib extension module
2080        was not built with OpenMP, then this keyword argument and value
2081        will have no impact.
2082
2083    Returns
2084    -------
2085    interpolate
2086        Returns a `numpy.ndarray` of dtype `np.float32` when scalar interpolation
2087        is performed or a `tuple` of `numpy.ndarray`s when vector interpolation is
2088        performed with the assumptions that 0-index is the interpolated u and
2089        1-index is the interpolated v.
2090    """
2091
2092    from . import iplib
2093
2094    prev_num_threads = 1
2095    try:
2096        prev_num_threads = iplib.openmp_get_num_threads()
2097        iplib.openmp_set_num_threads(num_threads)
2098    except(AttributeError):
2099        pass
2100
2101    print(f"grib2io.interpolate thread report: OpenMP num threads = {iplib.openmp_get_num_threads()}")
2102
2103    if isinstance(method,int) and method not in _interp_schemes.values():
2104        raise ValueError('Invalid interpolation method.')
2105    elif isinstance(method,str):
2106        if method in _interp_schemes.keys():
2107            method = _interp_schemes[method]
2108        else:
2109            raise ValueError('Invalid interpolation method.')
2110
2111    if method_options is None:
2112        method_options = np.zeros((20),dtype=np.int32)
2113        if method in {3,6}:
2114            method_options[0:2] = -1
2115
2116    mi = grid_def_in.npoints
2117    mo = grid_def_out.npoints
2118
2119    # Adjust shape of input array(s)
2120    a, newshp = _adjust_array_shape_for_interp(a, grid_def_in, grid_def_out)
2121
2122    # Call interpolation subroutines according to type of a.
2123    if isinstance(a,np.ndarray):
2124        # Scalar
2125        km = a.shape[0]
2126        if np.any(np.isnan(a)):
2127            ibi = np.ones((km), dtype=np.int32)
2128            li = np.where(np.isnan(a),0,1).astype(np.uint8)
2129        else:
2130            ibi = np.zeros((km), dtype=np.int32)
2131            li = np.zeros(a.shape,dtype=np.uint8)
2132        no, rlat, rlon, ibo, lo, go, iret = iplib.interpolate_scalar(
2133            method,
2134            method_options,
2135            grid_def_in.gdtn,
2136            np.array(grid_def_in.gdt, dtype=np.int32),
2137            grid_def_out.gdtn,
2138            np.array(grid_def_out.gdt, dtype=np.int32),
2139            mi,
2140            mo,
2141            km,
2142            ibi,
2143            li,
2144            a,
2145        )
2146        out = np.where(lo==0,np.nan,go).reshape(newshp)
2147    elif isinstance(a,tuple):
2148        # Vector
2149        km = a[0].shape[0]
2150        if np.any(np.isnan(a)):
2151            ibi = np.ones((km), dtype=np.int32)
2152            li = np.where(np.isnan(a),0,1).astype(np.uint8)
2153        else:
2154            ibi = np.zeros((km), dtype=np.int32)
2155            li = np.zeros(a[0].shape,dtype=np.uint8)
2156        no, rlat, rlon, crot, srot, ibo, lo, uo, vo, iret = iplib.interpolate_vector(
2157            method,
2158            method_options,
2159            grid_def_in.gdtn,
2160            np.array(grid_def_in.gdt, dtype=np.int32),
2161            grid_def_out.gdtn,
2162            np.array(grid_def_out.gdt, dtype=np.int32),
2163            mi,
2164            mo,
2165            km,
2166            ibi,
2167            li,
2168            a[0],
2169            a[1],
2170        )
2171        uo = np.where(lo==0,np.nan,uo).reshape(newshp)
2172        vo = np.where(lo==0,np.nan,vo).reshape(newshp)
2173        out = (uo,vo)
2174
2175    try:
2176        iplib.openmp_set_num_threads(prev_num_threads)
2177    except(AttributeError):
2178        pass
2179
2180    return out

This is the module-level interpolation function.

This interfaces with the 4-byte (32-bit float) NCEPLIBS-ip library through grib2io's internal iplib Cython extension module.

Parameters
  • a (numpy.ndarray or tuple): Input data. If 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):
2183def interpolate_to_stations(
2184    a,
2185    method: Union[int, str],
2186    grid_def_in,
2187    lats,
2188    lons,
2189    method_options=None,
2190    num_threads=1
2191):
2192    """
2193    Module-level interpolation function for interpolation to stations.
2194
2195    Interfaces with the 4-byte (32-bit float) [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip)
2196    via grib2io's iplib Cython exntension module. It supports scalar and
2197    vector interpolation according to the type of object a.
2198
2199    Parameters
2200    ----------
2201    a : numpy.ndarray or tuple
2202        Input data.  If `a` is a `numpy.ndarray`, scalar interpolation will be
2203        performed.  If `a` is a `tuple`, then vector interpolation will be
2204        performed with the assumption that u = a[0] and v = a[1] and are both
2205        `numpy.ndarray`.
2206
2207        These data are expected to be in 2-dimensional form with shape (ny, nx)
2208        or 3-dimensional (:, ny, nx) where the 1st dimension represents another
2209        spatial, temporal, or classification (i.e. ensemble members) dimension.
2210        The function will properly flatten the (ny,nx) dimensions into (nx * ny)
2211        acceptable for input into the interpolation subroutines. If needed, these
2212        data will be converted to `np.float32`.
2213    method
2214        Interpolate method to use. This can either be an integer or string using
2215        the following mapping:
2216
2217        | Interpolate Scheme | Integer Value |
2218        | :---:              | :---:         |
2219        | 'bilinear'         | 0             |
2220        | 'bicubic'          | 1             |
2221        | 'neighbor'         | 2             |
2222        | 'budget'           | 3             |
2223        | 'spectral'         | 4             |
2224        | 'neighbor-budget'  | 6             |
2225
2226    grid_def_in : grib2io.Grib2GridDef
2227        Grib2GridDef object for the input grid.
2228    lats : numpy.ndarray or list
2229        Latitudes for station points
2230    lons : numpy.ndarray or list
2231        Longitudes for station points
2232    method_options : list of ints, optional
2233        Interpolation options. See the NCEPLIBS-ip documentation for
2234        more information on how these are used.
2235    num_threads : int, optional
2236        Number of OpenMP threads to use for interpolation. The default
2237        value is 1. If NCEPLIBS-ip and grib2io's iplib extension module
2238        was not built with OpenMP, then this keyword argument and value
2239        will have no impact.
2240
2241    Returns
2242    -------
2243    interpolate_to_stations
2244        Returns a `numpy.ndarray` of dtype `np.float32` when scalar
2245        interpolation is performed or a `tuple` of `numpy.ndarray`s
2246        when vector interpolation is performed with the assumptions
2247        that 0-index is the interpolated u and 1-index is the
2248        interpolated v.
2249    """
2250    from . import iplib
2251
2252    prev_num_threads = 1
2253    try:
2254        prev_num_threads = iplib.openmp_get_num_threads()
2255        iplib.openmp_set_num_threads(num_threads)
2256    except(AttributeError):
2257        pass
2258
2259    if isinstance(method,int) and method not in _interp_schemes.values():
2260        raise ValueError('Invalid interpolation method.')
2261    elif isinstance(method,str):
2262        if method in _interp_schemes.keys():
2263            method = _interp_schemes[method]
2264        else:
2265            raise ValueError('Invalid interpolation method.')
2266
2267    if method_options is None:
2268        method_options = np.zeros((20),dtype=np.int32)
2269        if method in {3,6}:
2270            method_options[0:2] = -1
2271
2272    # Check lats and lons
2273    if isinstance(lats,list):
2274        nlats = len(lats)
2275    elif isinstance(lats,np.ndarray) and len(lats.shape) == 1:
2276        nlats = lats.shape[0]
2277    else:
2278        raise ValueError('Station latitudes must be a list or 1-D NumPy array.')
2279    if isinstance(lons,list):
2280        nlons = len(lons)
2281    elif isinstance(lons,np.ndarray) and len(lons.shape) == 1:
2282        nlons = lons.shape[0]
2283    else:
2284        raise ValueError('Station longitudes must be a list or 1-D NumPy array.')
2285    if nlats != nlons:
2286        raise ValueError('Station lats and lons must be same size.')
2287
2288    mi = grid_def_in.npoints
2289    mo = nlats
2290
2291    # Adjust shape of input array(s)
2292    a, newshp = _adjust_array_shape_for_interp_stations(a, grid_def_in, mo)
2293
2294    # Use gdtn = -1 for stations and an empty template array
2295    gdtn = -1
2296    gdt = np.zeros((200),dtype=np.int32)
2297
2298    # Call interpolation subroutines according to type of a.
2299    if isinstance(a,np.ndarray):
2300        # Scalar
2301        km = a.shape[0]
2302        ibi = np.zeros((km), dtype=np.int32)
2303        li = np.zeros(a.shape,dtype=np.uint8)
2304        no, rlat, rlon, ibo, lo, go, iret = iplib.interpolate_scalar(
2305            method,
2306            method_options,
2307            grid_def_in.gdtn,
2308            np.array(grid_def_in.gdt, dtype=np.int32),
2309            gdtn,
2310            gdt,
2311            mi,
2312            mo,
2313            km,
2314            ibi,
2315            li,
2316            a,
2317            lats=np.array(lats, dtype=np.float32),
2318            lons=np.array(lons, dtype=np.float32),
2319        )
2320        out = go.reshape(newshp)
2321
2322    elif isinstance(a,tuple):
2323        # Vector
2324        km = a[0].shape[0]
2325        ibi = np.zeros((km), dtype=np.int32)
2326        li = np.zeros(a[0].shape,dtype=np.uint8)
2327        no, rlat, rlon, crot, srot, ibo, lo, uo, vo, iret = iplib.interpolate_vector(
2328            method,
2329            method_options,
2330            grid_def_in.gdtn,
2331            np.array(grid_def_in.gdt, dtype=np.int32),
2332            gdtn,
2333            gdt,
2334            mi,
2335            mo,
2336            km,
2337            ibi,
2338            li,
2339            a[0],
2340            a[1],
2341            lats=np.array(lats, dtype=np.float32),
2342            lons=np.array(lons, dtype=np.float32),
2343        )
2344        out = (uo.reshape(newshp),vo.reshape(newshp))
2345
2346    try:
2347        iplib.openmp_set_num_threads(prev_num_threads)
2348    except(AttributeError):
2349        pass
2350
2351    return out

Module-level interpolation function for interpolation to stations.

Interfaces with the 4-byte (32-bit float) NCEPLIBS-ip library via grib2io's iplib Cython exntension module. It supports scalar and vector interpolation according to the type of object a.

Parameters
  • a (numpy.ndarray or tuple): Input data. If 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:
834class Grib2Message:
835    """
836    Creation class for a GRIB2 message.
837
838    This class returns a dynamically-created Grib2Message object that
839    inherits from `_Grib2Message` and grid, product, data representation
840    template classes according to the template numbers for the respective
841    sections. If `section3`, `section4`, or `section5` are omitted, then
842    the appropriate keyword arguments for the template number `gdtn=`,
843    `pdtn=`, or `drtn=` must be provided.
844
845    Parameters
846    ----------
847    section0
848        GRIB2 section 0 array.
849    section1
850        GRIB2 section 1 array.
851    section2
852        Local Use section data.
853    section3
854        GRIB2 section 3 array.
855    section4
856        GRIB2 section 4 array.
857    section5
858        GRIB2 section 5 array.
859
860    Returns
861    -------
862    Msg
863        A dynamically-create Grib2Message object that inherits from
864        _Grib2Message, a grid definition template class, product
865        definition template class, and a data representation template
866        class.
867    """
868    def __new__(self, section0: NDArray = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]),
869                      section1: NDArray = np.zeros((13),dtype=np.int64),
870                      section2: Optional[bytes] = None,
871                      section3: Optional[NDArray] = None,
872                      section4: Optional[NDArray] = None,
873                      section5: Optional[NDArray] = None, *args, **kwargs):
874
875        if np.all(section1==0):
876            try:
877                # Python >= 3.10
878                section1[5:11] = datetime.datetime.fromtimestamp(0, datetime.UTC).timetuple()[:6]
879            except(AttributeError):
880                # Python < 3.10
881                section1[5:11] = datetime.datetime.utcfromtimestamp(0).timetuple()[:6]
882
883        bases = list()
884        if section3 is None:
885            if 'gdtn' in kwargs.keys():
886                gdtn = kwargs['gdtn']
887                Gdt = templates.gdt_class_by_gdtn(gdtn)
888                bases.append(Gdt)
889                section3 = np.zeros((Gdt._len+5),dtype=np.int64)
890                section3[4] = gdtn
891            else:
892                raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array")
893        else:
894            gdtn = section3[4]
895            Gdt = templates.gdt_class_by_gdtn(gdtn)
896            bases.append(Gdt)
897
898        if section4 is None:
899            if 'pdtn' in kwargs.keys():
900                pdtn = kwargs['pdtn']
901                Pdt = templates.pdt_class_by_pdtn(pdtn)
902                bases.append(Pdt)
903                section4 = np.zeros((Pdt._len+2),dtype=np.int64)
904                section4[1] = pdtn
905            else:
906                raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array")
907        else:
908            pdtn = section4[1]
909            Pdt = templates.pdt_class_by_pdtn(pdtn)
910            bases.append(Pdt)
911
912        if section5 is None:
913            if 'drtn' in kwargs.keys():
914                drtn = kwargs['drtn']
915                Drt = templates.drt_class_by_drtn(drtn)
916                bases.append(Drt)
917                section5 = np.zeros((Drt._len+2),dtype=np.int64)
918                section5[1] = drtn
919            else:
920                raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array")
921        else:
922            drtn = section5[1]
923            Drt = templates.drt_class_by_drtn(drtn)
924            bases.append(Drt)
925
926        # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo
927        try:
928            Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"]
929        except KeyError:
930            @dataclass(init=False, repr=False)
931            class Msg(_Grib2Message, *bases):
932                pass
933            _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg
934
935        return Msg(section0, section1, section2, section3, section4, section5, *args)

Creation class for a GRIB2 message.

This class returns a dynamically-created Grib2Message object that inherits from _Grib2Message and grid, product, data representation template classes according to the template numbers for the respective sections. If section3, section4, or section5 are omitted, then the appropriate keyword arguments for the template number gdtn=, pdtn=, or drtn= must be provided.

Parameters
  • section0: GRIB2 section 0 array.
  • section1: GRIB2 section 1 array.
  • section2: Local Use section data.
  • section3: GRIB2 section 3 array.
  • section4: GRIB2 section 4 array.
  • section5: GRIB2 section 5 array.
Returns
  • Msg: A dynamically-create Grib2Message object that inherits from _Grib2Message, a grid definition template class, product definition template class, and a data representation template class.
@dataclass
class _Grib2Message:
 938@dataclass
 939class _Grib2Message:
 940    """
 941    GRIB2 Message base class.
 942    """
 943    # GRIB2 Sections
 944    section0: NDArray = field(init=True,repr=False)
 945    section1: NDArray = field(init=True,repr=False)
 946    section2: bytes = field(init=True,repr=False)
 947    section3: NDArray = field(init=True,repr=False)
 948    section4: NDArray = field(init=True,repr=False)
 949    section5: NDArray = field(init=True,repr=False)
 950    bitMapFlag: templates.Grib2Metadata = field(init=True,repr=False,default=255)
 951
 952    # Section 0 looked up attributes
 953    indicatorSection: NDArray = field(init=False,repr=False,default=templates.IndicatorSection())
 954    discipline: templates.Grib2Metadata = field(init=False,repr=False,default=templates.Discipline())
 955
 956    # Section 1 looked up attributes
 957    identificationSection: NDArray = field(init=False,repr=False,default=templates.IdentificationSection())
 958    originatingCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingCenter())
 959    originatingSubCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingSubCenter())
 960    masterTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.MasterTableInfo())
 961    localTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.LocalTableInfo())
 962    significanceOfReferenceTime: templates.Grib2Metadata = field(init=False,repr=False,default=templates.SignificanceOfReferenceTime())
 963    year: int = field(init=False,repr=False,default=templates.Year())
 964    month: int = field(init=False,repr=False,default=templates.Month())
 965    day: int = field(init=False,repr=False,default=templates.Day())
 966    hour: int = field(init=False,repr=False,default=templates.Hour())
 967    minute: int = field(init=False,repr=False,default=templates.Minute())
 968    second: int = field(init=False,repr=False,default=templates.Second())
 969    refDate: datetime.datetime = field(init=False,repr=False,default=templates.RefDate())
 970    productionStatus: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductionStatus())
 971    typeOfData: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfData())
 972
 973    # Section 3 looked up common attributes.  Other looked up attributes are available according
 974    # to the Grid Definition Template.
 975    gridDefinitionSection: NDArray = field(init=False,repr=False,default=templates.GridDefinitionSection())
 976    sourceOfGridDefinition: int = field(init=False,repr=False,default=templates.SourceOfGridDefinition())
 977    numberOfDataPoints: int = field(init=False,repr=False,default=templates.NumberOfDataPoints())
 978    interpretationOfListOfNumbers: templates.Grib2Metadata = field(init=False,repr=False,default=templates.InterpretationOfListOfNumbers())
 979    gridDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.GridDefinitionTemplateNumber())
 980    gridDefinitionTemplate: list = field(init=False,repr=False,default=templates.GridDefinitionTemplate())
 981    _earthparams: dict = field(init=False,repr=False,default=templates.EarthParams())
 982    _dxsign: float = field(init=False,repr=False,default=templates.DxSign())
 983    _dysign: float = field(init=False,repr=False,default=templates.DySign())
 984    _llscalefactor: float = field(init=False,repr=False,default=templates.LLScaleFactor())
 985    _lldivisor: float = field(init=False,repr=False,default=templates.LLDivisor())
 986    _xydivisor: float = field(init=False,repr=False,default=templates.XYDivisor())
 987    shapeOfEarth: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ShapeOfEarth())
 988    earthShape: str = field(init=False,repr=False,default=templates.EarthShape())
 989    earthRadius: float = field(init=False,repr=False,default=templates.EarthRadius())
 990    earthMajorAxis: float = field(init=False,repr=False,default=templates.EarthMajorAxis())
 991    earthMinorAxis: float = field(init=False,repr=False,default=templates.EarthMinorAxis())
 992    resolutionAndComponentFlags: list = field(init=False,repr=False,default=templates.ResolutionAndComponentFlags())
 993    ny: int = field(init=False,repr=False,default=templates.Ny())
 994    nx: int = field(init=False,repr=False,default=templates.Nx())
 995    scanModeFlags: list = field(init=False,repr=False,default=templates.ScanModeFlags())
 996    projParameters: dict = field(init=False,repr=False,default=templates.ProjParameters())
 997
 998    # Section 4
 999    productDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductDefinitionTemplateNumber())
1000    productDefinitionTemplate: NDArray = field(init=False,repr=False,default=templates.ProductDefinitionTemplate())
1001
1002    # Section 5 looked up common attributes.  Other looked up attributes are
1003    # available according to the Data Representation Template.
1004    numberOfPackedValues: int = field(init=False,repr=False,default=templates.NumberOfPackedValues())
1005    dataRepresentationTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.DataRepresentationTemplateNumber())
1006    dataRepresentationTemplate: list = field(init=False,repr=False,default=templates.DataRepresentationTemplate())
1007    typeOfValues: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfValues())
1008
1009    def __copy__(self):
1010        """Shallow copy"""
1011        new = Grib2Message(
1012            self.section0,
1013            self.section1,
1014            self.section2,
1015            self.section3,
1016            self.section4,
1017            drtn=self.drtn,
1018        )
1019        return new
1020
1021    def __deepcopy__(self, memo):
1022        """Deep copy"""
1023        new = Grib2Message(
1024            np.copy(self.section0),
1025            np.copy(self.section1),
1026            copy.deepcopy(self.section2),
1027            np.copy(self.section3),
1028            np.copy(self.section4),
1029            np.copy(self.section5),
1030        )
1031        memo[id(self)] = new
1032        new.data = np.copy(self.data)
1033        new.bitmap = None if self.bitmap is None else np.copy(self.bitmap)
1034        return new
1035
1036    def __post_init__(self):
1037        """Set some attributes after init."""
1038        self._auto_nans = _AUTO_NANS
1039        self._coordlist = np.zeros((0), dtype=np.float32)
1040        self._data = None
1041        self._deflist = np.zeros((0), dtype=np.int64)
1042        self._msgnum = -1
1043        self._ondiskarray = None
1044        self._orig_section5 = np.copy(self.section5)
1045        self._signature = self._generate_signature()
1046        try:
1047            self._sha1_section3 = hashlib.sha1(self.section3).hexdigest()
1048        except(TypeError):
1049            pass
1050        self.bitMapFlag = templates.Grib2Metadata(self.bitMapFlag,table='6.0')
1051        self.bitmap = None
1052
1053    @property
1054    def _isNDFD(self):
1055        """Check if GRIB2 message is from NWS NDFD"""
1056        return np.all(self.section1[0:2]==[8,65535])
1057
1058    @property
1059    def _isAerosol(self):
1060        """Check if GRIB2 message contains aerosol data"""
1061        is_aero_template = self.productDefinitionTemplateNumber.value in tables.AEROSOL_PDTNS
1062        is_aero_param = ((str(self.parameterCategory) == '13') |
1063                     (str(self.parameterCategory) == '20')) and str(self.parameterNumber) in tables.AEROSOL_PARAMS
1064        # Check table 4.205 aerosol presence
1065        is_aero_type = (str(self.parameterCategory) == '205' and
1066                        str(self.parameterNumber) == '1')
1067        return is_aero_template or is_aero_param or is_aero_type
1068
1069    @property
1070    def gdtn(self):
1071        """Return Grid Definition Template Number"""
1072        return self.section3[4]
1073
1074
1075    @property
1076    def gdt(self):
1077        """Return Grid Definition Template."""
1078        return self.gridDefinitionTemplate
1079
1080
1081    @property
1082    def pdtn(self):
1083        """Return Product Definition Template Number."""
1084        return self.section4[1]
1085
1086
1087    @property
1088    def pdt(self):
1089        """Return Product Definition Template."""
1090        return self.productDefinitionTemplate
1091
1092
1093    @property
1094    def drtn(self):
1095        """Return Data Representation Template Number."""
1096        return self.section5[1]
1097
1098
1099    @property
1100    def drt(self):
1101        """Return Data Representation Template."""
1102        return self.dataRepresentationTemplate
1103
1104
1105    @property
1106    def pdy(self):
1107        """Return the PDY ('YYYYMMDD')."""
1108        return ''.join([str(i) for i in self.section1[5:8]])
1109
1110
1111    @property
1112    def griddef(self):
1113        """Return a Grib2GridDef instance for a GRIB2 message."""
1114        return Grib2GridDef.from_section3(self.section3)
1115
1116
1117    @property
1118    def lats(self):
1119        """Return grid latitudes."""
1120        return self.latlons()[0]
1121
1122
1123    @property
1124    def lons(self):
1125        """Return grid longitudes."""
1126        return self.latlons()[1]
1127
1128    @property
1129    def min(self):
1130        """Return minimum value of data."""
1131        return np.nanmin(self.data)
1132
1133
1134    @property
1135    def max(self):
1136        """Return maximum value of data."""
1137        return np.nanmax(self.data)
1138
1139
1140    @property
1141    def mean(self):
1142        """Return mean value of data."""
1143        return np.nanmean(self.data)
1144
1145
1146    @property
1147    def median(self):
1148        """Return median value of data."""
1149        return np.nanmedian(self.data)
1150
1151
1152    @property
1153    def shape(self):
1154        """Return shape of data."""
1155        return self.griddef.shape
1156
1157
1158    def __repr__(self):
1159        """
1160        Return an unambiguous string representation of the object.
1161
1162        Returns
1163        -------
1164        repr
1165            A string representation of the object, including information from
1166            sections 0, 1, 3, 4, 5, and 6.
1167        """
1168        info = ''
1169        for sect in [0,1,3,4,5,6]:
1170            for k,v in self.attrs_by_section(sect,values=True).items():
1171                info += f'Section {sect}: {k} = {v}\n'
1172        return info
1173
1174    def __str__(self):
1175        """
1176        Return a readable string representation of the object.
1177
1178        Returns
1179        -------
1180        str
1181            A formatted string representation of the object, including
1182            selected attributes.
1183        """
1184        return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:'
1185                f'{self.fullName} ({self.units}):{self.level}:'
1186                f'{self.leadTime}')
1187
1188
1189    def _generate_signature(self):
1190        """Generature SHA-1 hash string from GRIB2 integer sections."""
1191        return hashlib.sha1(np.concatenate((self.section0,self.section1,
1192                                            self.section3,self.section4,
1193                                            self.section5))).hexdigest()
1194
1195
1196    def attrs_by_section(self, sect: int, values: bool=False):
1197        """
1198        Provide a tuple of attribute names for the given GRIB2 section.
1199
1200        Parameters
1201        ----------
1202        sect
1203            The GRIB2 section number.
1204        values
1205            Optional (default is `False`) argument to return attributes values.
1206
1207        Returns
1208        -------
1209        attrs_by_section
1210            A list of attribute names or dict of name:value pairs if `values =
1211            True`.
1212        """
1213        if sect in {0,1,6}:
1214            attrs = templates._section_attrs[sect]
1215        elif sect in {3,4,5}:
1216            def _find_class_index(n):
1217                _key = {3:'Grid', 4:'Product', 5:'Data'}
1218                for i,c in enumerate(self.__class__.__mro__):
1219                    if _key[n] in c.__name__:
1220                        return i
1221                else:
1222                    return []
1223            if sys.version_info.minor <= 8:
1224                attrs = templates._section_attrs[sect]+\
1225                        [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')]
1226            else:
1227                attrs = templates._section_attrs[sect]+\
1228                        self.__class__.__mro__[_find_class_index(sect)]._attrs()
1229        else:
1230            attrs = []
1231        if values:
1232            return {k:getattr(self,k) for k in attrs}
1233        else:
1234            return attrs
1235
1236
1237    def copy(self, deep: bool = True):
1238        """Returns a copy of this Grib2Message.
1239
1240        When `deep=True`, a copy is made of each of the GRIB2 section arrays and
1241        the data are unpacked from the source object and copied into the new
1242        object. Otherwise, a shallow copy of each array is performed and no data
1243        are copied.
1244
1245        Parameters
1246        ----------
1247        deep : bool, default: True
1248            Whether each GRIB2 section array and data are copied onto
1249            the new object. Default is True.
1250
1251        Returns
1252        -------
1253        object : Grib2Message
1254            New Grib2Message object.
1255
1256            .. versionadded:: 2.6.0
1257        """
1258        return copy.deepcopy(self) if deep else copy.copy(self)
1259
1260
1261    def pack(self):
1262        """
1263        Pack GRIB2 section data into a binary message.
1264
1265        It is the user's responsibility to populate the GRIB2 section
1266        information with appropriate metadata.
1267        """
1268        # Create beginning of packed binary message with section 0 and 1 data.
1269        self._sections = []
1270        self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection)
1271        self._sections += [0,1]
1272
1273        # Add section 2 if present.
1274        if isinstance(self.section2,bytes) and len(self.section2) > 0:
1275            self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2)
1276            self._sections.append(2)
1277
1278        # Add section 3.
1279        self.section3[1] = self.nx * self.ny
1280        self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection,
1281                                                   self.gridDefinitionTemplate,
1282                                                   self._deflist)
1283        self._sections.append(3)
1284
1285        # Prepare data.
1286        if self._data is None:
1287            if self._ondiskarray is None:
1288                raise ValueError("Grib2Message object has no data, thus it cannot be packed.")
1289        field = np.copy(self.data)
1290        if self.scanModeFlags is not None:
1291            if self.scanModeFlags[3]:
1292                fieldsave = field.astype('f') # Casting makes a copy
1293                field[1::2,:] = fieldsave[1::2,::-1]
1294        fld = field.astype('f')
1295
1296        # Prepare bitmap, if necessary
1297        bitmapflag = self.bitMapFlag.value
1298        if bitmapflag == 0:
1299            if self.bitmap is not None:
1300                bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT)
1301            else:
1302                bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT)
1303        else:
1304            bmap = None
1305
1306        # Prepare data for packing if nans are present
1307        fld = np.ravel(fld)
1308        if bitmapflag in {0,254}:
1309            fld = np.where(np.isnan(fld),0,fld)
1310        else:
1311            if np.isnan(fld).any():
1312                if hasattr(self,'priMissingValue'):
1313                    fld = np.where(np.isnan(fld),self.priMissingValue,fld)
1314            if hasattr(self,'_missvalmap'):
1315                if hasattr(self,'priMissingValue'):
1316                    fld = np.where(self._missvalmap==1,self.priMissingValue,fld)
1317                if hasattr(self,'secMissingValue'):
1318                    fld = np.where(self._missvalmap==2,self.secMissingValue,fld)
1319
1320        # Add sections 4, 5, 6, and 7.
1321        self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn,
1322                                                    self.productDefinitionTemplate,
1323                                                    self._coordlist,
1324                                                    self.drtn,
1325                                                    self.dataRepresentationTemplate,
1326                                                    fld,
1327                                                    bitmapflag,
1328                                                    bmap)
1329        self._sections.append(4)
1330        self._sections.append(5)
1331        self._sections.append(6)
1332        self._sections.append(7)
1333
1334        # Finalize GRIB2 message with section 8.
1335        self._msg, self._pos = g2clib.grib2_end(self._msg)
1336        self._sections.append(8)
1337        self.section0[-1] = len(self._msg)
1338
1339
1340    @property
1341    def data(self) -> np.array:
1342        """Access the unpacked data values."""
1343        if self._data is None:
1344            if self._auto_nans != _AUTO_NANS:
1345                self._data = self._ondiskarray
1346            self._data = np.asarray(self._ondiskarray)
1347        return self._data
1348
1349
1350    @data.setter
1351    def data(self, arr):
1352        """
1353        Set the internal data array, enforcing shape (ny, nx) and dtype float32.
1354
1355        If the Grid Definition Section (section 3) of Grib2Message object is
1356        not fully formed (i.e. nx, ny = 0, 0), then the shape of the data array
1357        will be used to set nx and ny of the Grib2Message object. It will be the
1358        responsibility of the user to populate the rest of the Grid Definition
1359        Section attributes.
1360
1361        Parameters
1362        ----------
1363        arr : array_like
1364            A 2D array whose shape must match ``(self.ny, self.nx)``.
1365            It will be converted to ``float32`` and C-contiguous if needed.
1366
1367        Raises
1368        ------
1369        ValueError
1370            If the shape of `arr` does not match the expected dimensions.
1371        """
1372        if not isinstance(arr, np.ndarray):
1373            raise ValueError(f"Grib2Message data only supports numpy arrays")
1374        if self.nx == 0 and self.ny == 0:
1375            self.ny = arr.shape[0]
1376            self.nx = arr.shape[1]
1377        if arr.shape != (self.ny, self.nx):
1378            raise ValueError(
1379                f"Data shape mismatch: expected ({self.ny}, {self.nx}), "
1380                f"got {arr.shape}"
1381            )
1382        # Ensure contiguous memory layout (important for C interoperability)
1383        if not arr.flags["C_CONTIGUOUS"]:
1384            arr = np.ascontiguousarray(arr, dtype=np.float32)
1385        self._data = arr
1386
1387
1388    def flush_data(self):
1389        """
1390        Flush the unpacked data values from the Grib2Message object.
1391
1392        Notes
1393        -----
1394        If the Grib2Message object was constructed from "scratch" (i.e.
1395        not read from file), this method will remove the data array from
1396        the object and it cannot be recovered.
1397        """
1398        self._data = None
1399        self.bitmap = None
1400
1401
1402    def __getitem__(self, item):
1403        return self.data[item]
1404
1405
1406    def __setitem__(self, item):
1407        raise NotImplementedError('assignment of data not supported via setitem')
1408
1409
1410    def latlons(self, *args, **kwrgs):
1411        """Alias for `grib2io.Grib2Message.grid` method."""
1412        return self.grid(*args, **kwrgs)
1413
1414
1415    def grid(self, unrotate: bool=True):
1416        """
1417        Return lats,lons (in degrees) of grid.
1418
1419        Currently can handle reg. lat/lon,cglobal Gaussian, mercator,
1420        stereographic, lambert conformal, albers equal-area, space-view and
1421        azimuthal equidistant grids.
1422
1423        Parameters
1424        ----------
1425        unrotate
1426            If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the
1427            grid, otherwise `False`, do not.
1428
1429        Returns
1430        -------
1431        lats, lons : numpy.ndarray
1432            Returns two numpy.ndarrays with dtype=numpy.float32 of grid
1433            latitudes and longitudes in units of degrees.
1434        """
1435        if self._sha1_section3 in _latlon_datastore.keys():
1436            return (_latlon_datastore[self._sha1_section3]['latitude'],
1437                    _latlon_datastore[self._sha1_section3]['longitude'])
1438        gdtn = self.gridDefinitionTemplateNumber.value
1439        gdtmpl = self.gridDefinitionTemplate
1440        reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid
1441        if gdtn == 0:
1442            # Regular lat/lon grid
1443            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1444            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1445            dlon = self.gridlengthXDirection
1446            dlat = self.gridlengthYDirection
1447            if lon2 < lon1 and dlon < 0: lon1 = -lon1
1448            lats = np.linspace(lat1,lat2,self.ny)
1449            if reggrid:
1450                lons = np.linspace(lon1,lon2,self.nx)
1451            else:
1452                lons = np.linspace(lon1,lon2,self.ny*2)
1453            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1454        elif gdtn == 1: # Rotated Lat/Lon grid
1455            pj = pyproj.Proj(self.projParameters)
1456            lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint
1457            lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint
1458            if lon1 > 180.0: lon1 -= 360.0
1459            if lon2 > 180.0: lon2 -= 360.0
1460            lats = np.linspace(lat1,lat2,self.ny)
1461            lons = np.linspace(lon1,lon2,self.nx)
1462            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1463            if unrotate:
1464                from grib2io.utils import rotated_grid
1465                lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation,
1466                                                  self.latitudeSouthernPole,
1467                                                  self.longitudeSouthernPole)
1468        elif gdtn == 40: # Gaussian grid (only works for global!)
1469            from grib2io.utils.gauss_grid import gaussian_latitudes
1470            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1471            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1472            nlats = self.ny
1473            if not reggrid: # Reduced Gaussian grid.
1474                nlons = 2*nlats
1475                dlon = 360./nlons
1476            else:
1477                nlons = self.nx
1478                dlon = self.gridlengthXDirection
1479            lons = np.linspace(lon1,lon2,nlons)
1480            # Compute Gaussian lats (north to south)
1481            lats = gaussian_latitudes(nlats)
1482            if lat1 < lat2:  # reverse them if necessary
1483                lats = lats[::-1]
1484            lons,lats = np.meshgrid(lons,lats)
1485        elif gdtn in {10,20,30,31,110}:
1486            # Mercator, Lambert Conformal, Stereographic, Albers Equal Area,
1487            # Azimuthal Equidistant
1488            dx,dy = self.gridlengthXDirection, self.gridlengthYDirection
1489            lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1490            pj = pyproj.Proj(self.projParameters)
1491            llcrnrx, llcrnry = pj(lon1,lat1)
1492            x = llcrnrx+dx*np.arange(self.nx)
1493            y = llcrnry+dy*np.arange(self.ny)
1494            x,y = np.meshgrid(x, y)
1495            lons,lats = pj(x, y, inverse=True)
1496        elif gdtn == 90:
1497            # Satellite Projection
1498            dx = self.gridlengthXDirection
1499            dy = self.gridlengthYDirection
1500            pj = pyproj.Proj(self.projParameters)
1501            x = dx*np.indices((self.ny,self.nx),'f')[1,:,:]
1502            x -= 0.5*x.max()
1503            y = dy*np.indices((self.ny,self.nx),'f')[0,:,:]
1504            y -= 0.5*y.max()
1505            lons,lats = pj(x,y,inverse=True)
1506            # Set lons,lats to 1.e30 where undefined
1507            abslons = np.fabs(lons)
1508            abslats = np.fabs(lats)
1509            lons = np.where(abslons < 1.e20, lons, 1.e30)
1510            lats = np.where(abslats < 1.e20, lats, 1.e30)
1511        elif gdtn == 32769:
1512            # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered)
1513            from grib2io.utils import arakawa_rotated_grid
1514            from grib2io.utils.rotated_grid import DEG2RAD
1515            di, dj = 0.0, 0.0
1516            do_180 = False
1517            idir = 1 if self.scanModeFlags[0] == 0 else -1
1518            jdir = -1 if self.scanModeFlags[1] == 0 else 1
1519            grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1
1520            do_rot = 1 if grid_oriented == 1 else 0
1521            la1 = self.latitudeFirstGridpoint
1522            lo1 = self.longitudeFirstGridpoint
1523            clon = self.longitudeCenterGridpoint
1524            clat = self.latitudeCenterGridpoint
1525            lasp = clat - 90.0
1526            losp = clon
1527            llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp)
1528            la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp)
1529            rlat = -llat
1530            rlon = -llon
1531            if self.nx == 1:
1532                di = 0.0
1533            elif idir == 1:
1534                ti = rlon
1535                while ti < llon:
1536                    ti += 360.0
1537                di = (ti - llon)/float(self.nx-1)
1538            else:
1539                ti = llon
1540                while ti < rlon:
1541                    ti += 360.0
1542                di = (ti - rlon)/float(self.nx-1)
1543            if self.ny == 1:
1544               dj = 0.0
1545            else:
1546                dj = (rlat - llat)/float(self.ny-1)
1547                if dj < 0.0:
1548                    dj = -dj
1549            if idir == 1:
1550                if llon > rlon:
1551                    llon -= 360.0
1552                if llon < 0 and rlon > 0:
1553                    do_180 = True
1554            else:
1555                if rlon > llon:
1556                    rlon -= 360.0
1557                if rlon < 0 and llon > 0:
1558                    do_180 = True
1559            xlat1d = llat + (np.arange(self.ny)*jdir*dj)
1560            xlon1d = llon + (np.arange(self.nx)*idir*di)
1561            xlons, xlats = np.meshgrid(xlon1d,xlat1d)
1562            rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll)
1563            lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp)
1564            if do_180:
1565                lons = np.where(lons>180.0,lons-360.0,lons)
1566            vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles)
1567            rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats)
1568            del xlat1d, xlon1d, xlats, xlons
1569        else:
1570            raise ValueError('Unsupported grid')
1571
1572        _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons)
1573        try:
1574            _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots
1575        except(NameError):
1576            pass
1577
1578        return lats, lons
1579
1580
1581    def map_keys(self):
1582        """
1583        Unpack data grid replacing integer values with strings.
1584
1585        These types of fields are categorical or classifications where data
1586        values do not represent an observable or predictable physical quantity.
1587        An example of such a field would be [Dominant Precipitation Type -
1588        DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml)
1589
1590        Returns
1591        -------
1592        map_keys
1593            numpy.ndarray of string values per element.
1594        """
1595        hold_auto_nans = _AUTO_NANS
1596        set_auto_nans(False)
1597
1598        if (np.all(self.section1[0:2] == [7, 14]) and self.shortName == "PWTHER") or (
1599            self._isNDFD and self.shortName in {"WX", "WWA"}
1600        ):
1601            keys = utils.decode_wx_strings(self.section2)
1602            if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]:
1603                keys[int(self.priMissingValue)] = 'Missing'
1604            if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]:
1605                keys[int(self.secMissingValue)] = 'Missing'
1606            u,inv = np.unique(self.data,return_inverse=True)
1607            fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape)
1608        else:
1609            # For data whose units are defined in a code table (i.e. classification or mask)
1610            tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0]
1611            fld = self.data.astype(np.int32).astype(str)
1612            tbl = tables.get_table(tblname,expand=True)
1613            for val in np.unique(fld):
1614                fld = np.where(fld==val,tbl[val],fld)
1615        set_auto_nans(hold_auto_nans)
1616        return fld
1617
1618
1619    def to_bytes(self, validate: bool=True):
1620        """
1621        Return packed GRIB2 message in bytes format.
1622
1623        This will be useful for exporting data in non-file formats. For example,
1624        can be used to output grib data directly to S3 using the boto3 client
1625        without the need to write a temporary file to upload first.
1626
1627        Parameters
1628        ----------
1629        validate: default=True
1630            If `True` (DEFAULT), validates first/last four bytes for proper
1631            formatting, else returns None. If `False`, message is output as is.
1632
1633        Returns
1634        -------
1635        to_bytes
1636            Returns GRIB2 formatted message as bytes.
1637        """
1638        if hasattr(self,'_msg'):
1639            if validate:
1640                if self.validate():
1641                    return self._msg
1642                else:
1643                    return None
1644            else:
1645                return self._msg
1646        else:
1647            return None
1648
1649
1650    def interpolate(self, method, grid_def_out, method_options=None, drtn=None,
1651                    num_threads=1):
1652        """
1653        Grib2Message Interpolator
1654
1655        Performs spatial interpolation via the [NCEPLIBS-ip
1656        library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate
1657        method only supports scalar interpolation. If you need to perform
1658        vector interpolation, use the module-level `grib2io.interpolate`
1659        function.
1660
1661        Parameters
1662        ----------
1663        method
1664            Interpolate method to use. This can either be an integer or string
1665            using the following mapping:
1666
1667            | Interpolate Scheme | Integer Value |
1668            | :---:              | :---:         |
1669            | 'bilinear'         | 0             |
1670            | 'bicubic'          | 1             |
1671            | 'neighbor'         | 2             |
1672            | 'budget'           | 3             |
1673            | 'spectral'         | 4             |
1674            | 'neighbor-budget'  | 6             |
1675
1676        grid_def_out : grib2io.Grib2GridDef
1677            Grib2GridDef object of the output grid.
1678        method_options : list of ints, optional
1679            Interpolation options. See the NCEPLIBS-ip documentation for
1680            more information on how these are used.
1681        drtn
1682            Data Representation Template to be used for the returned
1683            interpolated GRIB2 message. When `None`, the data representation
1684            template of the source GRIB2 message is used. Once again, it is the
1685            user's responsibility to properly set the Data Representation
1686            Template attributes.
1687        num_threads : int, optional
1688            Number of OpenMP threads to use for interpolation. The default
1689            value is 1. If NCEPLIBS-ip and grib2io's iplib extension module
1690            was not built with OpenMP, then this keyword argument and value
1691            will have no impact.
1692
1693        Returns
1694        -------
1695        interpolate
1696            If interpolating to a grid, a new Grib2Message object is returned.
1697            The GRIB2 metadata of the new Grib2Message object is identical to
1698            the input except where required to be different because of the new
1699            grid specs and possibly a new data representation template.
1700
1701            If interpolating to station points, the interpolated data values are
1702            returned as a numpy.ndarray.
1703        """
1704        section0 = self.section0
1705        section0[-1] = 0
1706        gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn]
1707        section3 = np.concatenate((gds,grid_def_out.gdt))
1708        drtn = self.drtn if drtn is None else drtn
1709
1710        msg = Grib2Message(section0,self.section1,self.section2,section3,
1711                           self.section4,None,self.bitMapFlag.value,drtn=drtn)
1712
1713        msg._msgnum = -1
1714        msg._deflist = self._deflist
1715        msg._coordlist = self._coordlist
1716        shape = (msg.ny,msg.nx)
1717        ndim = 2
1718        if msg.typeOfValues == 0:
1719            dtype = 'float32'
1720        elif msg.typeOfValues == 1:
1721            dtype = 'int32'
1722        msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out,
1723                                method_options=method_options,num_threads=num_threads).reshape(msg.ny,msg.nx)
1724        msg.section5[0] = grid_def_out.npoints
1725        return msg
1726
1727
1728    def subset(self, lats, lons):
1729        """
1730        Return a spatial subset.
1731
1732        Currently only supports regular grids of the following types:
1733
1734        | Grid Type                                                    | gdtn  |
1735        | :---:                                                        | :---: |
1736        | Latitude/Longitude, Equidistant Cylindrical, or Plate Carree | 0     |
1737        | Rotated Latitude/Longitude                                   | 1     |
1738        | Mercator                                                     | 10    |
1739        | Polar Stereographic                                          | 20    |
1740        | Lambert Conformal                                            | 30    |
1741        | Albers Equal-Area                                            | 31    |
1742        | Gaussian Latitude/Longitude                                  | 40    |
1743        | Equatorial Azimuthal Equidistant Projection                  | 110   |
1744
1745        Parameters
1746        ----------
1747        lats
1748            List or tuple of latitudes.  The minimum and maximum latitudes will
1749            be used to define the southern and northern boundaries.
1750
1751            The order of the latitudes is not important.  The function will
1752            determine which is the minimum and maximum.
1753
1754            The latitudes should be in decimal degrees with 0.0 at the equator,
1755            positive values in the northern hemisphere increasing to 90, and
1756            negative values in the southern hemisphere decreasing to -90.
1757        lons
1758            List or tuple of longitudes.  The minimum and maximum longitudes
1759            will be used to define the western and eastern boundaries.
1760
1761            The order of the longitudes is not important.  The function will
1762            determine which is the minimum and maximum.
1763
1764            GRIB2 longitudes should be in decimal degrees with 0.0 at the prime
1765            meridian, positive values increasing eastward to 360.  There are no
1766            negative GRIB2 longitudes.
1767
1768            The typical west longitudes that start at 0.0 at the prime meridian
1769            and decrease to -180 westward, are converted to GRIB2 longitudes by
1770            '360 - (absolute value of the west longitude)' where typical
1771            eastern longitudes are unchanged as GRIB2 longitudes.
1772
1773        Returns
1774        -------
1775        subset
1776            A spatial subset of a GRIB2 message.
1777        """
1778        if self.gdtn not in [0, 1, 10, 20, 30, 31, 40, 110]:
1779            raise ValueError(
1780                """
1781
1782Subset only works for
1783    Latitude/Longitude, Equidistant Cylindrical, or Plate Carree (gdtn=0)
1784    Rotated Latitude/Longitude (gdtn=1)
1785    Mercator (gdtn=10)
1786    Polar Stereographic (gdtn=20)
1787    Lambert Conformal (gdtn=30)
1788    Albers Equal-Area (gdtn=31)
1789    Gaussian Latitude/Longitude (gdtn=40)
1790    Equatorial Azimuthal Equidistant Projection (gdtn=110)
1791
1792"""
1793            )
1794
1795        if self.nx == 0 or self.ny == 0:
1796            raise ValueError(
1797                """
1798
1799Subset only works for regular grids.
1800
1801"""
1802            )
1803
1804        newmsg = Grib2Message(
1805            np.copy(self.section0),
1806            np.copy(self.section1),
1807            np.copy(self.section2),
1808            np.copy(self.section3),
1809            np.copy(self.section4),
1810            np.copy(self.section5),
1811        )
1812
1813        msglats, msglons = self.grid()
1814
1815        la1 = np.max(lats)
1816        lo1 = np.min(lons)
1817        la2 = np.min(lats)
1818        lo2 = np.max(lons)
1819
1820        # Find the indices of the first and last grid points to the nearest
1821        # lat/lon values in the grid.
1822        first_lat = np.abs(msglats - la1)
1823        first_lon = np.abs(msglons - lo1)
1824        max_idx = np.maximum(first_lat, first_lon)
1825        first_j, first_i = np.where(max_idx == np.min(max_idx))
1826
1827        last_lat = np.abs(msglats - la2)
1828        last_lon = np.abs(msglons - lo2)
1829        max_idx = np.maximum(last_lat, last_lon)
1830        last_j, last_i = np.where(max_idx == np.min(max_idx))
1831
1832        setattr(newmsg, "latitudeFirstGridpoint", msglats[first_j[0], first_i[0]])
1833        setattr(newmsg, "longitudeFirstGridpoint", msglons[first_j[0], first_i[0]])
1834        setattr(newmsg, "nx", np.abs(first_i[0] - last_i[0]))
1835        setattr(newmsg, "ny", np.abs(first_j[0] - last_j[0]))
1836
1837        # Set *LastGridpoint attributes even if only used for gdtn=[0, 1, 40].
1838        # This information is used to subset xarray datasets and even though
1839        # unnecessary for some supported grid types, it won't affect a grib2io
1840        # message to set them.
1841        setattr(newmsg, "latitudeLastGridpoint", msglats[last_j[0], last_i[0]])
1842        setattr(newmsg, "longitudeLastGridpoint", msglons[last_j[0], last_i[0]])
1843
1844        setattr(
1845            newmsg,
1846            "data",
1847            self.data[
1848                min(first_j[0], last_j[0]) : max(first_j[0], last_j[0]),
1849                min(first_i[0], last_i[0]) : max(first_i[0], last_i[0]),
1850            ].copy(),
1851        )
1852
1853        # Need to set the newmsg._sha1_section3 to a blank string so the grid
1854        # method ignores the cached lat/lon values.  This will force the grid
1855        # method to recompute the lat/lon values for the subsetted grid.
1856        newmsg._sha1_section3 = ""
1857        newmsg.grid()
1858
1859        return newmsg
1860
1861    def validate(self):
1862        """
1863        Validate a complete GRIB2 message.
1864
1865        The g2c library does its own internal validation when g2_gribend() is called, but
1866        we will check in grib2io also. The validation checks if the first 4 bytes in
1867        self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in
1868        section 0 equals the length of the packed message.
1869
1870        Returns
1871        -------
1872        `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise.
1873        """
1874        valid = False
1875        if hasattr(self,'_msg'):
1876            if self._msg[0:4]+self._msg[-4:] == b'GRIB7777':
1877                if self.section0[-1] == len(self._msg):
1878                    valid = True
1879        return valid

GRIB2 Message base class.

_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
1069    @property
1070    def gdtn(self):
1071        """Return Grid Definition Template Number"""
1072        return self.section3[4]

Return Grid Definition Template Number

gdt
1075    @property
1076    def gdt(self):
1077        """Return Grid Definition Template."""
1078        return self.gridDefinitionTemplate

Return Grid Definition Template.

pdtn
1081    @property
1082    def pdtn(self):
1083        """Return Product Definition Template Number."""
1084        return self.section4[1]

Return Product Definition Template Number.

pdt
1087    @property
1088    def pdt(self):
1089        """Return Product Definition Template."""
1090        return self.productDefinitionTemplate

Return Product Definition Template.

drtn
1093    @property
1094    def drtn(self):
1095        """Return Data Representation Template Number."""
1096        return self.section5[1]

Return Data Representation Template Number.

drt
1099    @property
1100    def drt(self):
1101        """Return Data Representation Template."""
1102        return self.dataRepresentationTemplate

Return Data Representation Template.

pdy
1105    @property
1106    def pdy(self):
1107        """Return the PDY ('YYYYMMDD')."""
1108        return ''.join([str(i) for i in self.section1[5:8]])

Return the PDY ('YYYYMMDD').

griddef
1111    @property
1112    def griddef(self):
1113        """Return a Grib2GridDef instance for a GRIB2 message."""
1114        return Grib2GridDef.from_section3(self.section3)

Return a Grib2GridDef instance for a GRIB2 message.

lats
1117    @property
1118    def lats(self):
1119        """Return grid latitudes."""
1120        return self.latlons()[0]

Return grid latitudes.

lons
1123    @property
1124    def lons(self):
1125        """Return grid longitudes."""
1126        return self.latlons()[1]

Return grid longitudes.

min
1128    @property
1129    def min(self):
1130        """Return minimum value of data."""
1131        return np.nanmin(self.data)

Return minimum value of data.

max
1134    @property
1135    def max(self):
1136        """Return maximum value of data."""
1137        return np.nanmax(self.data)

Return maximum value of data.

mean
1140    @property
1141    def mean(self):
1142        """Return mean value of data."""
1143        return np.nanmean(self.data)

Return mean value of data.

median
1146    @property
1147    def median(self):
1148        """Return median value of data."""
1149        return np.nanmedian(self.data)

Return median value of data.

shape
1152    @property
1153    def shape(self):
1154        """Return shape of data."""
1155        return self.griddef.shape

Return shape of data.

def attrs_by_section(self, sect: int, values: bool = False):
1196    def attrs_by_section(self, sect: int, values: bool=False):
1197        """
1198        Provide a tuple of attribute names for the given GRIB2 section.
1199
1200        Parameters
1201        ----------
1202        sect
1203            The GRIB2 section number.
1204        values
1205            Optional (default is `False`) argument to return attributes values.
1206
1207        Returns
1208        -------
1209        attrs_by_section
1210            A list of attribute names or dict of name:value pairs if `values =
1211            True`.
1212        """
1213        if sect in {0,1,6}:
1214            attrs = templates._section_attrs[sect]
1215        elif sect in {3,4,5}:
1216            def _find_class_index(n):
1217                _key = {3:'Grid', 4:'Product', 5:'Data'}
1218                for i,c in enumerate(self.__class__.__mro__):
1219                    if _key[n] in c.__name__:
1220                        return i
1221                else:
1222                    return []
1223            if sys.version_info.minor <= 8:
1224                attrs = templates._section_attrs[sect]+\
1225                        [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')]
1226            else:
1227                attrs = templates._section_attrs[sect]+\
1228                        self.__class__.__mro__[_find_class_index(sect)]._attrs()
1229        else:
1230            attrs = []
1231        if values:
1232            return {k:getattr(self,k) for k in attrs}
1233        else:
1234            return attrs

Provide a tuple of attribute names for the given GRIB2 section.

Parameters
  • sect: The GRIB2 section number.
  • values: Optional (default is False) argument to return attributes values.
Returns
  • attrs_by_section: A list of attribute names or dict of name:value pairs if values = True.
def copy(self, deep: bool = True):
1237    def copy(self, deep: bool = True):
1238        """Returns a copy of this Grib2Message.
1239
1240        When `deep=True`, a copy is made of each of the GRIB2 section arrays and
1241        the data are unpacked from the source object and copied into the new
1242        object. Otherwise, a shallow copy of each array is performed and no data
1243        are copied.
1244
1245        Parameters
1246        ----------
1247        deep : bool, default: True
1248            Whether each GRIB2 section array and data are copied onto
1249            the new object. Default is True.
1250
1251        Returns
1252        -------
1253        object : Grib2Message
1254            New Grib2Message object.
1255
1256            .. versionadded:: 2.6.0
1257        """
1258        return copy.deepcopy(self) if deep else copy.copy(self)

Returns a copy of this Grib2Message.

When deep=True, a copy is made of each of the GRIB2 section arrays and the data are unpacked from the source object and copied into the new object. Otherwise, a shallow copy of each array is performed and no data are copied.

Parameters
  • deep : bool, default (True): Whether each GRIB2 section array and data are copied onto the new object. Default is True.
Returns
  • object (Grib2Message): New Grib2Message object.

    New in version 2.6.0.

def pack(self):
1261    def pack(self):
1262        """
1263        Pack GRIB2 section data into a binary message.
1264
1265        It is the user's responsibility to populate the GRIB2 section
1266        information with appropriate metadata.
1267        """
1268        # Create beginning of packed binary message with section 0 and 1 data.
1269        self._sections = []
1270        self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection)
1271        self._sections += [0,1]
1272
1273        # Add section 2 if present.
1274        if isinstance(self.section2,bytes) and len(self.section2) > 0:
1275            self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2)
1276            self._sections.append(2)
1277
1278        # Add section 3.
1279        self.section3[1] = self.nx * self.ny
1280        self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection,
1281                                                   self.gridDefinitionTemplate,
1282                                                   self._deflist)
1283        self._sections.append(3)
1284
1285        # Prepare data.
1286        if self._data is None:
1287            if self._ondiskarray is None:
1288                raise ValueError("Grib2Message object has no data, thus it cannot be packed.")
1289        field = np.copy(self.data)
1290        if self.scanModeFlags is not None:
1291            if self.scanModeFlags[3]:
1292                fieldsave = field.astype('f') # Casting makes a copy
1293                field[1::2,:] = fieldsave[1::2,::-1]
1294        fld = field.astype('f')
1295
1296        # Prepare bitmap, if necessary
1297        bitmapflag = self.bitMapFlag.value
1298        if bitmapflag == 0:
1299            if self.bitmap is not None:
1300                bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT)
1301            else:
1302                bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT)
1303        else:
1304            bmap = None
1305
1306        # Prepare data for packing if nans are present
1307        fld = np.ravel(fld)
1308        if bitmapflag in {0,254}:
1309            fld = np.where(np.isnan(fld),0,fld)
1310        else:
1311            if np.isnan(fld).any():
1312                if hasattr(self,'priMissingValue'):
1313                    fld = np.where(np.isnan(fld),self.priMissingValue,fld)
1314            if hasattr(self,'_missvalmap'):
1315                if hasattr(self,'priMissingValue'):
1316                    fld = np.where(self._missvalmap==1,self.priMissingValue,fld)
1317                if hasattr(self,'secMissingValue'):
1318                    fld = np.where(self._missvalmap==2,self.secMissingValue,fld)
1319
1320        # Add sections 4, 5, 6, and 7.
1321        self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn,
1322                                                    self.productDefinitionTemplate,
1323                                                    self._coordlist,
1324                                                    self.drtn,
1325                                                    self.dataRepresentationTemplate,
1326                                                    fld,
1327                                                    bitmapflag,
1328                                                    bmap)
1329        self._sections.append(4)
1330        self._sections.append(5)
1331        self._sections.append(6)
1332        self._sections.append(7)
1333
1334        # Finalize GRIB2 message with section 8.
1335        self._msg, self._pos = g2clib.grib2_end(self._msg)
1336        self._sections.append(8)
1337        self.section0[-1] = len(self._msg)

Pack GRIB2 section data into a binary message.

It is the user's responsibility to populate the GRIB2 section information with appropriate metadata.

data: <built-in function array>
1340    @property
1341    def data(self) -> np.array:
1342        """Access the unpacked data values."""
1343        if self._data is None:
1344            if self._auto_nans != _AUTO_NANS:
1345                self._data = self._ondiskarray
1346            self._data = np.asarray(self._ondiskarray)
1347        return self._data

Access the unpacked data values.

def flush_data(self):
1388    def flush_data(self):
1389        """
1390        Flush the unpacked data values from the Grib2Message object.
1391
1392        Notes
1393        -----
1394        If the Grib2Message object was constructed from "scratch" (i.e.
1395        not read from file), this method will remove the data array from
1396        the object and it cannot be recovered.
1397        """
1398        self._data = None
1399        self.bitmap = None

Flush the unpacked data values from the Grib2Message object.

Notes

If the Grib2Message object was constructed from "scratch" (i.e. not read from file), this method will remove the data array from the object and it cannot be recovered.

def latlons(self, *args, **kwrgs):
1410    def latlons(self, *args, **kwrgs):
1411        """Alias for `grib2io.Grib2Message.grid` method."""
1412        return self.grid(*args, **kwrgs)

Alias for grib2io.Grib2Message.grid method.

def grid(self, unrotate: bool = True):
1415    def grid(self, unrotate: bool=True):
1416        """
1417        Return lats,lons (in degrees) of grid.
1418
1419        Currently can handle reg. lat/lon,cglobal Gaussian, mercator,
1420        stereographic, lambert conformal, albers equal-area, space-view and
1421        azimuthal equidistant grids.
1422
1423        Parameters
1424        ----------
1425        unrotate
1426            If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the
1427            grid, otherwise `False`, do not.
1428
1429        Returns
1430        -------
1431        lats, lons : numpy.ndarray
1432            Returns two numpy.ndarrays with dtype=numpy.float32 of grid
1433            latitudes and longitudes in units of degrees.
1434        """
1435        if self._sha1_section3 in _latlon_datastore.keys():
1436            return (_latlon_datastore[self._sha1_section3]['latitude'],
1437                    _latlon_datastore[self._sha1_section3]['longitude'])
1438        gdtn = self.gridDefinitionTemplateNumber.value
1439        gdtmpl = self.gridDefinitionTemplate
1440        reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid
1441        if gdtn == 0:
1442            # Regular lat/lon grid
1443            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1444            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1445            dlon = self.gridlengthXDirection
1446            dlat = self.gridlengthYDirection
1447            if lon2 < lon1 and dlon < 0: lon1 = -lon1
1448            lats = np.linspace(lat1,lat2,self.ny)
1449            if reggrid:
1450                lons = np.linspace(lon1,lon2,self.nx)
1451            else:
1452                lons = np.linspace(lon1,lon2,self.ny*2)
1453            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1454        elif gdtn == 1: # Rotated Lat/Lon grid
1455            pj = pyproj.Proj(self.projParameters)
1456            lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint
1457            lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint
1458            if lon1 > 180.0: lon1 -= 360.0
1459            if lon2 > 180.0: lon2 -= 360.0
1460            lats = np.linspace(lat1,lat2,self.ny)
1461            lons = np.linspace(lon1,lon2,self.nx)
1462            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1463            if unrotate:
1464                from grib2io.utils import rotated_grid
1465                lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation,
1466                                                  self.latitudeSouthernPole,
1467                                                  self.longitudeSouthernPole)
1468        elif gdtn == 40: # Gaussian grid (only works for global!)
1469            from grib2io.utils.gauss_grid import gaussian_latitudes
1470            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1471            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1472            nlats = self.ny
1473            if not reggrid: # Reduced Gaussian grid.
1474                nlons = 2*nlats
1475                dlon = 360./nlons
1476            else:
1477                nlons = self.nx
1478                dlon = self.gridlengthXDirection
1479            lons = np.linspace(lon1,lon2,nlons)
1480            # Compute Gaussian lats (north to south)
1481            lats = gaussian_latitudes(nlats)
1482            if lat1 < lat2:  # reverse them if necessary
1483                lats = lats[::-1]
1484            lons,lats = np.meshgrid(lons,lats)
1485        elif gdtn in {10,20,30,31,110}:
1486            # Mercator, Lambert Conformal, Stereographic, Albers Equal Area,
1487            # Azimuthal Equidistant
1488            dx,dy = self.gridlengthXDirection, self.gridlengthYDirection
1489            lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1490            pj = pyproj.Proj(self.projParameters)
1491            llcrnrx, llcrnry = pj(lon1,lat1)
1492            x = llcrnrx+dx*np.arange(self.nx)
1493            y = llcrnry+dy*np.arange(self.ny)
1494            x,y = np.meshgrid(x, y)
1495            lons,lats = pj(x, y, inverse=True)
1496        elif gdtn == 90:
1497            # Satellite Projection
1498            dx = self.gridlengthXDirection
1499            dy = self.gridlengthYDirection
1500            pj = pyproj.Proj(self.projParameters)
1501            x = dx*np.indices((self.ny,self.nx),'f')[1,:,:]
1502            x -= 0.5*x.max()
1503            y = dy*np.indices((self.ny,self.nx),'f')[0,:,:]
1504            y -= 0.5*y.max()
1505            lons,lats = pj(x,y,inverse=True)
1506            # Set lons,lats to 1.e30 where undefined
1507            abslons = np.fabs(lons)
1508            abslats = np.fabs(lats)
1509            lons = np.where(abslons < 1.e20, lons, 1.e30)
1510            lats = np.where(abslats < 1.e20, lats, 1.e30)
1511        elif gdtn == 32769:
1512            # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered)
1513            from grib2io.utils import arakawa_rotated_grid
1514            from grib2io.utils.rotated_grid import DEG2RAD
1515            di, dj = 0.0, 0.0
1516            do_180 = False
1517            idir = 1 if self.scanModeFlags[0] == 0 else -1
1518            jdir = -1 if self.scanModeFlags[1] == 0 else 1
1519            grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1
1520            do_rot = 1 if grid_oriented == 1 else 0
1521            la1 = self.latitudeFirstGridpoint
1522            lo1 = self.longitudeFirstGridpoint
1523            clon = self.longitudeCenterGridpoint
1524            clat = self.latitudeCenterGridpoint
1525            lasp = clat - 90.0
1526            losp = clon
1527            llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp)
1528            la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp)
1529            rlat = -llat
1530            rlon = -llon
1531            if self.nx == 1:
1532                di = 0.0
1533            elif idir == 1:
1534                ti = rlon
1535                while ti < llon:
1536                    ti += 360.0
1537                di = (ti - llon)/float(self.nx-1)
1538            else:
1539                ti = llon
1540                while ti < rlon:
1541                    ti += 360.0
1542                di = (ti - rlon)/float(self.nx-1)
1543            if self.ny == 1:
1544               dj = 0.0
1545            else:
1546                dj = (rlat - llat)/float(self.ny-1)
1547                if dj < 0.0:
1548                    dj = -dj
1549            if idir == 1:
1550                if llon > rlon:
1551                    llon -= 360.0
1552                if llon < 0 and rlon > 0:
1553                    do_180 = True
1554            else:
1555                if rlon > llon:
1556                    rlon -= 360.0
1557                if rlon < 0 and llon > 0:
1558                    do_180 = True
1559            xlat1d = llat + (np.arange(self.ny)*jdir*dj)
1560            xlon1d = llon + (np.arange(self.nx)*idir*di)
1561            xlons, xlats = np.meshgrid(xlon1d,xlat1d)
1562            rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll)
1563            lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp)
1564            if do_180:
1565                lons = np.where(lons>180.0,lons-360.0,lons)
1566            vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles)
1567            rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats)
1568            del xlat1d, xlon1d, xlats, xlons
1569        else:
1570            raise ValueError('Unsupported grid')
1571
1572        _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons)
1573        try:
1574            _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots
1575        except(NameError):
1576            pass
1577
1578        return lats, lons

Return lats,lons (in degrees) of grid.

Currently can handle reg. lat/lon,cglobal Gaussian, mercator, stereographic, lambert conformal, albers equal-area, space-view and azimuthal equidistant grids.

Parameters
  • unrotate: If True [DEFAULT], and grid is rotated lat/lon, then unrotate the grid, 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):
1581    def map_keys(self):
1582        """
1583        Unpack data grid replacing integer values with strings.
1584
1585        These types of fields are categorical or classifications where data
1586        values do not represent an observable or predictable physical quantity.
1587        An example of such a field would be [Dominant Precipitation Type -
1588        DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml)
1589
1590        Returns
1591        -------
1592        map_keys
1593            numpy.ndarray of string values per element.
1594        """
1595        hold_auto_nans = _AUTO_NANS
1596        set_auto_nans(False)
1597
1598        if (np.all(self.section1[0:2] == [7, 14]) and self.shortName == "PWTHER") or (
1599            self._isNDFD and self.shortName in {"WX", "WWA"}
1600        ):
1601            keys = utils.decode_wx_strings(self.section2)
1602            if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]:
1603                keys[int(self.priMissingValue)] = 'Missing'
1604            if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]:
1605                keys[int(self.secMissingValue)] = 'Missing'
1606            u,inv = np.unique(self.data,return_inverse=True)
1607            fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape)
1608        else:
1609            # For data whose units are defined in a code table (i.e. classification or mask)
1610            tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0]
1611            fld = self.data.astype(np.int32).astype(str)
1612            tbl = tables.get_table(tblname,expand=True)
1613            for val in np.unique(fld):
1614                fld = np.where(fld==val,tbl[val],fld)
1615        set_auto_nans(hold_auto_nans)
1616        return fld

Unpack data grid replacing integer values with strings.

These types of fields are categorical or classifications where data values do not represent an observable or predictable physical quantity. An example of such a field would be Dominant Precipitation Type - DPTYPE

Returns
  • map_keys: numpy.ndarray of string values per element.
def to_bytes(self, validate: bool = True):
1619    def to_bytes(self, validate: bool=True):
1620        """
1621        Return packed GRIB2 message in bytes format.
1622
1623        This will be useful for exporting data in non-file formats. For example,
1624        can be used to output grib data directly to S3 using the boto3 client
1625        without the need to write a temporary file to upload first.
1626
1627        Parameters
1628        ----------
1629        validate: default=True
1630            If `True` (DEFAULT), validates first/last four bytes for proper
1631            formatting, else returns None. If `False`, message is output as is.
1632
1633        Returns
1634        -------
1635        to_bytes
1636            Returns GRIB2 formatted message as bytes.
1637        """
1638        if hasattr(self,'_msg'):
1639            if validate:
1640                if self.validate():
1641                    return self._msg
1642                else:
1643                    return None
1644            else:
1645                return self._msg
1646        else:
1647            return None

Return packed GRIB2 message in bytes format.

This will be useful for exporting data in non-file formats. For example, can be used to output grib data directly to S3 using the boto3 client without the need to write a temporary file to upload first.

Parameters
  • validate (default=True): If True (DEFAULT), validates first/last four bytes for proper formatting, else returns None. 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):
1650    def interpolate(self, method, grid_def_out, method_options=None, drtn=None,
1651                    num_threads=1):
1652        """
1653        Grib2Message Interpolator
1654
1655        Performs spatial interpolation via the [NCEPLIBS-ip
1656        library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate
1657        method only supports scalar interpolation. If you need to perform
1658        vector interpolation, use the module-level `grib2io.interpolate`
1659        function.
1660
1661        Parameters
1662        ----------
1663        method
1664            Interpolate method to use. This can either be an integer or string
1665            using the following mapping:
1666
1667            | Interpolate Scheme | Integer Value |
1668            | :---:              | :---:         |
1669            | 'bilinear'         | 0             |
1670            | 'bicubic'          | 1             |
1671            | 'neighbor'         | 2             |
1672            | 'budget'           | 3             |
1673            | 'spectral'         | 4             |
1674            | 'neighbor-budget'  | 6             |
1675
1676        grid_def_out : grib2io.Grib2GridDef
1677            Grib2GridDef object of the output grid.
1678        method_options : list of ints, optional
1679            Interpolation options. See the NCEPLIBS-ip documentation for
1680            more information on how these are used.
1681        drtn
1682            Data Representation Template to be used for the returned
1683            interpolated GRIB2 message. When `None`, the data representation
1684            template of the source GRIB2 message is used. Once again, it is the
1685            user's responsibility to properly set the Data Representation
1686            Template attributes.
1687        num_threads : int, optional
1688            Number of OpenMP threads to use for interpolation. The default
1689            value is 1. If NCEPLIBS-ip and grib2io's iplib extension module
1690            was not built with OpenMP, then this keyword argument and value
1691            will have no impact.
1692
1693        Returns
1694        -------
1695        interpolate
1696            If interpolating to a grid, a new Grib2Message object is returned.
1697            The GRIB2 metadata of the new Grib2Message object is identical to
1698            the input except where required to be different because of the new
1699            grid specs and possibly a new data representation template.
1700
1701            If interpolating to station points, the interpolated data values are
1702            returned as a numpy.ndarray.
1703        """
1704        section0 = self.section0
1705        section0[-1] = 0
1706        gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn]
1707        section3 = np.concatenate((gds,grid_def_out.gdt))
1708        drtn = self.drtn if drtn is None else drtn
1709
1710        msg = Grib2Message(section0,self.section1,self.section2,section3,
1711                           self.section4,None,self.bitMapFlag.value,drtn=drtn)
1712
1713        msg._msgnum = -1
1714        msg._deflist = self._deflist
1715        msg._coordlist = self._coordlist
1716        shape = (msg.ny,msg.nx)
1717        ndim = 2
1718        if msg.typeOfValues == 0:
1719            dtype = 'float32'
1720        elif msg.typeOfValues == 1:
1721            dtype = 'int32'
1722        msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out,
1723                                method_options=method_options,num_threads=num_threads).reshape(msg.ny,msg.nx)
1724        msg.section5[0] = grid_def_out.npoints
1725        return msg

Grib2Message Interpolator

Performs spatial interpolation via the NCEPLIBS-ip library. This interpolate method only supports scalar interpolation. If you need to perform vector interpolation, use the module-level grib2io.interpolate function.

Parameters
  • method: Interpolate method to use. This can either be an integer or string using the following mapping:
Interpolate Scheme Integer Value
'bilinear' 0
'bicubic' 1
'neighbor' 2
'budget' 3
'spectral' 4
'neighbor-budget' 6

  • grid_def_out (grib2io.Grib2GridDef): Grib2GridDef object of the output grid.
  • method_options (list of ints, optional): Interpolation options. See the NCEPLIBS-ip documentation for more information on how these are used.
  • drtn: Data Representation Template to be used for the returned interpolated GRIB2 message. When None, the data representation template of the source GRIB2 message is used. Once again, it is the user's responsibility to properly set the Data Representation Template attributes.
  • num_threads (int, optional): Number of OpenMP threads to use for interpolation. The default value is 1. If NCEPLIBS-ip and grib2io's iplib extension module was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
  • interpolate: If interpolating to a grid, a new Grib2Message object is returned. The GRIB2 metadata of the new Grib2Message object is identical to the input except where required to be different because of the new grid specs and possibly a new data representation template.

If interpolating to station points, the interpolated data values are returned as a numpy.ndarray.

def subset(self, lats, lons):
1728    def subset(self, lats, lons):
1729        """
1730        Return a spatial subset.
1731
1732        Currently only supports regular grids of the following types:
1733
1734        | Grid Type                                                    | gdtn  |
1735        | :---:                                                        | :---: |
1736        | Latitude/Longitude, Equidistant Cylindrical, or Plate Carree | 0     |
1737        | Rotated Latitude/Longitude                                   | 1     |
1738        | Mercator                                                     | 10    |
1739        | Polar Stereographic                                          | 20    |
1740        | Lambert Conformal                                            | 30    |
1741        | Albers Equal-Area                                            | 31    |
1742        | Gaussian Latitude/Longitude                                  | 40    |
1743        | Equatorial Azimuthal Equidistant Projection                  | 110   |
1744
1745        Parameters
1746        ----------
1747        lats
1748            List or tuple of latitudes.  The minimum and maximum latitudes will
1749            be used to define the southern and northern boundaries.
1750
1751            The order of the latitudes is not important.  The function will
1752            determine which is the minimum and maximum.
1753
1754            The latitudes should be in decimal degrees with 0.0 at the equator,
1755            positive values in the northern hemisphere increasing to 90, and
1756            negative values in the southern hemisphere decreasing to -90.
1757        lons
1758            List or tuple of longitudes.  The minimum and maximum longitudes
1759            will be used to define the western and eastern boundaries.
1760
1761            The order of the longitudes is not important.  The function will
1762            determine which is the minimum and maximum.
1763
1764            GRIB2 longitudes should be in decimal degrees with 0.0 at the prime
1765            meridian, positive values increasing eastward to 360.  There are no
1766            negative GRIB2 longitudes.
1767
1768            The typical west longitudes that start at 0.0 at the prime meridian
1769            and decrease to -180 westward, are converted to GRIB2 longitudes by
1770            '360 - (absolute value of the west longitude)' where typical
1771            eastern longitudes are unchanged as GRIB2 longitudes.
1772
1773        Returns
1774        -------
1775        subset
1776            A spatial subset of a GRIB2 message.
1777        """
1778        if self.gdtn not in [0, 1, 10, 20, 30, 31, 40, 110]:
1779            raise ValueError(
1780                """
1781
1782Subset only works for
1783    Latitude/Longitude, Equidistant Cylindrical, or Plate Carree (gdtn=0)
1784    Rotated Latitude/Longitude (gdtn=1)
1785    Mercator (gdtn=10)
1786    Polar Stereographic (gdtn=20)
1787    Lambert Conformal (gdtn=30)
1788    Albers Equal-Area (gdtn=31)
1789    Gaussian Latitude/Longitude (gdtn=40)
1790    Equatorial Azimuthal Equidistant Projection (gdtn=110)
1791
1792"""
1793            )
1794
1795        if self.nx == 0 or self.ny == 0:
1796            raise ValueError(
1797                """
1798
1799Subset only works for regular grids.
1800
1801"""
1802            )
1803
1804        newmsg = Grib2Message(
1805            np.copy(self.section0),
1806            np.copy(self.section1),
1807            np.copy(self.section2),
1808            np.copy(self.section3),
1809            np.copy(self.section4),
1810            np.copy(self.section5),
1811        )
1812
1813        msglats, msglons = self.grid()
1814
1815        la1 = np.max(lats)
1816        lo1 = np.min(lons)
1817        la2 = np.min(lats)
1818        lo2 = np.max(lons)
1819
1820        # Find the indices of the first and last grid points to the nearest
1821        # lat/lon values in the grid.
1822        first_lat = np.abs(msglats - la1)
1823        first_lon = np.abs(msglons - lo1)
1824        max_idx = np.maximum(first_lat, first_lon)
1825        first_j, first_i = np.where(max_idx == np.min(max_idx))
1826
1827        last_lat = np.abs(msglats - la2)
1828        last_lon = np.abs(msglons - lo2)
1829        max_idx = np.maximum(last_lat, last_lon)
1830        last_j, last_i = np.where(max_idx == np.min(max_idx))
1831
1832        setattr(newmsg, "latitudeFirstGridpoint", msglats[first_j[0], first_i[0]])
1833        setattr(newmsg, "longitudeFirstGridpoint", msglons[first_j[0], first_i[0]])
1834        setattr(newmsg, "nx", np.abs(first_i[0] - last_i[0]))
1835        setattr(newmsg, "ny", np.abs(first_j[0] - last_j[0]))
1836
1837        # Set *LastGridpoint attributes even if only used for gdtn=[0, 1, 40].
1838        # This information is used to subset xarray datasets and even though
1839        # unnecessary for some supported grid types, it won't affect a grib2io
1840        # message to set them.
1841        setattr(newmsg, "latitudeLastGridpoint", msglats[last_j[0], last_i[0]])
1842        setattr(newmsg, "longitudeLastGridpoint", msglons[last_j[0], last_i[0]])
1843
1844        setattr(
1845            newmsg,
1846            "data",
1847            self.data[
1848                min(first_j[0], last_j[0]) : max(first_j[0], last_j[0]),
1849                min(first_i[0], last_i[0]) : max(first_i[0], last_i[0]),
1850            ].copy(),
1851        )
1852
1853        # Need to set the newmsg._sha1_section3 to a blank string so the grid
1854        # method ignores the cached lat/lon values.  This will force the grid
1855        # method to recompute the lat/lon values for the subsetted grid.
1856        newmsg._sha1_section3 = ""
1857        newmsg.grid()
1858
1859        return newmsg

Return a spatial subset.

Currently only supports regular grids of the following types:

Grid Type gdtn
Latitude/Longitude, Equidistant Cylindrical, or Plate Carree 0
Rotated Latitude/Longitude 1
Mercator 10
Polar Stereographic 20
Lambert Conformal 30
Albers Equal-Area 31
Gaussian Latitude/Longitude 40
Equatorial Azimuthal Equidistant Projection 110
Parameters
  • lats: List or tuple of latitudes. The minimum and maximum latitudes will be used to define the southern and northern boundaries.

The order of the latitudes is not important. The function will determine which is the minimum and maximum.

The latitudes should be in decimal degrees with 0.0 at the equator, positive values in the northern hemisphere increasing to 90, and negative values in the southern hemisphere decreasing to -90.

  • lons: List or tuple of longitudes. The minimum and maximum longitudes will be used to define the western and eastern boundaries.

The order of the longitudes is not important. The function will determine which is the minimum and maximum.

GRIB2 longitudes should be in decimal degrees with 0.0 at the prime meridian, positive values increasing eastward to 360. There are no negative GRIB2 longitudes.

The typical west longitudes that start at 0.0 at the prime meridian and decrease to -180 westward, are converted to GRIB2 longitudes by '360 - (absolute value of the west longitude)' where typical eastern longitudes are unchanged as GRIB2 longitudes.

Returns
  • subset: A spatial subset of a GRIB2 message.
def validate(self):
1861    def validate(self):
1862        """
1863        Validate a complete GRIB2 message.
1864
1865        The g2c library does its own internal validation when g2_gribend() is called, but
1866        we will check in grib2io also. The validation checks if the first 4 bytes in
1867        self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in
1868        section 0 equals the length of the packed message.
1869
1870        Returns
1871        -------
1872        `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise.
1873        """
1874        valid = False
1875        if hasattr(self,'_msg'):
1876            if self._msg[0:4]+self._msg[-4:] == b'GRIB7777':
1877                if self.section0[-1] == len(self._msg):
1878                    valid = True
1879        return valid

Validate a complete GRIB2 message.

The g2c library does its own internal validation when g2_gribend() is called, but we will check in grib2io also. The validation checks if the first 4 bytes in self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in section 0 equals the length of the packed message.

Returns
  • True if the packed GRIB2 message is complete and well-formed, False otherwise.
@dataclass
class Grib2GridDef:
2354@dataclass
2355class Grib2GridDef:
2356    """
2357    Class for Grid Definition Template Number and Template as attributes.
2358
2359    This allows for cleaner looking code when passing these metadata around.
2360    For example, the `grib2io._Grib2Message.interpolate` method and
2361    `grib2io.interpolate` function accepts these objects.
2362    """
2363    gdtn: int
2364    gdt: NDArray
2365
2366    @classmethod
2367    def from_section3(cls, section3):
2368        return cls(section3[4],section3[5:])
2369
2370    @property
2371    def nx(self):
2372        """Number of grid points in x-direction."""
2373        return int(self.gdt[7])
2374
2375    @property
2376    def ny(self):
2377        """Number of grid points in y-direction."""
2378        return int(self.gdt[8])
2379
2380    @property
2381    def npoints(self):
2382        """Total number of grid points."""
2383        return int(self.gdt[7] * self.gdt[8])
2384
2385    @property
2386    def shape(self):
2387        """Shape of the grid."""
2388        return (int(self.ny), int(self.nx))
2389
2390    def to_section3(self):
2391        """Return a full GRIB2 section3 array."""
2392        return np.array(
2393            [0, self.npoints, 0, 0, self.gdtn] + list(self.gdt)
2394        ).astype(np.int64)

Class for Grid Definition Template Number and Template as attributes.

This allows for cleaner looking code when passing these metadata around. For example, the grib2io._Grib2Message.interpolate method and grib2io.interpolate function accepts these objects.

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):
2366    @classmethod
2367    def from_section3(cls, section3):
2368        return cls(section3[4],section3[5:])
nx
2370    @property
2371    def nx(self):
2372        """Number of grid points in x-direction."""
2373        return int(self.gdt[7])

Number of grid points in x-direction.

ny
2375    @property
2376    def ny(self):
2377        """Number of grid points in y-direction."""
2378        return int(self.gdt[8])

Number of grid points in y-direction.

npoints
2380    @property
2381    def npoints(self):
2382        """Total number of grid points."""
2383        return int(self.gdt[7] * self.gdt[8])

Total number of grid points.

shape
2385    @property
2386    def shape(self):
2387        """Shape of the grid."""
2388        return (int(self.ny), int(self.nx))

Shape of the grid.

def to_section3(self):
2390    def to_section3(self):
2391        """Return a full GRIB2 section3 array."""
2392        return np.array(
2393            [0, self.npoints, 0, 0, self.gdtn] + list(self.gdt)
2394        ).astype(np.int64)

Return a full GRIB2 section3 array.

def msgs_from_index(index: dict, filehandle=None):
769def msgs_from_index(index: dict, filehandle=None):
770    """
771    Construct a list of Grib2Message objects from an index dictionary.
772
773    This function reconstructs a sequence of `Grib2Message` instances using
774    metadata sections stored in an index dictionary. If an open file handle is
775    provided, each message is linked to its on-disk binary data through a
776    `Grib2MessageOnDiskArray`, allowing deferred reading of the actual data
777    values from the GRIB2 file.
778
779    Parameters
780    ----------
781    index : dict
782        Dictionary containing parsed GRIB2 index information, including
783        section data arrays such as ``section0`` through ``section5``,
784        ``sectionOffset``, ``offset``, and ``bmapflag``.
785    filehandle : file-like object, optional
786        An open binary file handle to the GRIB2 file corresponding to the index.
787        If provided, the returned messages can access on-disk data arrays via
788        memory offsets. If not provided, only metadata will be available.
789
790    Returns
791    -------
792    list of Grib2Message
793        List of reconstructed `Grib2Message` objects built from the provided
794        index. Each message contains metadata, and if `filehandle` is given,
795        also references to on-disk data through a `Grib2MessageOnDiskArray`.
796
797    Notes
798    -----
799    - Each message is constructed by zipping the corresponding section entries
800      (sections 0–5 and bitmap flags).
801    - When a file handle is supplied, each message’s `_ondiskarray` attribute is
802      initialized to allow direct access to GRIB2 data values without loading
803      them fully into memory.
804    - The `_msgnum` attribute of each message is assigned sequentially to
805      preserve message order.
806    """
807    zipped = zip(
808        index["section0"],
809        index["section1"],
810        index["section2"],
811        index["section3"],
812        index["section4"],
813        index["section5"],
814        index["bmapflag"]
815    )
816    msgs = [Grib2Message(*sections) for sections in zipped]
817
818    if filehandle is not None:
819        for n, (msg, offset, secpos) in enumerate(zip(msgs, index["offset"], index["sectionOffset"])):
820            msg._ondiskarray = Grib2MessageOnDiskArray(
821                shape=(msg.ny, msg.nx),
822                ndim=2,
823                dtype=TYPE_OF_VALUES_DTYPE[msg.typeOfValues],
824                filehandle=filehandle,
825                msg=msg,
826                offset=offset,
827                bitmap_offset=secpos[6],
828                data_offset=secpos[7]
829            )
830            msg._msgnum = n
831    return msgs

Construct a list of Grib2Message objects from an index dictionary.

This function reconstructs a sequence of Grib2Message instances using metadata sections stored in an index dictionary. If an open file handle is provided, each message is linked to its on-disk binary data through a Grib2MessageOnDiskArray, allowing deferred reading of the actual data values from the GRIB2 file.

Parameters
  • index (dict): Dictionary containing parsed GRIB2 index information, including section data arrays such as 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.