grib2io API documentation

# 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 wrapper module. The decoding/encoding of GRIB2 metadata is translated into more descriptive, plain language metadata by looking up the integer code values against the appropriate GRIB2 code tables. These code tables are a part of the grib2io module.

View Source

     1from ._grib2io import *
     2from ._grib2io import __doc__
     3
     4try:
     5    from . import __config__
     6    __version__ = __config__.grib2io_version
     7except(ImportError):
     8    pass
     9
    10__all__ = ['open','Grib2Message','show_config','interpolate','tables','templates','utils',
    11           'Grib2GridDef']
    12
    13def show_config():
    14    """
    15    Print grib2io build configuration information.
    16    """
    17    from g2clib import __version__ as g2clib_version
    18    from g2clib import _has_png as have_png
    19    from g2clib import _has_jpeg as have_jpeg
    20    print("grib2io version %s Configuration:\n"%(__version__))
    21    print("\tg2c library version:".expandtabs(4),g2clib_version)
    22    print("\tJPEG compression support:".expandtabs(4),bool(have_jpeg))
    23    print("\tPNG compression support:".expandtabs(4),bool(have_png))

class open: View Source

     52class open():
     53    """
     54    GRIB2 File Object.  A physical file can contain one or more GRIB2 messages.  When instantiated,
     55    class `grib2io.open`, the file named `filename` is opened for reading (`mode = 'r'`) and is
     56    automatically indexed.  The indexing procedure reads some of the GRIB2 metadata for all GRIB2 Messages.
     57
     58    A GRIB2 Message may contain submessages whereby Section 2-7 can be repeated.  grib2io accommodates
     59    for this by flattening any GRIB2 submessages into multiple individual messages.
     60
     61    Attributes
     62    ----------
     63
     64    **`mode`** File IO mode of opening the file.
     65
     66    **`name`** Full path name of the GRIB2 file.
     67
     68    **`messages`** Count of GRIB2 Messages contained in the file.
     69
     70    **`current_message`** Current position of the file in units of GRIB2 Messages.
     71
     72    **`size`** Size of the file in units of bytes.
     73
     74    **`closed`** `True` is file handle is close; `False` otherwise.
     75
     76    **`variables`** Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
     77
     78    **`levels`** Tuple containing a unique list of wgrib2-formatted level/layer strings.
     79    """
     80    __slots__ = ('_filehandle','_hasindex','_index','mode','name','messages',
     81                 'current_message','size','closed','variables','levels','_pos')
     82    def __init__(self, filename, mode='r', **kwargs):
     83        """
     84        `open` Constructor
     85
     86        Parameters
     87        ----------
     88
     89        **`filename : str`**
     90
     91        File name containing GRIB2 messages.
     92
     93        **`mode : str, optional`**
     94
     95        File access mode where `r` opens the files for reading only; `w` opens the file for writing.
     96        """
     97        if mode in {'a','r','w'}:
     98            mode = mode+'b'
     99            if 'w' in mode: mode += '+'
    100        self._filehandle = builtins.open(filename,mode=mode,buffering=ONE_MB)
    101        self._hasindex = False
    102        self._index = {}
    103        self.mode = mode
    104        self.name = os.path.abspath(filename)
    105        self.messages = 0
    106        self.current_message = 0
    107        self.size = os.path.getsize(self.name)
    108        self.closed = self._filehandle.closed
    109        self.levels = None
    110        self.variables = None
    111        if 'r' in self.mode:
    112            try:
    113                self._build_index(no_data=kwargs['_xarray_backend'])
    114            except(KeyError):
    115                self._build_index()
    116        # FIX: Cannot perform reads on mode='a'
    117        #if 'a' in self.mode and self.size > 0: self._build_index()
    118
    119
    120    def __delete__(self, instance):
    121        """
    122        """
    123        self.close()
    124        del self._index
    125
    126
    127    def __enter__(self):
    128        """
    129        """
    130        return self
    131
    132
    133    def __exit__(self, atype, value, traceback):
    134        """
    135        """
    136        self.close()
    137
    138
    139    def __iter__(self):
    140        """
    141        """
    142        yield from self._index['msg']
    143
    144
    145    def __len__(self):
    146        """
    147        """
    148        return self.messages
    149
    150
    151    def __repr__(self):
    152        """
    153        """
    154        strings = []
    155        for k in self.__slots__:
    156            if k.startswith('_'): continue
    157            strings.append('%s = %s\n'%(k,eval('self.'+k)))
    158        return ''.join(strings)
    159
    160
    161    def __getitem__(self, key):
    162        """
    163        """
    164        if isinstance(key,int):
    165            if abs(key) >= len(self._index['msg']):
    166                raise IndexError("index out of range")
    167            else:
    168                return self._index['msg'][key]
    169        elif isinstance(key,str):
    170            return self.select(shortName=key)
    171        elif isinstance(key,slice):
    172            return self._index['msg'][key]
    173        else:
    174            raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.')
    175
    176
    177    def _build_index(self, no_data=False):
    178        """
    179        Perform indexing of GRIB2 Messages.
    180        """
    181        # Initialize index dictionary
    182        if not self._hasindex:
    183            self._index['offset'] = []
    184            self._index['bitmap_offset'] = []
    185            self._index['data_offset'] = []
    186            self._index['size'] = []
    187            self._index['data_size'] = []
    188            self._index['submessageOffset'] = []
    189            self._index['submessageBeginSection'] = []
    190            self._index['isSubmessage'] = []
    191            self._index['messageNumber'] = []
    192            self._index['msg'] = []
    193            self._hasindex = True
    194
    195        # Iterate
    196        while True:
    197            try:
    198                # Read first 4 bytes and decode...looking for "GRIB"
    199                pos = self._filehandle.tell()
    200                header = struct.unpack('>i',self._filehandle.read(4))[0]
    201
    202                # Test header. Then get information from GRIB2 Section 0: the discipline
    203                # number, edition number (should always be 2), and GRIB2 message size.
    204                # Then iterate to check for submessages.
    205                if header.to_bytes(4,'big') == b'GRIB':
    206
    207                    _issubmessage = False
    208                    _submsgoffset = 0
    209                    _submsgbegin = 0
    210                    _bmapflag = None
    211
    212                    # Read the rest of Section 0 using struct.
    213                    section0 = np.concatenate(([header],list(struct.unpack('>HBBQ',self._filehandle.read(12)))),dtype=np.int64)
    214                    assert section0[3] == 2
    215
    216                    # Read and unpack Section 1
    217                    secsize = struct.unpack('>i',self._filehandle.read(4))[0]
    218                    secnum = struct.unpack('>B',self._filehandle.read(1))[0]
    219                    assert secnum == 1
    220                    self._filehandle.seek(self._filehandle.tell()-5)
    221                    _grbmsg = self._filehandle.read(secsize)
    222                    _grbpos = 0
    223                    section1,_grbpos = g2clib.unpack1(_grbmsg,_grbpos,np.empty)
    224                    secrange = range(2,8)
    225                    while 1:
    226                        section2 = b''
    227                        for num in secrange:
    228                            secsize = struct.unpack('>i',self._filehandle.read(4))[0]
    229                            secnum = struct.unpack('>B',self._filehandle.read(1))[0]
    230                            if secnum == num:
    231                                if secnum == 2:
    232                                    if secsize > 0:
    233                                        section2 = self._filehandle.read(secsize-5)
    234                                elif secnum == 3:
    235                                    self._filehandle.seek(self._filehandle.tell()-5)
    236                                    _grbmsg = self._filehandle.read(secsize)
    237                                    _grbpos = 0
    238                                    # Unpack Section 3
    239                                    _gds,_gdt,_deflist,_grbpos = g2clib.unpack3(_grbmsg,_grbpos,np.empty)
    240                                    _gds = _gds.tolist()
    241                                    _gdt = _gdt.tolist()
    242                                    section3 = np.concatenate((_gds,_gdt))
    243                                    section3 = np.where(section3==4294967295,-1,section3)
    244                                elif secnum == 4:
    245                                    self._filehandle.seek(self._filehandle.tell()-5)
    246                                    _grbmsg = self._filehandle.read(secsize)
    247                                    _grbpos = 0
    248                                    # Unpack Section 4
    249                                    _numcoord,_pdt,_pdtnum,_coordlist,_grbpos = g2clib.unpack4(_grbmsg,_grbpos,np.empty)
    250                                    _pdt = _pdt.tolist()
    251                                    section4 = np.concatenate((np.array((_numcoord,_pdtnum)),_pdt))
    252                                elif secnum == 5:
    253                                    self._filehandle.seek(self._filehandle.tell()-5)
    254                                    _grbmsg = self._filehandle.read(secsize)
    255                                    _grbpos = 0
    256                                    # Unpack Section 5
    257                                    _drt,_drtn,_npts,self._pos = g2clib.unpack5(_grbmsg,_grbpos,np.empty)
    258                                    section5 = np.concatenate((np.array((_npts,_drtn)),_drt))
    259                                elif secnum == 6:
    260                                    # Unpack Section 6. Not really...just get the flag value.
    261                                    _bmapflag = struct.unpack('>B',self._filehandle.read(1))[0]
    262                                    if _bmapflag == 0:
    263                                        _bmappos = self._filehandle.tell()-6
    264                                    elif _bmapflag == 254:
    265                                        pass # Do this to keep the previous position value
    266                                    else:
    267                                        _bmappos = None
    268                                    self._filehandle.seek(self._filehandle.tell()+secsize-6)
    269                                elif secnum == 7:
    270                                    # Unpack Section 7. No need to read it, just index the position in file.
    271                                    _datapos = self._filehandle.tell()-5
    272                                    _datasize = secsize
    273                                    self._filehandle.seek(self._filehandle.tell()+secsize-5)
    274                                else:
    275                                    self._filehandle.seek(self._filehandle.tell()+secsize-5)
    276                            else:
    277                                if num == 2 and secnum == 3:
    278                                    pass # Allow this.  Just means no Local Use Section.
    279                                else:
    280                                    _issubmessage = True
    281                                    _submsgoffset = (self._filehandle.tell()-5)-(self._index['offset'][-1])
    282                                    _submsgbegin = secnum
    283                                self._filehandle.seek(self._filehandle.tell()-5)
    284                                continue
    285                        trailer = struct.unpack('>4s',self._filehandle.read(4))[0]
    286                        if trailer == b'7777':
    287                            self.messages += 1
    288                            self._index['offset'].append(pos)
    289                            self._index['bitmap_offset'].append(_bmappos)
    290                            self._index['data_offset'].append(_datapos)
    291                            self._index['size'].append(section0[-1])
    292                            self._index['data_size'].append(_datasize)
    293                            self._index['messageNumber'].append(self.messages)
    294                            self._index['isSubmessage'].append(_issubmessage)
    295                            if _issubmessage:
    296                                self._index['submessageOffset'].append(_submsgoffset)
    297                                self._index['submessageBeginSection'].append(_submsgbegin)
    298                            else:
    299                                self._index['submessageOffset'].append(0)
    300                                self._index['submessageBeginSection'].append(_submsgbegin)
    301
    302                            # Create Grib2Message with data.
    303                            msg = Grib2Message(section0,section1,section2,section3,section4,section5,_bmapflag)
    304                            msg._msgnum = self.messages-1
    305                            msg._deflist = _deflist
    306                            msg._coordlist = _coordlist
    307                            if not no_data:
    308                                shape = (msg.ny,msg.nx)
    309                                ndim = 2
    310                                if msg.typeOfValues == 0:
    311                                    dtype = 'float32'
    312                                elif msg.typeOfValues == 1:
    313                                    dtype = 'int32'
    314                                msg._data = Grib2MessageOnDiskArray(shape, ndim, dtype, self._filehandle,
    315                                                                    msg, pos, _bmappos, _datapos)
    316                            self._index['msg'].append(msg)
    317
    318                            break
    319                        else:
    320                            self._filehandle.seek(self._filehandle.tell()-4)
    321                            self.messages += 1
    322                            self._index['offset'].append(pos)
    323                            self._index['bitmap_offset'].append(_bmappos)
    324                            self._index['data_offset'].append(_datapos)
    325                            self._index['size'].append(section0[-1])
    326                            self._index['data_size'].append(_datasize)
    327                            self._index['messageNumber'].append(self.messages)
    328                            self._index['isSubmessage'].append(_issubmessage)
    329                            self._index['submessageOffset'].append(_submsgoffset)
    330                            self._index['submessageBeginSection'].append(_submsgbegin)
    331
    332                            # Create Grib2Message with data.
    333                            msg = Grib2Message(section0,section1,section2,section3,section4,section5,_bmapflag)
    334                            msg._msgnum = self.messages-1
    335                            msg._deflist = _deflist
    336                            msg._coordlist = _coordlist
    337                            if not no_data:
    338                                shape = (msg.ny,msg.nx)
    339                                ndim = 2
    340                                if msg.typeOfValues == 0:
    341                                    dtype = 'float32'
    342                                elif msg.typeOfValues == 1:
    343                                    dtype = 'int32'
    344                                msg._data = Grib2MessageOnDiskArray(shape, ndim, dtype, self._filehandle,
    345                                                                    msg, pos, _bmappos, _datapos)
    346                            self._index['msg'].append(msg)
    347
    348                            continue
    349
    350            except(struct.error):
    351                if 'r' in self.mode:
    352                    self._filehandle.seek(0)
    353                break
    354
    355        # Index at end of _build_index()
    356        if self._hasindex and not no_data:
    357             self.variables = tuple(sorted(set([msg.shortName for msg in self._index['msg']])))
    358             self.levels = tuple(sorted(set([msg.level for msg in self._index['msg']])))
    359
    360
    361    def close(self):
    362        """
    363        Close the file handle
    364        """
    365        if not self._filehandle.closed:
    366            self.messages = 0
    367            self.current_message = 0
    368            self._filehandle.close()
    369            self.closed = self._filehandle.closed
    370
    371
    372    def read(self, size=None):
    373        """
    374        Read size amount of GRIB2 messages from the current position. If no argument is
    375        given, then size is None and all messages are returned from the current position
    376        in the file. This read method follows the behavior of Python's builtin open()
    377        function, but whereas that operates on units of bytes, we operate on units of
    378        GRIB2 messages.
    379
    380        Parameters
    381        ----------
    382
    383        **`size : int, optional`**
    384
    385        The number of GRIB2 messages to read from the current position. If no argument is
    386        give, the default value is `None` and remainder of the file is read.
    387
    388        Returns
    389        -------
    390
    391        `Grib2Message` object when size = 1 or a `list` of Grib2Messages when
    392        size > 1.
    393        """
    394        if size is not None and size < 0:
    395            size = None
    396        if size is None or size > 1:
    397            start = self.tell()
    398            stop = self.messages if size is None else start+size
    399            if size is None:
    400                self.current_message = self.messages-1
    401            else:
    402                self.current_message += size
    403            return self._index['msg'][slice(start,stop,1)]
    404        elif size == 1:
    405            self.current_message += 1
    406            return self._index['msg'][self.current_message]
    407        else:
    408            None
    409
    410
    411    def seek(self, pos):
    412        """
    413        Set the position within the file in units of GRIB2 messages.
    414
    415        Parameters
    416        ----------
    417
    418        **`pos : int`**
    419
    420        The GRIB2 Message number to set the file pointer to.
    421        """
    422        if self._hasindex:
    423            self._filehandle.seek(self._index['offset'][pos])
    424            self.current_message = pos
    425
    426
    427    def tell(self):
    428        """
    429        Returns the position of the file in units of GRIB2 Messages.
    430        """
    431        return self.current_message
    432
    433
    434    def select(self,**kwargs):
    435        """
    436        Select GRIB2 messages by `Grib2Message` attributes.
    437        """
    438        # TODO: Added ability to process multiple values for each keyword (attribute)
    439        idxs = []
    440        nkeys = len(kwargs.keys())
    441        for k,v in kwargs.items():
    442            idxs += [msg._msgnum for msg in self._index['msg'] if getattr(msg,k) == v]
    443        idxs = np.array(idxs,dtype=np.int32)
    444        return [self._index['msg'][i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]
    445
    446
    447    def write(self, msg):
    448        """
    449        Writes GRIB2 message object to file.
    450
    451        Parameters
    452        ----------
    453
    454        **`msg : Grib2Message or sequence of Grib2Messages`**
    455
    456        GRIB2 message objects to write to file.
    457        """
    458        if isinstance(msg,list):
    459            for m in msg:
    460                self.write(m)
    461            return
    462
    463        if issubclass(msg.__class__,_Grib2Message):
    464            if hasattr(msg,'_msg'):
    465                self._filehandle.write(msg._msg)
    466            else:
    467                if msg._signature != msg._generate_signature():
    468                    msg.pack()
    469                    self._filehandle.write(msg._msg)
    470                else:
    471                    if hasattr(msg._data,'filehandle'):
    472                        msg._data.filehandle.seek(msg._data.offset)
    473                        self._filehandle.write(msg._data.filehandle.read(msg.section0[-1]))
    474                    else:
    475                        msg.pack()
    476                        self._filehandle.write(msg._msg)
    477            self.flush()
    478            self.size = os.path.getsize(self.name)
    479            self._filehandle.seek(self.size-msg.section0[-1])
    480            self._build_index()
    481        else:
    482            raise TypeError("msg must be a Grib2Message object.")
    483        return
    484
    485
    486    def flush(self):
    487        """
    488        Flush the file object buffer.
    489        """
    490        self._filehandle.flush()
    491
    492
    493    def levels_by_var(self,name):
    494        """
    495        Return a list of level strings given a variable shortName.
    496
    497        Parameters
    498        ----------
    499
    500        **`name : str`**
    501
    502        Grib2Message variable shortName
    503
    504        Returns
    505        -------
    506
    507        A list of strings of unique level strings.
    508        """
    509        return list(sorted(set([msg.level for msg in self.select(shortName=name)])))
    510
    511
    512    def vars_by_level(self,level):
    513        """
    514        Return a list of variable shortName strings given a level.
    515
    516        Parameters
    517        ----------
    518
    519        **`level : str`**
    520
    521        Grib2Message variable level
    522
    523        Returns
    524        -------
    525
    526        A list of strings of variable shortName strings.
    527        """
    528        return list(sorted(set([msg.shortName for msg in self.select(level=level)])))

GRIB2 File Object. 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.

## Attributes

`mode` File IO mode of opening the file.

`name` Full path name of the GRIB2 file.

`messages` Count of GRIB2 Messages contained in the file.

`current_message` Current position of the file in units of GRIB2 Messages.

`size` Size of the file in units of bytes.

`closed` `True` is file handle is close; `False` otherwise.

`variables` Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).

`levels` Tuple containing a unique list of wgrib2-formatted level/layer strings.

open(filename, mode='r', **kwargs) View Source

     82    def __init__(self, filename, mode='r', **kwargs):
     83        """
     84        `open` Constructor
     85
     86        Parameters
     87        ----------
     88
     89        **`filename : str`**
     90
     91        File name containing GRIB2 messages.
     92
     93        **`mode : str, optional`**
     94
     95        File access mode where `r` opens the files for reading only; `w` opens the file for writing.
     96        """
     97        if mode in {'a','r','w'}:
     98            mode = mode+'b'
     99            if 'w' in mode: mode += '+'
    100        self._filehandle = builtins.open(filename,mode=mode,buffering=ONE_MB)
    101        self._hasindex = False
    102        self._index = {}
    103        self.mode = mode
    104        self.name = os.path.abspath(filename)
    105        self.messages = 0
    106        self.current_message = 0
    107        self.size = os.path.getsize(self.name)
    108        self.closed = self._filehandle.closed
    109        self.levels = None
    110        self.variables = None
    111        if 'r' in self.mode:
    112            try:
    113                self._build_index(no_data=kwargs['_xarray_backend'])
    114            except(KeyError):
    115                self._build_index()
    116        # FIX: Cannot perform reads on mode='a'
    117        #if 'a' in self.mode and self.size > 0: self._build_index()

`open` Constructor

## Parameters

`filename : str`

File name containing GRIB2 messages.

`mode : str, optional`

File access mode where `r` opens the files for reading only; `w` opens the file for writing.

def close(self): View Source

    361    def close(self):
    362        """
    363        Close the file handle
    364        """
    365        if not self._filehandle.closed:
    366            self.messages = 0
    367            self.current_message = 0
    368            self._filehandle.close()
    369            self.closed = self._filehandle.closed

Close the file handle

def read(self, size=None): View Source

    372    def read(self, size=None):
    373        """
    374        Read size amount of GRIB2 messages from the current position. If no argument is
    375        given, then size is None and all messages are returned from the current position
    376        in the file. This read method follows the behavior of Python's builtin open()
    377        function, but whereas that operates on units of bytes, we operate on units of
    378        GRIB2 messages.
    379
    380        Parameters
    381        ----------
    382
    383        **`size : int, optional`**
    384
    385        The number of GRIB2 messages to read from the current position. If no argument is
    386        give, the default value is `None` and remainder of the file is read.
    387
    388        Returns
    389        -------
    390
    391        `Grib2Message` object when size = 1 or a `list` of Grib2Messages when
    392        size > 1.
    393        """
    394        if size is not None and size < 0:
    395            size = None
    396        if size is None or size > 1:
    397            start = self.tell()
    398            stop = self.messages if size is None else start+size
    399            if size is None:
    400                self.current_message = self.messages-1
    401            else:
    402                self.current_message += size
    403            return self._index['msg'][slice(start,stop,1)]
    404        elif size == 1:
    405            self.current_message += 1
    406            return self._index['msg'][self.current_message]
    407        else:
    408            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 : int, optional`

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

`Grib2Message` object when size = 1 or a `list` of Grib2Messages when size > 1.

def seek(self, pos): View Source

    411    def seek(self, pos):
    412        """
    413        Set the position within the file in units of GRIB2 messages.
    414
    415        Parameters
    416        ----------
    417
    418        **`pos : int`**
    419
    420        The GRIB2 Message number to set the file pointer to.
    421        """
    422        if self._hasindex:
    423            self._filehandle.seek(self._index['offset'][pos])
    424            self.current_message = pos

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

## Parameters

`pos : int`

The GRIB2 Message number to set the file pointer to.

def tell(self): View Source

    427    def tell(self):
    428        """
    429        Returns the position of the file in units of GRIB2 Messages.
    430        """
    431        return self.current_message

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

def select(self, **kwargs): View Source

    434    def select(self,**kwargs):
    435        """
    436        Select GRIB2 messages by `Grib2Message` attributes.
    437        """
    438        # TODO: Added ability to process multiple values for each keyword (attribute)
    439        idxs = []
    440        nkeys = len(kwargs.keys())
    441        for k,v in kwargs.items():
    442            idxs += [msg._msgnum for msg in self._index['msg'] if getattr(msg,k) == v]
    443        idxs = np.array(idxs,dtype=np.int32)
    444        return [self._index['msg'][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): View Source

    447    def write(self, msg):
    448        """
    449        Writes GRIB2 message object to file.
    450
    451        Parameters
    452        ----------
    453
    454        **`msg : Grib2Message or sequence of Grib2Messages`**
    455
    456        GRIB2 message objects to write to file.
    457        """
    458        if isinstance(msg,list):
    459            for m in msg:
    460                self.write(m)
    461            return
    462
    463        if issubclass(msg.__class__,_Grib2Message):
    464            if hasattr(msg,'_msg'):
    465                self._filehandle.write(msg._msg)
    466            else:
    467                if msg._signature != msg._generate_signature():
    468                    msg.pack()
    469                    self._filehandle.write(msg._msg)
    470                else:
    471                    if hasattr(msg._data,'filehandle'):
    472                        msg._data.filehandle.seek(msg._data.offset)
    473                        self._filehandle.write(msg._data.filehandle.read(msg.section0[-1]))
    474                    else:
    475                        msg.pack()
    476                        self._filehandle.write(msg._msg)
    477            self.flush()
    478            self.size = os.path.getsize(self.name)
    479            self._filehandle.seek(self.size-msg.section0[-1])
    480            self._build_index()
    481        else:
    482            raise TypeError("msg must be a Grib2Message object.")
    483        return

Writes GRIB2 message object to file.

## Parameters

`msg : Grib2Message or sequence of Grib2Messages`

GRIB2 message objects to write to file.

def flush(self): View Source

    486    def flush(self):
    487        """
    488        Flush the file object buffer.
    489        """
    490        self._filehandle.flush()

Flush the file object buffer.

def levels_by_var(self, name): View Source

    493    def levels_by_var(self,name):
    494        """
    495        Return a list of level strings given a variable shortName.
    496
    497        Parameters
    498        ----------
    499
    500        **`name : str`**
    501
    502        Grib2Message variable shortName
    503
    504        Returns
    505        -------
    506
    507        A list of strings of unique level strings.
    508        """
    509        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 : str`

Grib2Message variable shortName

## Returns

A list of strings of unique level strings.

def vars_by_level(self, level): View Source

    512    def vars_by_level(self,level):
    513        """
    514        Return a list of variable shortName strings given a level.
    515
    516        Parameters
    517        ----------
    518
    519        **`level : str`**
    520
    521        Grib2Message variable level
    522
    523        Returns
    524        -------
    525
    526        A list of strings of variable shortName strings.
    527        """
    528        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 : str`

Grib2Message variable level

## Returns

A list of strings of variable shortName strings.

class Grib2Message: View Source

    531class Grib2Message:
    532    """
    533    """
    534    def __new__(self, section0: np.array = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]),
    535                      section1: np.array = np.zeros((13),dtype=np.int64),
    536                      section2: bytes = None,
    537                      section3: np.array = None,
    538                      section4: np.array = None,
    539                      section5: np.array = None, *args, **kwargs):
    540
    541        bases = list()
    542        if section3 is None:
    543            if 'gdtn' in kwargs.keys():
    544                gdtn = kwargs['gdtn']
    545                Gdt = templates.gdt_class_by_gdtn(gdtn)
    546                bases.append(Gdt)
    547                section3 = np.zeros((Gdt._len+5),dtype=np.int64)
    548            else:
    549                raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array")
    550        else:
    551            gdtn = section3[4]
    552            Gdt = templates.gdt_class_by_gdtn(gdtn)
    553            bases.append(Gdt)
    554
    555        if section4 is None:
    556            if 'pdtn' in kwargs.keys():
    557                pdtn = kwargs['pdtn']
    558                Pdt = templates.pdt_class_by_pdtn(pdtn)
    559                bases.append(Pdt)
    560                section4 = np.zeros((Pdt._len+2),dtype=np.int64)
    561            else:
    562                raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array")
    563        else:
    564            pdtn = section4[1]
    565            Pdt = templates.pdt_class_by_pdtn(pdtn)
    566            bases.append(Pdt)
    567
    568        if section5 is None:
    569            if 'drtn' in kwargs.keys():
    570                drtn = kwargs['drtn']
    571                Drt = templates.drt_class_by_drtn(drtn)
    572                bases.append(Drt)
    573                section5 = np.zeros((Drt._len+2),dtype=np.int64)
    574            else:
    575                raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array")
    576        else:
    577            drtn = section5[1]
    578            Drt = templates.drt_class_by_drtn(drtn)
    579            bases.append(Drt)
    580
    581        # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo
    582        try:
    583            Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"]
    584        except KeyError:
    585            @dataclass(init=False, repr=False)
    586            class Msg(_Grib2Message, *bases):
    587                pass
    588            _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg
    589
    590
    591
    592        return Msg(section0, section1, section2, section3, section4, section5, *args)

Grib2Message()
def show_config(): View Source

    14def show_config():
    15    """
    16    Print grib2io build configuration information.
    17    """
    18    from g2clib import __version__ as g2clib_version
    19    from g2clib import _has_png as have_png
    20    from g2clib import _has_jpeg as have_jpeg
    21    print("grib2io version %s Configuration:\n"%(__version__))
    22    print("\tg2c library version:".expandtabs(4),g2clib_version)
    23    print("\tJPEG compression support:".expandtabs(4),bool(have_jpeg))
    24    print("\tPNG compression support:".expandtabs(4),bool(have_png))

Print grib2io build configuration information.

def interpolate(a, method, grid_def_in, grid_def_out, method_options=None): View Source

    1224def interpolate(a, method, grid_def_in, grid_def_out, method_options=None):
    1225    """
    1226    Perform grid spatial interpolation via the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip).
    1227
    1228    Parameters
    1229    ----------
    1230
    1231    **`a : numpy.ndarray`**
    1232
    1233    Array data to interpolate from. These data are expected to be in
    1234    2-dimensional form with shape (ny, nx) or 3-dimensional where the
    1235    3rd dimension represents another spatial, temporal, or classification
    1236    (i.e. ensemble members) dimension. The function will properly flatten
    1237    the array that is acceptable for the NCEPLIBS-ip interpolation
    1238    subroutines.
    1239
    1240    **`method : int or str`**
    1241
    1242    Interpolate method to use. This can either be an integer or string using
    1243    the following mapping:
    1244
    1245    | Interpolate Scheme | Integer Value |
    1246    | :---:              | :---:         |
    1247    | 'bilinear'         | 0             |
    1248    | 'bicubic'          | 1             |
    1249    | 'neighbor'         | 2             |
    1250    | 'budget'           | 3             |
    1251    | 'spectral'         | 4             |
    1252    | 'neighbor-budget'  | 6             |
    1253
    1254    **`grid_def_in : grib2io.Grib2GridDef`**
    1255
    1256    Grib2GridDef object of the input grid.
    1257
    1258    **`grid_def_out : grib2io.Grib2GridDef`**
    1259
    1260    Grib2GridDef object of the output grid.
    1261
    1262    **`method_options : list of ints, optional`**
    1263
    1264    Interpolation options. See the NCEPLIBS-ip doucmentation for
    1265    more information on how these are used.
    1266    """
    1267    from . import _interpolate
    1268
    1269    interp_schemes = {'bilinear':0, 'bicubic':1, 'neighbor':2,
    1270                      'budget':3, 'spectral':4, 'neighbor-budget':6}
    1271
    1272    if isinstance(method,int) and method not in interp_schemes.values():
    1273        raise ValueError('Invalid interpolation method.')
    1274    elif isinstance(method,str):
    1275        if method in interp_schemes.keys():
    1276            method = interp_schemes[method]
    1277        else:
    1278            raise ValueError('Invalid interpolation method.')
    1279
    1280    if method_options is None:
    1281        method_options = np.zeros((20),dtype=np.int32)
    1282        if method == 3:
    1283            method_options[0:2] = -1
    1284
    1285    ni = grid_def_in.nx*grid_def_in.ny
    1286    no = grid_def_out.nx*grid_def_out.ny
    1287
    1288    if len(a.shape) == 2 and a.shape == (grid_def_in.ny,grid_def_in.nx):
    1289        newshp = (grid_def_out.ny,grid_def_out.nx)
    1290        a = np.expand_dims(a.flatten(),axis=0)
    1291    elif len(a.shape) == 3 and a.shape[-2:] == (grid_def_in.ny,grid_def_in.nx):
    1292        newshp = (a.shape[0],grid_def_out.ny,grid_def_out.nx)
    1293        a = a.reshape(*a.shape[:-2],-1)
    1294    else:
    1295        raise ValueError("Array shape must be either (ny,nx) or (:,ny,nx).")
    1296
    1297    ibi = np.zeros((a.shape[0]),dtype=np.int32)
    1298    li = np.zeros(a.shape,dtype=np.int32)
    1299    go = np.zeros((a.shape[0],grid_def_out.ny*grid_def_out.nx),dtype=np.float32)
    1300
    1301    no,ibo,lo,iret = _interpolate.interpolate(method,method_options,
    1302                                              grid_def_in.gdtn,grid_def_in.gdt,
    1303                                              grid_def_out.gdtn,grid_def_out.gdt,
    1304                                              ibi,li.T,a.T,go.T)
    1305
    1306    return go.reshape(newshp)

Perform grid spatial interpolation via the NCEPLIBS-ip library.

## Parameters

`a : numpy.ndarray`

Array data to interpolate from. These data are expected to be in 2-dimensional form with shape (ny, nx) or 3-dimensional where the 3rd dimension represents another spatial, temporal, or classification (i.e. ensemble members) dimension. The function will properly flatten the array that is acceptable for the NCEPLIBS-ip interpolation subroutines.

`method : int or str`

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 of the input grid.

`grid_def_out : grib2io.Grib2GridDef`

Grib2GridDef object of the output grid.

`method_options : list of ints, optional`

Interpolation options. See the NCEPLIBS-ip doucmentation for more information on how these are used.

@dataclass
class Grib2GridDef: View Source

    1309@dataclass
    1310class Grib2GridDef:
    1311    """
    1312    Class to hold GRIB2 Grid Definition Template Number and Template as
    1313    class attributes. This allows for cleaner looking code when passing these
    1314    metadata around.  For example, the `grib2io._Grib2Message.interpolate`
    1315    method and `grib2io.interpolate` function accepts these objects.
    1316    """
    1317    gdtn: int
    1318    gdt: np.array
    1319
    1320    @classmethod
    1321    def from_section3(cls, section3):
    1322        return cls(section3[4],section3[5:])
    1323
    1324    @property
    1325    def nx(self):
    1326        return self.gdt[7]
    1327
    1328    @property
    1329    def ny(self):
    1330        return self.gdt[8]
    1331
    1332    @property
    1333    def npoints(self):
    1334        return self.gdt[7] * self.gdt[8]

Class to hold GRIB2 Grid Definition Template Number and Template as class 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: <built-in function array>)
@classmethod
def from_section3(cls, section3): View Source

    1320    @classmethod
    1321    def from_section3(cls, section3):
    1322        return cls(section3[4],section3[5:])