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.

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']
 8
 9try:
10    from . import __config__
11    __version__ = __config__.grib2io_version
12except(ImportError):
13    pass
14
15from .g2clib import __version__ as __g2clib_version__
16from .g2clib import _has_jpeg
17from .g2clib import _has_png
18from .g2clib import _has_aec
19
20has_jpeg_support = bool(_has_jpeg)
21has_png_support  = bool(_has_png)
22has_aec_support = bool(_has_aec)
23
24from .tables.originating_centers import _ncep_grib2_table_version
25ncep_grib2_table_version = _ncep_grib2_table_version
26g2c_version = __g2clib_version__
27
28def show_config():
29    """Print grib2io build configuration information."""
30    print(f'grib2io version {__version__} Configuration:\n')
31    print(f'\tg2c library version: {__g2clib_version__}')
32    print(f'\tJPEG compression support: {has_jpeg_support}')
33    print(f'\tPNG compression support: {has_png_support}')
34    print(f'\tAEC compression support: {has_aec_support}')
35    print(f'')
36    print(f'\tNCEP GRIB2 Table Version: {_ncep_grib2_table_version}')
class open:
 60class open():
 61    """
 62    GRIB2 File Object.
 63
 64    A physical file can contain one or more GRIB2 messages.  When instantiated,
 65    class `grib2io.open`, the file named `filename` is opened for reading (`mode
 66    = 'r'`) and is automatically indexed.  The indexing procedure reads some of
 67    the GRIB2 metadata for all GRIB2 Messages.  A GRIB2 Message may contain
 68    submessages whereby Section 2-7 can be repeated.  grib2io accommodates for
 69    this by flattening any GRIB2 submessages into multiple individual messages.
 70
 71    It is important to note that GRIB2 files from some Meteorological agencies
 72    contain other data than GRIB2 messages.  GRIB2 files from ECMWF can contain
 73    GRIB1 and GRIB2 messages.  grib2io checks for these and safely ignores them.
 74
 75    Attributes
 76    ----------
 77    closed : bool
 78        `True` is file handle is close; `False` otherwise.
 79    current_message : int
 80        Current position of the file in units of GRIB2 Messages.
 81    levels : tuple
 82        Tuple containing a unique list of wgrib2-formatted level/layer strings.
 83    messages : int
 84        Count of GRIB2 Messages contained in the file.
 85    mode : str
 86        File IO mode of opening the file.
 87    name : str
 88        Full path name of the GRIB2 file.
 89    size : int
 90        Size of the file in units of bytes.
 91    variables : tuple
 92        Tuple containing a unique list of variable short names (i.e. GRIB2
 93        abbreviation names).
 94    """
 95    __slots__ = ('_fileid', '_filehandle', '_hasindex', '_index', '_nodata',
 96                 '_pos', 'closed', 'current_message', 'messages', 'mode',
 97                 'name', 'size')
 98    def __init__(self, filename: str, mode: str="r", **kwargs):
 99        """
100        Initialize GRIB2 File object instance.
101
102        Parameters
103        ----------
104        filename
105            File name containing GRIB2 messages.
106        mode: default="r"
107            File access mode where "r" opens the files for reading only; "w"
108            opens the file for writing.
109        """
110        # Manage keywords
111        if "_xarray_backend" not in kwargs:
112            kwargs["_xarray_backend"] = False
113            self._nodata = False
114        else:
115            self._nodata = kwargs["_xarray_backend"]
116        if mode in {'a','r','w'}:
117            mode = mode+'b'
118            if 'w' in mode: mode += '+'
119            if 'a' in mode: mode += '+'
120
121        # Some GRIB2 files are gzipped, so check for that here, but
122        # raise error when using xarray backend.
123        if 'r' in mode:
124            self._filehandle = builtins.open(filename, mode=mode)
125            # Gzip files contain a 2-byte header b'\x1f\x8b'.
126            if self._filehandle.read(2) == b'\x1f\x8b':
127                self._filehandle.close()
128                if '_xarray_backend' in kwargs.keys():
129                    raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.')
130                import gzip
131                self._filehandle = gzip.open(filename, mode=mode)
132            else:
133                self._filehandle = builtins.open(filename, mode=mode)
134        else:
135            self._filehandle = builtins.open(filename, mode=mode)
136
137        self.name = os.path.abspath(filename)
138        fstat = os.stat(self.name)
139        self._hasindex = False
140        self._index = {}
141        self.mode = mode
142        self.messages = 0
143        self.current_message = 0
144        self.size = fstat.st_size
145        self.closed = self._filehandle.closed
146        self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+
147                                     str(self.size)).encode('ASCII')).hexdigest()
148        if 'r' in self.mode:
149            self._build_index()
150        # FIX: Cannot perform reads on mode='a'
151        #if 'a' in self.mode and self.size > 0: self._build_index()
152
153
154    def __delete__(self, instance):
155        self.close()
156        del self._index
157
158
159    def __enter__(self):
160        return self
161
162
163    def __exit__(self, atype, value, traceback):
164        self.close()
165
166
167    def __iter__(self):
168        yield from self._index['msg']
169
170
171    def __len__(self):
172        return self.messages
173
174
175    def __repr__(self):
176        strings = []
177        for k in self.__slots__:
178            if k.startswith('_'): continue
179            strings.append('%s = %s\n'%(k,eval('self.'+k)))
180        return ''.join(strings)
181
182
183    def __getitem__(self, key):
184        if isinstance(key,int):
185            if abs(key) >= len(self._index['msg']):
186                raise IndexError("index out of range")
187            else:
188                return self._index['msg'][key]
189        elif isinstance(key,str):
190            return self.select(shortName=key)
191        elif isinstance(key,slice):
192            return self._index['msg'][key]
193        else:
194            raise KeyError('Key must be an integer, slice, or GRIB2 variable shortName.')
195
196
197    def _build_index(self):
198        """Perform indexing of GRIB2 Messages."""
199        # Initialize index dictionary
200        if not self._hasindex:
201            self._index['sectionOffset'] = []
202            self._index['sectionSize'] = []
203            self._index['msgSize'] = []
204            self._index['msgNumber'] = []
205            self._index['msg'] = []
206            self._index['isSubmessage'] = []
207            self._hasindex = True
208
209        # Iterate
210        while True:
211            try:
212                # Initialize
213                bmapflag = None
214                pos = self._filehandle.tell()
215                section2 = b''
216                trailer = b''
217                _secpos = dict.fromkeys(range(8))
218                _secsize = dict.fromkeys(range(8))
219                _isSubmessage = False
220
221                # Ignore headers (usually text) that are not part of the GRIB2
222                # file.  For example, NAVGEM files have a http header at the
223                # beginning that needs to be ignored.
224
225                # Read a byte at a time until "GRIB" is found.  Using
226                # "wgrib2" on a NAVGEM file, the header was 421 bytes and
227                # decided to go to 2048 bytes to be safe. For normal GRIB2
228                # files this should be quick and break out of the first
229                # loop when "test_offset" is 0.
230                for test_offset in range(2048):
231                    self._filehandle.seek(pos + test_offset)
232                    header = struct.unpack(">i", self._filehandle.read(4))[0]
233                    if header.to_bytes(4, "big") == b"GRIB":
234                        pos = pos + test_offset
235                        break
236
237                # Read the rest of Section 0 using struct.
238                _secpos[0] = self._filehandle.tell()-4
239                _secsize[0] = 16
240                secmsg = self._filehandle.read(12)
241                section0 = np.concatenate(([header],list(struct.unpack('>HBBQ',secmsg))),dtype=np.int64)
242
243                # Make sure message is GRIB2.
244                if section0[3] != 2:
245                    # Check for GRIB1 and ignore.
246                    if secmsg[3] == 1:
247                        warnings.warn("GRIB version 1 message detected.  Ignoring...")
248                        grib1_size = int.from_bytes(secmsg[:3],"big")
249                        self._filehandle.seek(self._filehandle.tell()+grib1_size-16)
250                        continue
251                    else:
252                        raise ValueError("Bad GRIB version number.")
253
254                # Read and unpack sections 1 through 8 which all follow a
255                # pattern of section size, number, and content.
256                while 1:
257                    # Read first 5 bytes of the section which contains the size
258                    # of the section (4 bytes) and the section number (1 byte).
259                    secmsg = self._filehandle.read(5)
260                    secsize, secnum = struct.unpack('>iB',secmsg)
261
262                    # Record the offset of the section number and "append" the
263                    # rest of the section to secmsg.
264                    _secpos[secnum] = self._filehandle.tell()-5
265                    _secsize[secnum] = secsize
266                    if secnum in {1,3,4,5}:
267                        secmsg += self._filehandle.read(secsize-5)
268                    grbpos = 0
269
270                    # Unpack section
271                    if secnum == 1:
272                        # Unpack Section 1
273                        section1, grbpos = g2clib.unpack1(secmsg,grbpos,np.empty)
274                    elif secnum == 2:
275                        # Unpack Section 2
276                        section2 = self._filehandle.read(secsize-5)
277                    elif secnum == 3:
278                        # Unpack Section 3
279                        gds, gdt, deflist, grbpos = g2clib.unpack3(secmsg,grbpos,np.empty)
280                        gds = gds.tolist()
281                        gdt = gdt.tolist()
282                        section3 = np.concatenate((gds,gdt))
283                        section3 = np.where(section3==4294967295,-1,section3)
284                    elif secnum == 4:
285                        # Unpack Section 4
286                        numcoord, pdt, pdtnum, coordlist, grbpos = g2clib.unpack4(secmsg,grbpos,np.empty)
287                        pdt = pdt.tolist()
288                        section4 = np.concatenate((np.array((numcoord,pdtnum)),pdt))
289                    elif secnum == 5:
290                        # Unpack Section 5
291                        drt, drtn, npts, self._pos = g2clib.unpack5(secmsg,grbpos,np.empty)
292                        section5 = np.concatenate((np.array((npts,drtn)),drt))
293                        section5 = np.where(section5==4294967295,-1,section5)
294                    elif secnum == 6:
295                        # Unpack Section 6 - Just the bitmap flag
296                        bmapflag = struct.unpack('>B',self._filehandle.read(1))[0]
297                        if bmapflag == 0:
298                            bmappos = self._filehandle.tell()-6
299                        elif bmapflag == 254:
300                            # Do this to keep the previous position value
301                            pass
302                        else:
303                            bmappos = None
304                        self._filehandle.seek(self._filehandle.tell()+secsize-6)
305                    elif secnum == 7:
306                        # Do not unpack section 7 here, but move the file pointer
307                        # to after section 7.
308                        self._filehandle.seek(self._filehandle.tell()+secsize-5)
309
310                        # Update the file index.
311                        self.messages += 1
312                        self._index['sectionOffset'].append(copy.deepcopy(_secpos))
313                        self._index['sectionSize'].append(copy.deepcopy(_secsize))
314                        self._index['msgSize'].append(section0[-1])
315                        self._index['msgNumber'].append(self.messages)
316                        self._index['isSubmessage'].append(_isSubmessage)
317
318                        # Create Grib2Message with data.
319                        msg = Grib2Message(section0,section1,section2,section3,section4,section5,bmapflag)
320                        msg._msgnum = self.messages-1
321                        msg._deflist = deflist
322                        msg._coordlist = coordlist
323                        if not self._nodata:
324                            msg._data = Grib2MessageOnDiskArray((msg.ny,msg.nx), 2,
325                                                                TYPE_OF_VALUES_DTYPE[msg.typeOfValues],
326                                                                self._filehandle,
327                                                                msg, pos, _secpos[6], _secpos[7])
328                        self._index['msg'].append(msg)
329
330                        # If here, then we have moved through GRIB2 section 1-7.
331                        # Now we need to check the next 4 bytes after section 7.
332                        trailer = struct.unpack('>i',self._filehandle.read(4))[0]
333
334                        # If we reach the GRIB2 trailer string ('7777'), then we
335                        # can break begin processing the next GRIB2 message.  If
336                        # not, then we continue within the same iteration to
337                        # process a GRIB2 submessage.
338                        if trailer.to_bytes(4, "big") == b'7777':
339                            break
340                        else:
341                            # If here, trailer should be the size of the first
342                            # section of the next submessage, then the next byte
343                            # is the section number.  Check this value.
344                            nextsec = struct.unpack('>B',self._filehandle.read(1))[0]
345                            if nextsec not in {2,3,4}:
346                                raise ValueError("Bad GRIB2 message structure.")
347                            self._filehandle.seek(self._filehandle.tell()-5)
348                            _isSubmessage = True
349                            continue
350                    else:
351                        raise ValueError("Bad GRIB2 section number.")
352
353            except(struct.error):
354                if 'r' in self.mode:
355                    self._filehandle.seek(0)
356                break
357
358
359    @property
360    def levels(self):
361        if self._hasindex and not self._nodata:
362            return tuple(sorted(set([msg.level for msg in self._index['msg']])))
363        else:
364            return None
365
366
367    @property
368    def variables(self):
369        if self._hasindex and not self._nodata:
370            return tuple(sorted(set([msg.shortName for msg in self._index['msg']])))
371        else:
372            return None
373
374
375    def close(self):
376        """Close the file handle."""
377        if not self._filehandle.closed:
378            self.messages = 0
379            self.current_message = 0
380            self._filehandle.close()
381            self.closed = self._filehandle.closed
382
383
384    def read(self, size: Optional[int]=None):
385        """
386        Read size amount of GRIB2 messages from the current position.
387
388        If no argument is given, then size is None and all messages are returned
389        from the current position in the file. This read method follows the
390        behavior of Python's builtin open() function, but whereas that operates
391        on units of bytes, we operate on units of GRIB2 messages.
392
393        Parameters
394        ----------
395        size: default=None
396            The number of GRIB2 messages to read from the current position. If
397            no argument is give, the default value is None and remainder of
398            the file is read.
399
400        Returns
401        -------
402        read
403            ``Grib2Message`` object when size = 1 or a list of Grib2Messages
404            when size > 1.
405        """
406        if size is not None and size < 0:
407            size = None
408        if size is None or size > 1:
409            start = self.tell()
410            stop = self.messages if size is None else start+size
411            if size is None:
412                self.current_message = self.messages-1
413            else:
414                self.current_message += size
415            return self._index['msg'][slice(start,stop,1)]
416        elif size == 1:
417            self.current_message += 1
418            return self._index['msg'][self.current_message]
419        else:
420            None
421
422
423    def seek(self, pos: int):
424        """
425        Set the position within the file in units of GRIB2 messages.
426
427        Parameters
428        ----------
429        pos
430            The GRIB2 Message number to set the file pointer to.
431        """
432        if self._hasindex:
433            self._filehandle.seek(self._index['sectionOffset'][0][pos])
434            self.current_message = pos
435
436
437    def tell(self):
438        """Returns the position of the file in units of GRIB2 Messages."""
439        return self.current_message
440
441
442    def select(self, **kwargs):
443        """Select GRIB2 messages by `Grib2Message` attributes."""
444        # TODO: Added ability to process multiple values for each keyword (attribute)
445        idxs = []
446        nkeys = len(kwargs.keys())
447        for k,v in kwargs.items():
448            for m in self._index['msg']:
449                if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum)
450        idxs = np.array(idxs,dtype=np.int32)
451        return [self._index['msg'][i] for i in [ii[0] for ii in collections.Counter(idxs).most_common() if ii[1] == nkeys]]
452
453
454    def write(self, msg):
455        """
456        Writes GRIB2 message object to file.
457
458        Parameters
459        ----------
460        msg
461            GRIB2 message objects to write to file.
462        """
463        if isinstance(msg,list):
464            for m in msg:
465                self.write(m)
466            return
467
468        if issubclass(msg.__class__,_Grib2Message):
469            if hasattr(msg,'_msg'):
470                self._filehandle.write(msg._msg)
471            else:
472                if msg._signature != msg._generate_signature():
473                    msg.pack()
474                    self._filehandle.write(msg._msg)
475                else:
476                    if hasattr(msg._data,'filehandle'):
477                        msg._data.filehandle.seek(msg._data.offset)
478                        self._filehandle.write(msg._data.filehandle.read(msg.section0[-1]))
479                    else:
480                        msg.pack()
481                        self._filehandle.write(msg._msg)
482            self.flush()
483            self.size = os.path.getsize(self.name)
484            self._filehandle.seek(self.size-msg.section0[-1])
485            self._build_index()
486        else:
487            raise TypeError("msg must be a Grib2Message object.")
488        return
489
490
491    def flush(self):
492        """Flush the file object buffer."""
493        self._filehandle.flush()
494
495
496    def levels_by_var(self, name: str):
497        """
498        Return a list of level strings given a variable shortName.
499
500        Parameters
501        ----------
502        name
503            Grib2Message variable shortName
504
505        Returns
506        -------
507        levels_by_var
508            A list of unique level strings.
509        """
510        return list(sorted(set([msg.level for msg in self.select(shortName=name)])))
511
512
513    def vars_by_level(self, level: str):
514        """
515        Return a list of variable shortName strings given a level.
516
517        Parameters
518        ----------
519        level
520            Grib2Message variable level
521
522        Returns
523        -------
524        vars_by_level
525            A list of unique variable shortName strings.
526        """
527        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.

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.
  • 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.
  • size (int): Size of the file in units of bytes.
  • variables (tuple): Tuple containing a unique list of variable short names (i.e. GRIB2 abbreviation names).
open(filename: str, mode: str = 'r', **kwargs)
 98    def __init__(self, filename: str, mode: str="r", **kwargs):
 99        """
100        Initialize GRIB2 File object instance.
101
102        Parameters
103        ----------
104        filename
105            File name containing GRIB2 messages.
106        mode: default="r"
107            File access mode where "r" opens the files for reading only; "w"
108            opens the file for writing.
109        """
110        # Manage keywords
111        if "_xarray_backend" not in kwargs:
112            kwargs["_xarray_backend"] = False
113            self._nodata = False
114        else:
115            self._nodata = kwargs["_xarray_backend"]
116        if mode in {'a','r','w'}:
117            mode = mode+'b'
118            if 'w' in mode: mode += '+'
119            if 'a' in mode: mode += '+'
120
121        # Some GRIB2 files are gzipped, so check for that here, but
122        # raise error when using xarray backend.
123        if 'r' in mode:
124            self._filehandle = builtins.open(filename, mode=mode)
125            # Gzip files contain a 2-byte header b'\x1f\x8b'.
126            if self._filehandle.read(2) == b'\x1f\x8b':
127                self._filehandle.close()
128                if '_xarray_backend' in kwargs.keys():
129                    raise RuntimeError('Gzip GRIB2 files are not supported by the Xarray backend.')
130                import gzip
131                self._filehandle = gzip.open(filename, mode=mode)
132            else:
133                self._filehandle = builtins.open(filename, mode=mode)
134        else:
135            self._filehandle = builtins.open(filename, mode=mode)
136
137        self.name = os.path.abspath(filename)
138        fstat = os.stat(self.name)
139        self._hasindex = False
140        self._index = {}
141        self.mode = mode
142        self.messages = 0
143        self.current_message = 0
144        self.size = fstat.st_size
145        self.closed = self._filehandle.closed
146        self._fileid = hashlib.sha1((self.name+str(fstat.st_ino)+
147                                     str(self.size)).encode('ASCII')).hexdigest()
148        if 'r' in self.mode:
149            self._build_index()
150        # FIX: Cannot perform reads on mode='a'
151        #if 'a' in self.mode and self.size > 0: self._build_index()

Initialize GRIB2 File object instance.

Parameters
  • filename: File name containing GRIB2 messages.
  • mode (default="r"): File access mode where "r" opens the files for reading only; "w" opens the file for writing.
name
mode
messages
current_message
size
closed
levels
359    @property
360    def levels(self):
361        if self._hasindex and not self._nodata:
362            return tuple(sorted(set([msg.level for msg in self._index['msg']])))
363        else:
364            return None
variables
367    @property
368    def variables(self):
369        if self._hasindex and not self._nodata:
370            return tuple(sorted(set([msg.shortName for msg in self._index['msg']])))
371        else:
372            return None
def close(self):
375    def close(self):
376        """Close the file handle."""
377        if not self._filehandle.closed:
378            self.messages = 0
379            self.current_message = 0
380            self._filehandle.close()
381            self.closed = self._filehandle.closed

Close the file handle.

def read(self, size: Optional[int] = None):
384    def read(self, size: Optional[int]=None):
385        """
386        Read size amount of GRIB2 messages from the current position.
387
388        If no argument is given, then size is None and all messages are returned
389        from the current position in the file. This read method follows the
390        behavior of Python's builtin open() function, but whereas that operates
391        on units of bytes, we operate on units of GRIB2 messages.
392
393        Parameters
394        ----------
395        size: default=None
396            The number of GRIB2 messages to read from the current position. If
397            no argument is give, the default value is None and remainder of
398            the file is read.
399
400        Returns
401        -------
402        read
403            ``Grib2Message`` object when size = 1 or a list of Grib2Messages
404            when size > 1.
405        """
406        if size is not None and size < 0:
407            size = None
408        if size is None or size > 1:
409            start = self.tell()
410            stop = self.messages if size is None else start+size
411            if size is None:
412                self.current_message = self.messages-1
413            else:
414                self.current_message += size
415            return self._index['msg'][slice(start,stop,1)]
416        elif size == 1:
417            self.current_message += 1
418            return self._index['msg'][self.current_message]
419        else:
420            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):
423    def seek(self, pos: int):
424        """
425        Set the position within the file in units of GRIB2 messages.
426
427        Parameters
428        ----------
429        pos
430            The GRIB2 Message number to set the file pointer to.
431        """
432        if self._hasindex:
433            self._filehandle.seek(self._index['sectionOffset'][0][pos])
434            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):
437    def tell(self):
438        """Returns the position of the file in units of GRIB2 Messages."""
439        return self.current_message

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

def select(self, **kwargs):
442    def select(self, **kwargs):
443        """Select GRIB2 messages by `Grib2Message` attributes."""
444        # TODO: Added ability to process multiple values for each keyword (attribute)
445        idxs = []
446        nkeys = len(kwargs.keys())
447        for k,v in kwargs.items():
448            for m in self._index['msg']:
449                if hasattr(m,k) and getattr(m,k) == v: idxs.append(m._msgnum)
450        idxs = np.array(idxs,dtype=np.int32)
451        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):
454    def write(self, msg):
455        """
456        Writes GRIB2 message object to file.
457
458        Parameters
459        ----------
460        msg
461            GRIB2 message objects to write to file.
462        """
463        if isinstance(msg,list):
464            for m in msg:
465                self.write(m)
466            return
467
468        if issubclass(msg.__class__,_Grib2Message):
469            if hasattr(msg,'_msg'):
470                self._filehandle.write(msg._msg)
471            else:
472                if msg._signature != msg._generate_signature():
473                    msg.pack()
474                    self._filehandle.write(msg._msg)
475                else:
476                    if hasattr(msg._data,'filehandle'):
477                        msg._data.filehandle.seek(msg._data.offset)
478                        self._filehandle.write(msg._data.filehandle.read(msg.section0[-1]))
479                    else:
480                        msg.pack()
481                        self._filehandle.write(msg._msg)
482            self.flush()
483            self.size = os.path.getsize(self.name)
484            self._filehandle.seek(self.size-msg.section0[-1])
485            self._build_index()
486        else:
487            raise TypeError("msg must be a Grib2Message object.")
488        return

Writes GRIB2 message object to file.

Parameters
  • msg: GRIB2 message objects to write to file.
def flush(self):
491    def flush(self):
492        """Flush the file object buffer."""
493        self._filehandle.flush()

Flush the file object buffer.

def levels_by_var(self, name: str):
496    def levels_by_var(self, name: str):
497        """
498        Return a list of level strings given a variable shortName.
499
500        Parameters
501        ----------
502        name
503            Grib2Message variable shortName
504
505        Returns
506        -------
507        levels_by_var
508            A list of unique level strings.
509        """
510        return list(sorted(set([msg.level for msg in self.select(shortName=name)])))

Return a list of level strings given a variable shortName.

Parameters
  • name: Grib2Message variable shortName
Returns
  • levels_by_var: A list of unique level strings.
def vars_by_level(self, level: str):
513    def vars_by_level(self, level: str):
514        """
515        Return a list of variable shortName strings given a level.
516
517        Parameters
518        ----------
519        level
520            Grib2Message variable level
521
522        Returns
523        -------
524        vars_by_level
525            A list of unique variable shortName strings.
526        """
527        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.
def show_config():
29def show_config():
30    """Print grib2io build configuration information."""
31    print(f'grib2io version {__version__} Configuration:\n')
32    print(f'\tg2c library version: {__g2clib_version__}')
33    print(f'\tJPEG compression support: {has_jpeg_support}')
34    print(f'\tPNG compression support: {has_png_support}')
35    print(f'\tAEC compression support: {has_aec_support}')
36    print(f'')
37    print(f'\tNCEP 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):
1490def interpolate(a, method: Union[int, str], grid_def_in, grid_def_out, 
1491                method_options=None, num_threads=1):
1492    """
1493    This is the module-level interpolation function.
1494
1495    This interfaces with the grib2io_interp component package that interfaces to
1496    the [NCEPLIBS-ip library](https://github.com/NOAA-EMC/NCEPLIBS-ip).
1497
1498    Parameters
1499    ----------
1500    a : numpy.ndarray or tuple
1501        Input data.  If `a` is a `numpy.ndarray`, scalar interpolation will be
1502        performed.  If `a` is a `tuple`, then vector interpolation will be
1503        performed with the assumption that u = a[0] and v = a[1] and are both
1504        `numpy.ndarray`.
1505
1506        These data are expected to be in 2-dimensional form with shape (ny, nx)
1507        or 3-dimensional (:, ny, nx) where the 1st dimension represents another
1508        spatial, temporal, or classification (i.e. ensemble members) dimension.
1509        The function will properly flatten the (ny,nx) dimensions into (nx * ny)
1510        acceptable for input into the interpolation subroutines.
1511    method
1512        Interpolate method to use. This can either be an integer or string using
1513        the following mapping:
1514
1515        | Interpolate Scheme | Integer Value |
1516        | :---:              | :---:         |
1517        | 'bilinear'         | 0             |
1518        | 'bicubic'          | 1             |
1519        | 'neighbor'         | 2             |
1520        | 'budget'           | 3             |
1521        | 'spectral'         | 4             |
1522        | 'neighbor-budget'  | 6             |
1523
1524    grid_def_in : grib2io.Grib2GridDef
1525        Grib2GridDef object for the input grid.
1526    grid_def_out : grib2io.Grib2GridDef
1527        Grib2GridDef object for the output grid or station points.
1528    method_options : list of ints, optional
1529        Interpolation options. See the NCEPLIBS-ip documentation for
1530        more information on how these are used.
1531    num_threads : int, optional
1532        Number of OpenMP threads to use for interpolation. The default
1533        value is 1. If grib2io_interp was not built with OpenMP, then
1534        this keyword argument and value will have no impact.
1535
1536    Returns
1537    -------
1538    interpolate
1539        Returns a `numpy.ndarray` when scalar interpolation is performed or a
1540        `tuple` of `numpy.ndarray`s when vector interpolation is performed with
1541        the assumptions that 0-index is the interpolated u and 1-index is the
1542        interpolated v.
1543    """
1544    import grib2io_interp
1545    from grib2io_interp import interpolate
1546
1547    prev_num_threads = 1
1548    try:
1549        import grib2io_interp
1550        if grib2io_interp.has_openmp_support:
1551            prev_num_threads = grib2io_interp.get_openmp_threads()
1552            grib2io_interp.set_openmp_threads(num_threads)
1553    except(AttributeError):
1554        pass
1555
1556    if isinstance(method,int) and method not in _interp_schemes.values():
1557        raise ValueError('Invalid interpolation method.')
1558    elif isinstance(method,str):
1559        if method in _interp_schemes.keys():
1560            method = _interp_schemes[method]
1561        else:
1562            raise ValueError('Invalid interpolation method.')
1563
1564    if method_options is None:
1565        method_options = np.zeros((20),dtype=np.int32)
1566        if method in {3,6}:
1567            method_options[0:2] = -1
1568
1569    ni = grid_def_in.npoints
1570    no = grid_def_out.npoints
1571
1572    # Adjust shape of input array(s)
1573    a,newshp = _adjust_array_shape_for_interp(a,grid_def_in,grid_def_out)
1574
1575    # Set lats and lons if stations, else create array for grids.
1576    if grid_def_out.gdtn == -1:
1577        rlat = np.array(grid_def_out.lats,dtype=np.float32)
1578        rlon = np.array(grid_def_out.lons,dtype=np.float32)
1579    else:
1580        rlat = np.zeros((no),dtype=np.float32)
1581        rlon = np.zeros((no),dtype=np.float32)
1582
1583    # Call interpolation subroutines according to type of a.
1584    if isinstance(a,np.ndarray):
1585        # Scalar
1586        if np.any(np.isnan(a)):
1587            ibi = np.zeros((a.shape[0]),dtype=np.int32)+1
1588            li = np.where(np.isnan(a),0,1).astype(np.int8)
1589        else:
1590            ibi = np.zeros((a.shape[0]),dtype=np.int32)
1591            li = np.zeros(a.shape,dtype=np.int8)
1592        go = np.zeros((a.shape[0],no),dtype=np.float32)
1593        no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options,
1594                                                 grid_def_in.gdtn,grid_def_in.gdt,
1595                                                 grid_def_out.gdtn,grid_def_out.gdt,
1596                                                 ibi,li.T,a.T,go.T,rlat,rlon)
1597        lo = lo.T.reshape(newshp)
1598        out = go.reshape(newshp)
1599        out = np.where(lo==0,np.nan,out)
1600    elif isinstance(a,tuple):
1601        # Vector
1602        if np.any(np.isnan(a)):
1603            ibi = np.zeros((a.shape[0]),dtype=np.int32)+1
1604            li = np.where(np.isnan(a),0,1).astype(np.int8)
1605        else:
1606            ibi = np.zeros((a.shape[0]),dtype=np.int32)
1607            li = np.zeros(a.shape,dtype=np.int8)
1608        uo = np.zeros((a[0].shape[0],no),dtype=np.float32)
1609        vo = np.zeros((a[1].shape[0],no),dtype=np.float32)
1610        crot = np.ones((no),dtype=np.float32)
1611        srot = np.zeros((no),dtype=np.float32)
1612        no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options,
1613                                                 grid_def_in.gdtn,grid_def_in.gdt,
1614                                                 grid_def_out.gdtn,grid_def_out.gdt,
1615                                                 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T,
1616                                                 rlat,rlon,crot,srot)
1617        del crot
1618        del srot
1619        lo = lo[:,0].reshape(newshp)
1620        uo = uo.reshape(new)
1621        vo = vo.reshape(new)
1622        uo = np.where(lo==0,np.nan,uo)
1623        vo = np.where(lo==0,np.nan,vo)
1624        out = (uo,vo)
1625
1626    del rlat
1627    del rlon
1628
1629    try:
1630        if grib2io_interp.has_openmp_support:
1631            grib2io_interp.set_openmp_threads(prev_num_threads)
1632    except(AttributeError):
1633        pass
1634
1635    return out

This is the module-level interpolation function.

This interfaces with the grib2io_interp component package that interfaces to the NCEPLIBS-ip library.

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.

  • 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 grib2io_interp was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
  • interpolate: Returns a numpy.ndarray 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, grid_def_in, lats, lons, method_options=None, num_threads=1):
1638def interpolate_to_stations(a, method, grid_def_in, lats, lons,
1639                            method_options=None, num_threads=1):
1640    """
1641    Module-level interpolation function for interpolation to stations.
1642
1643    Interfaces with the grib2io_interp component package that interfaces to the
1644    [NCEPLIBS-ip
1645    library](https://github.com/NOAA-EMC/NCEPLIBS-ip). It supports scalar and
1646    vector interpolation according to the type of object a.
1647
1648    Parameters
1649    ----------
1650    a : numpy.ndarray or tuple
1651        Input data.  If `a` is a `numpy.ndarray`, scalar interpolation will be
1652        performed.  If `a` is a `tuple`, then vector interpolation will be
1653        performed with the assumption that u = a[0] and v = a[1] and are both
1654        `numpy.ndarray`.
1655
1656        These data are expected to be in 2-dimensional form with shape (ny, nx)
1657        or 3-dimensional (:, ny, nx) where the 1st dimension represents another
1658        spatial, temporal, or classification (i.e. ensemble members) dimension.
1659        The function will properly flatten the (ny,nx) dimensions into (nx * ny)
1660        acceptable for input into the interpolation subroutines.
1661    method
1662        Interpolate method to use. This can either be an integer or string using
1663        the following mapping:
1664
1665        | Interpolate Scheme | Integer Value |
1666        | :---:              | :---:         |
1667        | 'bilinear'         | 0             |
1668        | 'bicubic'          | 1             |
1669        | 'neighbor'         | 2             |
1670        | 'budget'           | 3             |
1671        | 'spectral'         | 4             |
1672        | 'neighbor-budget'  | 6             |
1673
1674    grid_def_in : grib2io.Grib2GridDef
1675        Grib2GridDef object for the input grid.
1676    lats : numpy.ndarray or list
1677        Latitudes for station points
1678    lons : numpy.ndarray or list
1679        Longitudes for station points
1680    method_options : list of ints, optional
1681        Interpolation options. See the NCEPLIBS-ip documentation for
1682        more information on how these are used.
1683    num_threads : int, optional
1684        Number of OpenMP threads to use for interpolation. The default
1685        value is 1. If grib2io_interp was not built with OpenMP, then
1686        this keyword argument and value will have no impact.
1687
1688    Returns
1689    -------
1690    interpolate_to_stations
1691        Returns a `numpy.ndarray` when scalar interpolation is performed or a
1692        `tuple` of `numpy.ndarray`s when vector interpolation is performed with
1693        the assumptions that 0-index is the interpolated u and 1-index is the
1694        interpolated v.
1695    """
1696    import grib2io_interp
1697    from grib2io_interp import interpolate
1698
1699    prev_num_threads = 1
1700    try:
1701        import grib2io_interp
1702        if grib2io_interp.has_openmp_support:
1703            prev_num_threads = grib2io_interp.get_openmp_threads()
1704            grib2io_interp.set_openmp_threads(num_threads)
1705    except(AttributeError):
1706        pass
1707
1708    if isinstance(method,int) and method not in _interp_schemes.values():
1709        raise ValueError('Invalid interpolation method.')
1710    elif isinstance(method,str):
1711        if method in _interp_schemes.keys():
1712            method = _interp_schemes[method]
1713        else:
1714            raise ValueError('Invalid interpolation method.')
1715
1716    if method_options is None:
1717        method_options = np.zeros((20),dtype=np.int32)
1718        if method in {3,6}:
1719            method_options[0:2] = -1
1720
1721    # Check lats and lons
1722    if isinstance(lats,list):
1723        nlats = len(lats)
1724    elif isinstance(lats,np.ndarray) and len(lats.shape) == 1:
1725        nlats = lats.shape[0]
1726    else:
1727        raise ValueError('Station latitudes must be a list or 1-D NumPy array.')
1728    if isinstance(lons,list):
1729        nlons = len(lons)
1730    elif isinstance(lons,np.ndarray) and len(lons.shape) == 1:
1731        nlons = lons.shape[0]
1732    else:
1733        raise ValueError('Station longitudes must be a list or 1-D NumPy array.')
1734    if nlats != nlons:
1735        raise ValueError('Station lats and lons must be same size.')
1736
1737    ni = grid_def_in.npoints
1738    no = nlats
1739
1740    # Adjust shape of input array(s)
1741    a,newshp = _adjust_array_shape_for_interp_stations(a,grid_def_in,no)
1742
1743    # Set lats and lons if stations
1744    rlat = np.array(lats,dtype=np.float32)
1745    rlon = np.array(lons,dtype=np.float32)
1746
1747    # Use gdtn = -1 for stations and an empty template array
1748    gdtn = -1
1749    gdt = np.zeros((200),dtype=np.int32)
1750
1751    # Call interpolation subroutines according to type of a.
1752    if isinstance(a,np.ndarray):
1753        # Scalar
1754        ibi = np.zeros((a.shape[0]),dtype=np.int32)
1755        li = np.zeros(a.shape,dtype=np.int32)
1756        go = np.zeros((a.shape[0],no),dtype=np.float32)
1757        no,ibo,lo,iret = interpolate.interpolate_scalar(method,method_options,
1758                                                 grid_def_in.gdtn,grid_def_in.gdt,
1759                                                 gdtn,gdt,
1760                                                 ibi,li.T,a.T,go.T,rlat,rlon)
1761        out = go.reshape(newshp)
1762    elif isinstance(a,tuple):
1763        # Vector
1764        ibi = np.zeros((a[0].shape[0]),dtype=np.int32)
1765        li = np.zeros(a[0].shape,dtype=np.int32)
1766        uo = np.zeros((a[0].shape[0],no),dtype=np.float32)
1767        vo = np.zeros((a[1].shape[0],no),dtype=np.float32)
1768        crot = np.ones((no),dtype=np.float32)
1769        srot = np.zeros((no),dtype=np.float32)
1770        no,ibo,lo,iret = interpolate.interpolate_vector(method,method_options,
1771                                                 grid_def_in.gdtn,grid_def_in.gdt,
1772                                                 gdtn,gdt,
1773                                                 ibi,li.T,a[0].T,a[1].T,uo.T,vo.T,
1774                                                 rlat,rlon,crot,srot)
1775        del crot
1776        del srot
1777        out = (uo.reshape(newshp),vo.reshape(newshp))
1778
1779    del rlat
1780    del rlon
1781
1782    try:
1783        if grib2io_interp.has_openmp_support:
1784            grib2io_interp.set_openmp_threads(prev_num_threads)
1785    except(AttributeError):
1786        pass
1787
1788    return out

Module-level interpolation function for interpolation to stations.

Interfaces with the grib2io_interp component package that interfaces to the NCEPLIBS-ip library. 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.

  • 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 grib2io_interp was not built with OpenMP, then this keyword argument and value will have no impact.
Returns
  • interpolate_to_stations: Returns a numpy.ndarray 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:
530class Grib2Message:
531    """
532    Creation class for a GRIB2 message.
533
534    This class returns a dynamically-created Grib2Message object that
535    inherits from `_Grib2Message` and grid, product, data representation
536    template classes according to the template numbers for the respective
537    sections. If `section3`, `section4`, or `section5` are omitted, then
538    the appropriate keyword arguments for the template number `gdtn=`, 
539    `pdtn=`, or `drtn=` must be provided.
540
541    Parameters
542    ----------
543    section0
544        GRIB2 section 0 array.
545    section1
546        GRIB2 section 1 array.
547    section2
548        Local Use section data.
549    section3
550        GRIB2 section 3 array.
551    section4
552        GRIB2 section 4 array.
553    section5
554        GRIB2 section 5 array.
555        
556    Returns
557    -------
558    Msg
559        A dynamically-create Grib2Message object that inherits from
560        _Grib2Message, a grid definition template class, product 
561        definition template class, and a data representation template
562        class.
563    """
564    def __new__(self, section0: NDArray = np.array([struct.unpack('>I',b'GRIB')[0],0,0,2,0]),
565                      section1: NDArray = np.zeros((13),dtype=np.int64),
566                      section2: Optional[bytes] = None,
567                      section3: Optional[NDArray] = None,
568                      section4: Optional[NDArray] = None,
569                      section5: Optional[NDArray] = None, *args, **kwargs):
570
571        if np.all(section1==0):
572            try:
573                # Python >= 3.10
574                section1[5:11] = datetime.datetime.fromtimestamp(0, datetime.UTC).timetuple()[:6]
575            except(AttributeError):
576                # Python < 3.10
577                section1[5:11] = datetime.datetime.utcfromtimestamp(0).timetuple()[:6]
578
579        bases = list()
580        if section3 is None:
581            if 'gdtn' in kwargs.keys():
582                gdtn = kwargs['gdtn']
583                Gdt = templates.gdt_class_by_gdtn(gdtn)
584                bases.append(Gdt)
585                section3 = np.zeros((Gdt._len+5),dtype=np.int64)
586                section3[4] = gdtn
587            else:
588                raise ValueError("Must provide GRIB2 Grid Definition Template Number or section 3 array")
589        else:
590            gdtn = section3[4]
591            Gdt = templates.gdt_class_by_gdtn(gdtn)
592            bases.append(Gdt)
593
594        if section4 is None:
595            if 'pdtn' in kwargs.keys():
596                pdtn = kwargs['pdtn']
597                Pdt = templates.pdt_class_by_pdtn(pdtn)
598                bases.append(Pdt)
599                section4 = np.zeros((Pdt._len+2),dtype=np.int64)
600                section4[1] = pdtn
601            else:
602                raise ValueError("Must provide GRIB2 Production Definition Template Number or section 4 array")
603        else:
604            pdtn = section4[1]
605            Pdt = templates.pdt_class_by_pdtn(pdtn)
606            bases.append(Pdt)
607
608        if section5 is None:
609            if 'drtn' in kwargs.keys():
610                drtn = kwargs['drtn']
611                Drt = templates.drt_class_by_drtn(drtn)
612                bases.append(Drt)
613                section5 = np.zeros((Drt._len+2),dtype=np.int64)
614                section5[1] = drtn
615            else:
616                raise ValueError("Must provide GRIB2 Data Representation Template Number or section 5 array")
617        else:
618            drtn = section5[1]
619            Drt = templates.drt_class_by_drtn(drtn)
620            bases.append(Drt)
621
622        # attempt to use existing Msg class if it has already been made with gdtn,pdtn,drtn combo
623        try:
624            Msg = _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"]
625        except KeyError:
626            @dataclass(init=False, repr=False)
627            class Msg(_Grib2Message, *bases):
628                pass
629            _msg_class_store[f"{gdtn}:{pdtn}:{drtn}"] = Msg
630
631        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:
 634@dataclass
 635class _Grib2Message:
 636    """
 637    GRIB2 Message base class.
 638    """
 639    # GRIB2 Sections
 640    section0: NDArray = field(init=True,repr=False)
 641    section1: NDArray = field(init=True,repr=False)
 642    section2: bytes = field(init=True,repr=False)
 643    section3: NDArray = field(init=True,repr=False)
 644    section4: NDArray = field(init=True,repr=False)
 645    section5: NDArray = field(init=True,repr=False)
 646    bitMapFlag: templates.Grib2Metadata = field(init=True,repr=False,default=255)
 647
 648    # Section 0 looked up attributes
 649    indicatorSection: NDArray = field(init=False,repr=False,default=templates.IndicatorSection())
 650    discipline: templates.Grib2Metadata = field(init=False,repr=False,default=templates.Discipline())
 651
 652    # Section 1 looked up attributes
 653    identificationSection: NDArray = field(init=False,repr=False,default=templates.IdentificationSection())
 654    originatingCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingCenter())
 655    originatingSubCenter: templates.Grib2Metadata = field(init=False,repr=False,default=templates.OriginatingSubCenter())
 656    masterTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.MasterTableInfo())
 657    localTableInfo: templates.Grib2Metadata = field(init=False,repr=False,default=templates.LocalTableInfo())
 658    significanceOfReferenceTime: templates.Grib2Metadata = field(init=False,repr=False,default=templates.SignificanceOfReferenceTime())
 659    year: int = field(init=False,repr=False,default=templates.Year())
 660    month: int = field(init=False,repr=False,default=templates.Month())
 661    day: int = field(init=False,repr=False,default=templates.Day())
 662    hour: int = field(init=False,repr=False,default=templates.Hour())
 663    minute: int = field(init=False,repr=False,default=templates.Minute())
 664    second: int = field(init=False,repr=False,default=templates.Second())
 665    refDate: datetime.datetime = field(init=False,repr=False,default=templates.RefDate())
 666    productionStatus: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductionStatus())
 667    typeOfData: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfData())
 668
 669    # Section 3 looked up common attributes.  Other looked up attributes are available according
 670    # to the Grid Definition Template.
 671    gridDefinitionSection: NDArray = field(init=False,repr=False,default=templates.GridDefinitionSection())
 672    sourceOfGridDefinition: int = field(init=False,repr=False,default=templates.SourceOfGridDefinition())
 673    numberOfDataPoints: int = field(init=False,repr=False,default=templates.NumberOfDataPoints())
 674    interpretationOfListOfNumbers: templates.Grib2Metadata = field(init=False,repr=False,default=templates.InterpretationOfListOfNumbers())
 675    gridDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.GridDefinitionTemplateNumber())
 676    gridDefinitionTemplate: list = field(init=False,repr=False,default=templates.GridDefinitionTemplate())
 677    _earthparams: dict = field(init=False,repr=False,default=templates.EarthParams())
 678    _dxsign: float = field(init=False,repr=False,default=templates.DxSign())
 679    _dysign: float = field(init=False,repr=False,default=templates.DySign())
 680    _llscalefactor: float = field(init=False,repr=False,default=templates.LLScaleFactor())
 681    _lldivisor: float = field(init=False,repr=False,default=templates.LLDivisor())
 682    _xydivisor: float = field(init=False,repr=False,default=templates.XYDivisor())
 683    shapeOfEarth: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ShapeOfEarth())
 684    earthShape: str = field(init=False,repr=False,default=templates.EarthShape())
 685    earthRadius: float = field(init=False,repr=False,default=templates.EarthRadius())
 686    earthMajorAxis: float = field(init=False,repr=False,default=templates.EarthMajorAxis())
 687    earthMinorAxis: float = field(init=False,repr=False,default=templates.EarthMinorAxis())
 688    resolutionAndComponentFlags: list = field(init=False,repr=False,default=templates.ResolutionAndComponentFlags())
 689    ny: int = field(init=False,repr=False,default=templates.Ny())
 690    nx: int = field(init=False,repr=False,default=templates.Nx())
 691    scanModeFlags: list = field(init=False,repr=False,default=templates.ScanModeFlags())
 692    projParameters: dict = field(init=False,repr=False,default=templates.ProjParameters())
 693
 694    # Section 4
 695    productDefinitionTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.ProductDefinitionTemplateNumber())
 696    productDefinitionTemplate: NDArray = field(init=False,repr=False,default=templates.ProductDefinitionTemplate())
 697
 698    # Section 5 looked up common attributes.  Other looked up attributes are
 699    # available according to the Data Representation Template.
 700    numberOfPackedValues: int = field(init=False,repr=False,default=templates.NumberOfPackedValues())
 701    dataRepresentationTemplateNumber: templates.Grib2Metadata = field(init=False,repr=False,default=templates.DataRepresentationTemplateNumber())
 702    dataRepresentationTemplate: list = field(init=False,repr=False,default=templates.DataRepresentationTemplate())
 703    typeOfValues: templates.Grib2Metadata = field(init=False,repr=False,default=templates.TypeOfValues())
 704
 705
 706    def __post_init__(self):
 707        """Set some attributes after init."""
 708        self._msgnum = -1
 709        self._deflist = None
 710        self._coordlist = None
 711        self._signature = self._generate_signature()
 712        try:
 713            self._sha1_section3 = hashlib.sha1(self.section3).hexdigest()
 714        except(TypeError):
 715            pass
 716        self.bitMapFlag = templates.Grib2Metadata(self.bitMapFlag,table='6.0')
 717        self.bitmap = None
 718
 719
 720    @property
 721    def _isNDFD(self):
 722        """Check if GRIB2 message is from NWS NDFD"""
 723        return np.all(self.section1[0:2]==[8,65535])
 724
 725
 726    @property
 727    def gdtn(self):
 728        """Return Grid Definition Template Number"""
 729        return self.section3[4]
 730
 731
 732    @property
 733    def gdt(self):
 734        """Return Grid Definition Template."""
 735        return self.gridDefinitionTemplate
 736
 737
 738    @property
 739    def pdtn(self):
 740        """Return Product Definition Template Number."""
 741        return self.section4[1]
 742
 743
 744    @property
 745    def pdt(self):
 746        """Return Product Definition Template."""
 747        return self.productDefinitionTemplate
 748
 749
 750    @property
 751    def drtn(self):
 752        """Return Data Representation Template Number."""
 753        return self.section5[1]
 754
 755
 756    @property
 757    def drt(self):
 758        """Return Data Representation Template."""
 759        return self.dataRepresentationTemplate
 760
 761
 762    @property
 763    def pdy(self):
 764        """Return the PDY ('YYYYMMDD')."""
 765        return ''.join([str(i) for i in self.section1[5:8]])
 766
 767
 768    @property
 769    def griddef(self):
 770        """Return a Grib2GridDef instance for a GRIB2 message."""
 771        return Grib2GridDef.from_section3(self.section3)
 772
 773
 774    @property
 775    def lats(self):
 776        """Return grid latitudes."""
 777        return self.latlons()[0]
 778
 779
 780    @property
 781    def lons(self):
 782        """Return grid longitudes."""
 783        return self.latlons()[1]
 784
 785    @property
 786    def min(self):
 787        """Return minimum value of data."""
 788        return np.nanmin(self.data)
 789
 790
 791    @property
 792    def max(self):
 793        """Return maximum value of data."""
 794        return np.nanmax(self.data)
 795
 796
 797    @property
 798    def mean(self):
 799        """Return mean value of data."""
 800        return np.nanmean(self.data)
 801
 802
 803    @property
 804    def median(self):
 805        """Return median value of data."""
 806        return np.nanmedian(self.data)
 807
 808
 809    def __repr__(self):
 810        """
 811        Return an unambiguous string representation of the object.
 812
 813        Returns
 814        -------
 815        repr
 816            A string representation of the object, including information from
 817            sections 0, 1, 3, 4, 5, and 6.
 818        """
 819        info = ''
 820        for sect in [0,1,3,4,5,6]:
 821            for k,v in self.attrs_by_section(sect,values=True).items():
 822                info += f'Section {sect}: {k} = {v}\n'
 823        return info
 824
 825    def __str__(self):
 826        """
 827        Return a readable string representation of the object.
 828
 829        Returns
 830        -------
 831        str
 832            A formatted string representation of the object, including
 833            selected attributes.
 834        """
 835        return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:'
 836                f'{self.fullName} ({self.units}):{self.level}:'
 837                f'{self.leadTime}')
 838
 839
 840    def _generate_signature(self):
 841        """Generature SHA-1 hash string from GRIB2 integer sections."""
 842        return hashlib.sha1(np.concatenate((self.section0,self.section1,
 843                                            self.section3,self.section4,
 844                                            self.section5))).hexdigest()
 845
 846
 847    def attrs_by_section(self, sect: int, values: bool=False):
 848        """
 849        Provide a tuple of attribute names for the given GRIB2 section.
 850
 851        Parameters
 852        ----------
 853        sect
 854            The GRIB2 section number.
 855        values
 856            Optional (default is `False`) argument to return attributes values.
 857
 858        Returns
 859        -------
 860        attrs_by_section
 861            A list of attribute names or dict of name:value pairs if `values =
 862            True`.
 863        """
 864        if sect in {0,1,6}:
 865            attrs = templates._section_attrs[sect]
 866        elif sect in {3,4,5}:
 867            def _find_class_index(n):
 868                _key = {3:'Grid', 4:'Product', 5:'Data'}
 869                for i,c in enumerate(self.__class__.__mro__):
 870                    if _key[n] in c.__name__:
 871                        return i
 872                else:
 873                    return []
 874            if sys.version_info.minor <= 8:
 875                attrs = templates._section_attrs[sect]+\
 876                        [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')]
 877            else:
 878                attrs = templates._section_attrs[sect]+\
 879                        self.__class__.__mro__[_find_class_index(sect)]._attrs
 880        else:
 881            attrs = []
 882        if values:
 883            return {k:getattr(self,k) for k in attrs}
 884        else:
 885            return attrs
 886
 887
 888    def pack(self):
 889        """
 890        Pack GRIB2 section data into a binary message.
 891
 892        It is the user's responsibility to populate the GRIB2 section
 893        information with appropriate metadata.
 894        """
 895        # Create beginning of packed binary message with section 0 and 1 data.
 896        self._sections = []
 897        self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection)
 898        self._sections += [0,1]
 899
 900        # Add section 2 if present.
 901        if isinstance(self.section2,bytes) and len(self.section2) > 0:
 902            self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2)
 903            self._sections.append(2)
 904
 905        # Add section 3.
 906        self.section3[1] = self.nx * self.ny
 907        self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection,
 908                                                   self.gridDefinitionTemplate,
 909                                                   self._deflist)
 910        self._sections.append(3)
 911
 912        # Prepare data.
 913        field = np.copy(self.data)
 914        if self.scanModeFlags is not None:
 915            if self.scanModeFlags[3]:
 916                fieldsave = field.astype('f') # Casting makes a copy
 917                field[1::2,:] = fieldsave[1::2,::-1]
 918        fld = field.astype('f')
 919
 920        # Prepare bitmap, if necessary
 921        bitmapflag = self.bitMapFlag.value
 922        if bitmapflag == 0:
 923            if hasattr(self,'bitmap'):
 924                bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT)
 925            else:
 926                bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT)
 927        else:
 928            bmap = None
 929
 930        # Prepare optional coordinate list
 931        if self._coordlist is not None:
 932            crdlist = np.array(self._coordlist,'f')
 933        else:
 934            crdlist = None
 935
 936        # Prepare data for packing if nans are present
 937        fld = np.ravel(fld)
 938        if bitmapflag in {0,254}:
 939            fld = np.where(np.isnan(fld),0,fld)
 940        else:
 941            if np.isnan(fld).any():
 942                if hasattr(self,'priMissingValue'):
 943                    fld = np.where(np.isnan(fld),self.priMissingValue,fld)
 944            if hasattr(self,'_missvalmap'):
 945                if hasattr(self,'priMissingValue'):
 946                    fld = np.where(self._missvalmap==1,self.priMissingValue,fld)
 947                if hasattr(self,'secMissingValue'):
 948                    fld = np.where(self._missvalmap==2,self.secMissingValue,fld)
 949
 950        # Add sections 4, 5, 6, and 7.
 951        self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn,
 952                                                    self.productDefinitionTemplate,
 953                                                    crdlist,
 954                                                    self.drtn,
 955                                                    self.dataRepresentationTemplate,
 956                                                    fld,
 957                                                    bitmapflag,
 958                                                    bmap)
 959        self._sections.append(4)
 960        self._sections.append(5)
 961        self._sections.append(6)
 962        self._sections.append(7)
 963
 964        # Finalize GRIB2 message with section 8.
 965        self._msg, self._pos = g2clib.grib2_end(self._msg)
 966        self._sections.append(8)
 967        self.section0[-1] = len(self._msg)
 968
 969
 970    @property
 971    def data(self) -> np.array:
 972        """Access the unpacked data values."""
 973        if not hasattr(self,'_auto_nans'): self._auto_nans = _AUTO_NANS
 974        if hasattr(self,'_data'):
 975            if self._auto_nans != _AUTO_NANS:
 976                self._data = self._ondiskarray
 977            if isinstance(self._data, Grib2MessageOnDiskArray):
 978                self._ondiskarray = self._data
 979                self._data = np.asarray(self._data)
 980            return self._data
 981        raise ValueError
 982
 983
 984    @data.setter
 985    def data(self, data):
 986        if not isinstance(data, np.ndarray):
 987            raise ValueError('Grib2Message data only supports numpy arrays')
 988        self._data = data
 989
 990
 991    def flush_data(self):
 992        """Flush the unpacked data values from the Grib2Message object."""
 993        del self._data
 994        self._data = self._ondiskarray
 995        self.bitmap = None
 996
 997
 998    def __getitem__(self, item):
 999        return self.data[item]
1000
1001
1002    def __setitem__(self, item):
1003        raise NotImplementedError('assignment of data not supported via setitem')
1004
1005
1006    def latlons(self, *args, **kwrgs):
1007        """Alias for `grib2io.Grib2Message.grid` method."""
1008        return self.grid(*args, **kwrgs)
1009
1010
1011    def grid(self, unrotate: bool=True):
1012        """
1013        Return lats,lons (in degrees) of grid.
1014
1015        Currently can handle reg. lat/lon,cglobal Gaussian, mercator,
1016        stereographic, lambert conformal, albers equal-area, space-view and
1017        azimuthal equidistant grids.
1018
1019        Parameters
1020        ----------
1021        unrotate
1022            If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the
1023            grid, otherwise `False`, do not.
1024
1025        Returns
1026        -------
1027        lats, lons : numpy.ndarray
1028            Returns two numpy.ndarrays with dtype=numpy.float32 of grid
1029            latitudes and longitudes in units of degrees.
1030        """
1031        if self._sha1_section3 in _latlon_datastore.keys():
1032            return (_latlon_datastore[self._sha1_section3]['latitude'],
1033                    _latlon_datastore[self._sha1_section3]['longitude'])
1034        gdtn = self.gridDefinitionTemplateNumber.value
1035        gdtmpl = self.gridDefinitionTemplate
1036        reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid
1037        if gdtn == 0:
1038            # Regular lat/lon grid
1039            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1040            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1041            dlon = self.gridlengthXDirection
1042            dlat = self.gridlengthYDirection
1043            if lon2 < lon1 and dlon < 0: lon1 = -lon1
1044            lats = np.linspace(lat1,lat2,self.ny)
1045            if reggrid:
1046                lons = np.linspace(lon1,lon2,self.nx)
1047            else:
1048                lons = np.linspace(lon1,lon2,self.ny*2)
1049            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1050        elif gdtn == 1: # Rotated Lat/Lon grid
1051            pj = pyproj.Proj(self.projParameters)
1052            lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint
1053            lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint
1054            if lon1 > 180.0: lon1 -= 360.0
1055            if lon2 > 180.0: lon2 -= 360.0
1056            lats = np.linspace(lat1,lat2,self.ny)
1057            lons = np.linspace(lon1,lon2,self.nx)
1058            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1059            if unrotate:
1060                from grib2io.utils import rotated_grid
1061                lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation,
1062                                                  self.latitudeSouthernPole,
1063                                                  self.longitudeSouthernPole)
1064        elif gdtn == 40: # Gaussian grid (only works for global!)
1065            from grib2io.utils.gauss_grid import gaussian_latitudes
1066            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1067            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1068            nlats = self.ny
1069            if not reggrid: # Reduced Gaussian grid.
1070                nlons = 2*nlats
1071                dlon = 360./nlons
1072            else:
1073                nlons = self.nx
1074                dlon = self.gridlengthXDirection
1075            lons = np.linspace(lon1,lon2,nlons)
1076            # Compute Gaussian lats (north to south)
1077            lats = gaussian_latitudes(nlats)
1078            if lat1 < lat2:  # reverse them if necessary
1079                lats = lats[::-1]
1080            lons,lats = np.meshgrid(lons,lats)
1081        elif gdtn in {10,20,30,31,110}:
1082            # Mercator, Lambert Conformal, Stereographic, Albers Equal Area,
1083            # Azimuthal Equidistant
1084            dx,dy = self.gridlengthXDirection, self.gridlengthYDirection
1085            lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1086            pj = pyproj.Proj(self.projParameters)
1087            llcrnrx, llcrnry = pj(lon1,lat1)
1088            x = llcrnrx+dx*np.arange(self.nx)
1089            y = llcrnry+dy*np.arange(self.ny)
1090            x,y = np.meshgrid(x, y)
1091            lons,lats = pj(x, y, inverse=True)
1092        elif gdtn == 90:
1093            # Satellite Projection
1094            dx = self.gridlengthXDirection
1095            dy = self.gridlengthYDirection
1096            pj = pyproj.Proj(self.projParameters)
1097            x = dx*np.indices((self.ny,self.nx),'f')[1,:,:]
1098            x -= 0.5*x.max()
1099            y = dy*np.indices((self.ny,self.nx),'f')[0,:,:]
1100            y -= 0.5*y.max()
1101            lons,lats = pj(x,y,inverse=True)
1102            # Set lons,lats to 1.e30 where undefined
1103            abslons = np.fabs(lons)
1104            abslats = np.fabs(lats)
1105            lons = np.where(abslons < 1.e20, lons, 1.e30)
1106            lats = np.where(abslats < 1.e20, lats, 1.e30)
1107        elif gdtn == 32769:
1108            # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered)
1109            from grib2io.utils import arakawa_rotated_grid
1110            from grib2io.utils.rotated_grid import DEG2RAD
1111            di, dj = 0.0, 0.0
1112            do_180 = False
1113            idir = 1 if self.scanModeFlags[0] == 0 else -1
1114            jdir = -1 if self.scanModeFlags[1] == 0 else 1
1115            grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1
1116            do_rot = 1 if grid_oriented == 1 else 0
1117            la1 = self.latitudeFirstGridpoint
1118            lo1 = self.longitudeFirstGridpoint
1119            clon = self.longitudeCenterGridpoint
1120            clat = self.latitudeCenterGridpoint
1121            lasp = clat - 90.0
1122            losp = clon
1123            llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp)
1124            la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp)
1125            rlat = -llat
1126            rlon = -llon
1127            if self.nx == 1:
1128                di = 0.0
1129            elif idir == 1:
1130                ti = rlon
1131                while ti < llon:
1132                    ti += 360.0
1133                di = (ti - llon)/float(self.nx-1)
1134            else:
1135                ti = llon
1136                while ti < rlon:
1137                    ti += 360.0
1138                di = (ti - rlon)/float(self.nx-1)
1139            if self.ny == 1:
1140               dj = 0.0
1141            else:
1142                dj = (rlat - llat)/float(self.ny-1)
1143                if dj < 0.0:
1144                    dj = -dj
1145            if idir == 1:
1146                if llon > rlon:
1147                    llon -= 360.0
1148                if llon < 0 and rlon > 0:
1149                    do_180 = True
1150            else:
1151                if rlon > llon:
1152                    rlon -= 360.0
1153                if rlon < 0 and llon > 0:
1154                    do_180 = True
1155            xlat1d = llat + (np.arange(self.ny)*jdir*dj)
1156            xlon1d = llon + (np.arange(self.nx)*idir*di)
1157            xlons, xlats = np.meshgrid(xlon1d,xlat1d)
1158            rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll)
1159            lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp)
1160            if do_180:
1161                lons = np.where(lons>180.0,lons-360.0,lons)
1162            vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles)
1163            rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats)
1164            del xlat1d, xlon1d, xlats, xlons
1165        else:
1166            raise ValueError('Unsupported grid')
1167
1168        _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons)
1169        try:
1170            _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots
1171        except(NameError):
1172            pass
1173
1174        return lats, lons
1175
1176
1177    def map_keys(self):
1178        """
1179        Unpack data grid replacing integer values with strings.
1180
1181        These types of fields are categorical or classifications where data
1182        values do not represent an observable or predictable physical quantity.
1183        An example of such a field would be [Dominant Precipitation Type -
1184        DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml)
1185
1186        Returns
1187        -------
1188        map_keys
1189            numpy.ndarray of string values per element.
1190        """
1191        hold_auto_nans = _AUTO_NANS
1192        set_auto_nans(False)
1193        if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \
1194        (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'):
1195            keys = utils.decode_wx_strings(self.section2)
1196            if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]:
1197                keys[int(self.priMissingValue)] = 'Missing'
1198            if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]:
1199                keys[int(self.secMissingValue)] = 'Missing'
1200            u,inv = np.unique(self.data,return_inverse=True)
1201            fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape)
1202        else:
1203            # For data whose units are defined in a code table (i.e. classification or mask)
1204            tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0]
1205            fld = self.data.astype(np.int32).astype(str)
1206            tbl = tables.get_table(tblname,expand=True)
1207            for val in np.unique(fld):
1208                fld = np.where(fld==val,tbl[val],fld)
1209        set_auto_nans(hold_auto_nans)
1210        return fld
1211
1212
1213    def to_bytes(self, validate: bool=True):
1214        """
1215        Return packed GRIB2 message in bytes format.
1216
1217        This will be useful for exporting data in non-file formats. For example,
1218        can be used to output grib data directly to S3 using the boto3 client
1219        without the need to write a temporary file to upload first.
1220
1221        Parameters
1222        ----------
1223        validate: default=True
1224            If `True` (DEFAULT), validates first/last four bytes for proper
1225            formatting, else returns None. If `False`, message is output as is.
1226
1227        Returns
1228        -------
1229        to_bytes
1230            Returns GRIB2 formatted message as bytes.
1231        """
1232        if hasattr(self,'_msg'):
1233            if validate:
1234                if self.validate():
1235                    return self._msg
1236                else:
1237                    return None
1238            else:
1239                return self._msg
1240        else:
1241            return None
1242
1243
1244    def interpolate(self, method, grid_def_out, method_options=None, drtn=None,
1245                    num_threads=1):
1246        """
1247        Grib2Message Interpolator
1248
1249        Performs spatial interpolation via the [NCEPLIBS-ip
1250        library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate
1251        method only supports scalar interpolation. If you need to perform
1252        vector interpolation, use the module-level `grib2io.interpolate`
1253        function.
1254
1255        Parameters
1256        ----------
1257        method
1258            Interpolate method to use. This can either be an integer or string
1259            using the following mapping:
1260
1261            | Interpolate Scheme | Integer Value |
1262            | :---:              | :---:         |
1263            | 'bilinear'         | 0             |
1264            | 'bicubic'          | 1             |
1265            | 'neighbor'         | 2             |
1266            | 'budget'           | 3             |
1267            | 'spectral'         | 4             |
1268            | 'neighbor-budget'  | 6             |
1269
1270        grid_def_out : grib2io.Grib2GridDef
1271            Grib2GridDef object of the output grid.
1272        method_options : list of ints, optional
1273            Interpolation options. See the NCEPLIBS-ip documentation for
1274            more information on how these are used.
1275        drtn
1276            Data Representation Template to be used for the returned
1277            interpolated GRIB2 message. When `None`, the data representation
1278            template of the source GRIB2 message is used. Once again, it is the
1279            user's responsibility to properly set the Data Representation
1280            Template attributes.
1281        num_threads : int, optional
1282            Number of OpenMP threads to use for interpolation. The default
1283            value is 1. If grib2io_interp was not built with OpenMP, then
1284            this keyword argument and value will have no impact.
1285
1286        Returns
1287        -------
1288        interpolate
1289            If interpolating to a grid, a new Grib2Message object is returned.
1290            The GRIB2 metadata of the new Grib2Message object is identical to
1291            the input except where required to be different because of the new
1292            grid specs and possibly a new data representation template.
1293
1294            If interpolating to station points, the interpolated data values are
1295            returned as a numpy.ndarray.
1296        """
1297        section0 = self.section0
1298        section0[-1] = 0
1299        gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn]
1300        section3 = np.concatenate((gds,grid_def_out.gdt))
1301        drtn = self.drtn if drtn is None else drtn
1302
1303        msg = Grib2Message(section0,self.section1,self.section2,section3,
1304                           self.section4,None,self.bitMapFlag.value,drtn=drtn)
1305
1306        msg._msgnum = -1
1307        msg._deflist = self._deflist
1308        msg._coordlist = self._coordlist
1309        shape = (msg.ny,msg.nx)
1310        ndim = 2
1311        if msg.typeOfValues == 0:
1312            dtype = 'float32'
1313        elif msg.typeOfValues == 1:
1314            dtype = 'int32'
1315        msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out,
1316                                method_options=method_options).reshape(msg.ny,msg.nx)
1317        msg.section5[0] = grid_def_out.npoints
1318        return msg
1319
1320
1321    def validate(self):
1322        """
1323        Validate a complete GRIB2 message.
1324
1325        The g2c library does its own internal validation when g2_gribend() is called, but
1326        we will check in grib2io also. The validation checks if the first 4 bytes in
1327        self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in
1328        section 0 equals the length of the packed message.
1329
1330        Returns
1331        -------
1332        `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise.
1333        """
1334        valid = False
1335        if hasattr(self,'_msg'):
1336            if self._msg[0:4]+self._msg[-4:] == b'GRIB7777':
1337                if self.section0[-1] == len(self._msg):
1338                    valid = True
1339        return valid

GRIB2 Message base class.

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

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[typing.Any, numpy.dtype[+_ScalarType_co]]

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[typing.Any, numpy.dtype[+_ScalarType_co]]

Product Definition Template

numberOfPackedValues: int

Number of Packed Values

dataRepresentationTemplate: list

Data Representation Template

gdtn
726    @property
727    def gdtn(self):
728        """Return Grid Definition Template Number"""
729        return self.section3[4]

Return Grid Definition Template Number

gdt
732    @property
733    def gdt(self):
734        """Return Grid Definition Template."""
735        return self.gridDefinitionTemplate

Return Grid Definition Template.

pdtn
738    @property
739    def pdtn(self):
740        """Return Product Definition Template Number."""
741        return self.section4[1]

Return Product Definition Template Number.

pdt
744    @property
745    def pdt(self):
746        """Return Product Definition Template."""
747        return self.productDefinitionTemplate

Return Product Definition Template.

drtn
750    @property
751    def drtn(self):
752        """Return Data Representation Template Number."""
753        return self.section5[1]

Return Data Representation Template Number.

drt
756    @property
757    def drt(self):
758        """Return Data Representation Template."""
759        return self.dataRepresentationTemplate

Return Data Representation Template.

pdy
762    @property
763    def pdy(self):
764        """Return the PDY ('YYYYMMDD')."""
765        return ''.join([str(i) for i in self.section1[5:8]])

Return the PDY ('YYYYMMDD').

griddef
768    @property
769    def griddef(self):
770        """Return a Grib2GridDef instance for a GRIB2 message."""
771        return Grib2GridDef.from_section3(self.section3)

Return a Grib2GridDef instance for a GRIB2 message.

lats
774    @property
775    def lats(self):
776        """Return grid latitudes."""
777        return self.latlons()[0]

Return grid latitudes.

lons
780    @property
781    def lons(self):
782        """Return grid longitudes."""
783        return self.latlons()[1]

Return grid longitudes.

min
785    @property
786    def min(self):
787        """Return minimum value of data."""
788        return np.nanmin(self.data)

Return minimum value of data.

max
791    @property
792    def max(self):
793        """Return maximum value of data."""
794        return np.nanmax(self.data)

Return maximum value of data.

mean
797    @property
798    def mean(self):
799        """Return mean value of data."""
800        return np.nanmean(self.data)

Return mean value of data.

median
803    @property
804    def median(self):
805        """Return median value of data."""
806        return np.nanmedian(self.data)

Return median value of data.

def attrs_by_section(self, sect: int, values: bool = False):
847    def attrs_by_section(self, sect: int, values: bool=False):
848        """
849        Provide a tuple of attribute names for the given GRIB2 section.
850
851        Parameters
852        ----------
853        sect
854            The GRIB2 section number.
855        values
856            Optional (default is `False`) argument to return attributes values.
857
858        Returns
859        -------
860        attrs_by_section
861            A list of attribute names or dict of name:value pairs if `values =
862            True`.
863        """
864        if sect in {0,1,6}:
865            attrs = templates._section_attrs[sect]
866        elif sect in {3,4,5}:
867            def _find_class_index(n):
868                _key = {3:'Grid', 4:'Product', 5:'Data'}
869                for i,c in enumerate(self.__class__.__mro__):
870                    if _key[n] in c.__name__:
871                        return i
872                else:
873                    return []
874            if sys.version_info.minor <= 8:
875                attrs = templates._section_attrs[sect]+\
876                        [a for a in dir(self.__class__.__mro__[_find_class_index(sect)]) if not a.startswith('_')]
877            else:
878                attrs = templates._section_attrs[sect]+\
879                        self.__class__.__mro__[_find_class_index(sect)]._attrs
880        else:
881            attrs = []
882        if values:
883            return {k:getattr(self,k) for k in attrs}
884        else:
885            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 pack(self):
888    def pack(self):
889        """
890        Pack GRIB2 section data into a binary message.
891
892        It is the user's responsibility to populate the GRIB2 section
893        information with appropriate metadata.
894        """
895        # Create beginning of packed binary message with section 0 and 1 data.
896        self._sections = []
897        self._msg,self._pos = g2clib.grib2_create(self.indicatorSection[2:4],self.identificationSection)
898        self._sections += [0,1]
899
900        # Add section 2 if present.
901        if isinstance(self.section2,bytes) and len(self.section2) > 0:
902            self._msg,self._pos = g2clib.grib2_addlocal(self._msg,self.section2)
903            self._sections.append(2)
904
905        # Add section 3.
906        self.section3[1] = self.nx * self.ny
907        self._msg,self._pos = g2clib.grib2_addgrid(self._msg,self.gridDefinitionSection,
908                                                   self.gridDefinitionTemplate,
909                                                   self._deflist)
910        self._sections.append(3)
911
912        # Prepare data.
913        field = np.copy(self.data)
914        if self.scanModeFlags is not None:
915            if self.scanModeFlags[3]:
916                fieldsave = field.astype('f') # Casting makes a copy
917                field[1::2,:] = fieldsave[1::2,::-1]
918        fld = field.astype('f')
919
920        # Prepare bitmap, if necessary
921        bitmapflag = self.bitMapFlag.value
922        if bitmapflag == 0:
923            if hasattr(self,'bitmap'):
924                bmap = np.ravel(self.bitmap).astype(DEFAULT_NUMPY_INT)
925            else:
926                bmap = np.ravel(np.where(np.isnan(fld),0,1)).astype(DEFAULT_NUMPY_INT)
927        else:
928            bmap = None
929
930        # Prepare optional coordinate list
931        if self._coordlist is not None:
932            crdlist = np.array(self._coordlist,'f')
933        else:
934            crdlist = None
935
936        # Prepare data for packing if nans are present
937        fld = np.ravel(fld)
938        if bitmapflag in {0,254}:
939            fld = np.where(np.isnan(fld),0,fld)
940        else:
941            if np.isnan(fld).any():
942                if hasattr(self,'priMissingValue'):
943                    fld = np.where(np.isnan(fld),self.priMissingValue,fld)
944            if hasattr(self,'_missvalmap'):
945                if hasattr(self,'priMissingValue'):
946                    fld = np.where(self._missvalmap==1,self.priMissingValue,fld)
947                if hasattr(self,'secMissingValue'):
948                    fld = np.where(self._missvalmap==2,self.secMissingValue,fld)
949
950        # Add sections 4, 5, 6, and 7.
951        self._msg,self._pos = g2clib.grib2_addfield(self._msg,self.pdtn,
952                                                    self.productDefinitionTemplate,
953                                                    crdlist,
954                                                    self.drtn,
955                                                    self.dataRepresentationTemplate,
956                                                    fld,
957                                                    bitmapflag,
958                                                    bmap)
959        self._sections.append(4)
960        self._sections.append(5)
961        self._sections.append(6)
962        self._sections.append(7)
963
964        # Finalize GRIB2 message with section 8.
965        self._msg, self._pos = g2clib.grib2_end(self._msg)
966        self._sections.append(8)
967        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>
970    @property
971    def data(self) -> np.array:
972        """Access the unpacked data values."""
973        if not hasattr(self,'_auto_nans'): self._auto_nans = _AUTO_NANS
974        if hasattr(self,'_data'):
975            if self._auto_nans != _AUTO_NANS:
976                self._data = self._ondiskarray
977            if isinstance(self._data, Grib2MessageOnDiskArray):
978                self._ondiskarray = self._data
979                self._data = np.asarray(self._data)
980            return self._data
981        raise ValueError

Access the unpacked data values.

def flush_data(self):
991    def flush_data(self):
992        """Flush the unpacked data values from the Grib2Message object."""
993        del self._data
994        self._data = self._ondiskarray
995        self.bitmap = None

Flush the unpacked data values from the Grib2Message object.

def latlons(self, *args, **kwrgs):
1006    def latlons(self, *args, **kwrgs):
1007        """Alias for `grib2io.Grib2Message.grid` method."""
1008        return self.grid(*args, **kwrgs)

Alias for grib2io.Grib2Message.grid method.

def grid(self, unrotate: bool = True):
1011    def grid(self, unrotate: bool=True):
1012        """
1013        Return lats,lons (in degrees) of grid.
1014
1015        Currently can handle reg. lat/lon,cglobal Gaussian, mercator,
1016        stereographic, lambert conformal, albers equal-area, space-view and
1017        azimuthal equidistant grids.
1018
1019        Parameters
1020        ----------
1021        unrotate
1022            If `True` [DEFAULT], and grid is rotated lat/lon, then unrotate the
1023            grid, otherwise `False`, do not.
1024
1025        Returns
1026        -------
1027        lats, lons : numpy.ndarray
1028            Returns two numpy.ndarrays with dtype=numpy.float32 of grid
1029            latitudes and longitudes in units of degrees.
1030        """
1031        if self._sha1_section3 in _latlon_datastore.keys():
1032            return (_latlon_datastore[self._sha1_section3]['latitude'],
1033                    _latlon_datastore[self._sha1_section3]['longitude'])
1034        gdtn = self.gridDefinitionTemplateNumber.value
1035        gdtmpl = self.gridDefinitionTemplate
1036        reggrid = self.gridDefinitionSection[2] == 0 # This means regular 2-d grid
1037        if gdtn == 0:
1038            # Regular lat/lon grid
1039            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1040            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1041            dlon = self.gridlengthXDirection
1042            dlat = self.gridlengthYDirection
1043            if lon2 < lon1 and dlon < 0: lon1 = -lon1
1044            lats = np.linspace(lat1,lat2,self.ny)
1045            if reggrid:
1046                lons = np.linspace(lon1,lon2,self.nx)
1047            else:
1048                lons = np.linspace(lon1,lon2,self.ny*2)
1049            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1050        elif gdtn == 1: # Rotated Lat/Lon grid
1051            pj = pyproj.Proj(self.projParameters)
1052            lat1,lon1 = self.latitudeFirstGridpoint,self.longitudeFirstGridpoint
1053            lat2,lon2 = self.latitudeLastGridpoint,self.longitudeLastGridpoint
1054            if lon1 > 180.0: lon1 -= 360.0
1055            if lon2 > 180.0: lon2 -= 360.0
1056            lats = np.linspace(lat1,lat2,self.ny)
1057            lons = np.linspace(lon1,lon2,self.nx)
1058            lons,lats = np.meshgrid(lons,lats) # Make 2-d arrays.
1059            if unrotate:
1060                from grib2io.utils import rotated_grid
1061                lats,lons = rotated_grid.unrotate(lats,lons,self.anglePoleRotation,
1062                                                  self.latitudeSouthernPole,
1063                                                  self.longitudeSouthernPole)
1064        elif gdtn == 40: # Gaussian grid (only works for global!)
1065            from grib2io.utils.gauss_grid import gaussian_latitudes
1066            lon1, lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1067            lon2, lat2 = self.longitudeLastGridpoint, self.latitudeLastGridpoint
1068            nlats = self.ny
1069            if not reggrid: # Reduced Gaussian grid.
1070                nlons = 2*nlats
1071                dlon = 360./nlons
1072            else:
1073                nlons = self.nx
1074                dlon = self.gridlengthXDirection
1075            lons = np.linspace(lon1,lon2,nlons)
1076            # Compute Gaussian lats (north to south)
1077            lats = gaussian_latitudes(nlats)
1078            if lat1 < lat2:  # reverse them if necessary
1079                lats = lats[::-1]
1080            lons,lats = np.meshgrid(lons,lats)
1081        elif gdtn in {10,20,30,31,110}:
1082            # Mercator, Lambert Conformal, Stereographic, Albers Equal Area,
1083            # Azimuthal Equidistant
1084            dx,dy = self.gridlengthXDirection, self.gridlengthYDirection
1085            lon1,lat1 = self.longitudeFirstGridpoint, self.latitudeFirstGridpoint
1086            pj = pyproj.Proj(self.projParameters)
1087            llcrnrx, llcrnry = pj(lon1,lat1)
1088            x = llcrnrx+dx*np.arange(self.nx)
1089            y = llcrnry+dy*np.arange(self.ny)
1090            x,y = np.meshgrid(x, y)
1091            lons,lats = pj(x, y, inverse=True)
1092        elif gdtn == 90:
1093            # Satellite Projection
1094            dx = self.gridlengthXDirection
1095            dy = self.gridlengthYDirection
1096            pj = pyproj.Proj(self.projParameters)
1097            x = dx*np.indices((self.ny,self.nx),'f')[1,:,:]
1098            x -= 0.5*x.max()
1099            y = dy*np.indices((self.ny,self.nx),'f')[0,:,:]
1100            y -= 0.5*y.max()
1101            lons,lats = pj(x,y,inverse=True)
1102            # Set lons,lats to 1.e30 where undefined
1103            abslons = np.fabs(lons)
1104            abslats = np.fabs(lats)
1105            lons = np.where(abslons < 1.e20, lons, 1.e30)
1106            lats = np.where(abslats < 1.e20, lats, 1.e30)
1107        elif gdtn == 32769:
1108            # Special NCEP Grid, Rotated Lat/Lon, Arakawa E-Grid (Non-Staggered)
1109            from grib2io.utils import arakawa_rotated_grid
1110            from grib2io.utils.rotated_grid import DEG2RAD
1111            di, dj = 0.0, 0.0
1112            do_180 = False
1113            idir = 1 if self.scanModeFlags[0] == 0 else -1
1114            jdir = -1 if self.scanModeFlags[1] == 0 else 1
1115            grid_oriented = 0 if self.resolutionAndComponentFlags[4] == 0 else 1
1116            do_rot = 1 if grid_oriented == 1 else 0
1117            la1 = self.latitudeFirstGridpoint
1118            lo1 = self.longitudeFirstGridpoint
1119            clon = self.longitudeCenterGridpoint
1120            clat = self.latitudeCenterGridpoint
1121            lasp = clat - 90.0
1122            losp = clon
1123            llat, llon = arakawa_rotated_grid.ll2rot(la1,lo1,lasp,losp)
1124            la2, lo2 = arakawa_rotated_grid.rot2ll(-llat,-llon,lasp,losp)
1125            rlat = -llat
1126            rlon = -llon
1127            if self.nx == 1:
1128                di = 0.0
1129            elif idir == 1:
1130                ti = rlon
1131                while ti < llon:
1132                    ti += 360.0
1133                di = (ti - llon)/float(self.nx-1)
1134            else:
1135                ti = llon
1136                while ti < rlon:
1137                    ti += 360.0
1138                di = (ti - rlon)/float(self.nx-1)
1139            if self.ny == 1:
1140               dj = 0.0
1141            else:
1142                dj = (rlat - llat)/float(self.ny-1)
1143                if dj < 0.0:
1144                    dj = -dj
1145            if idir == 1:
1146                if llon > rlon:
1147                    llon -= 360.0
1148                if llon < 0 and rlon > 0:
1149                    do_180 = True
1150            else:
1151                if rlon > llon:
1152                    rlon -= 360.0
1153                if rlon < 0 and llon > 0:
1154                    do_180 = True
1155            xlat1d = llat + (np.arange(self.ny)*jdir*dj)
1156            xlon1d = llon + (np.arange(self.nx)*idir*di)
1157            xlons, xlats = np.meshgrid(xlon1d,xlat1d)
1158            rot2ll_vectorized = np.vectorize(arakawa_rotated_grid.rot2ll)
1159            lats, lons = rot2ll_vectorized(xlats,xlons,lasp,losp)
1160            if do_180:
1161                lons = np.where(lons>180.0,lons-360.0,lons)
1162            vector_rotation_angles_vectorized = np.vectorize(arakawa_rotated_grid.vector_rotation_angles)
1163            rots = vector_rotation_angles_vectorized(lats, lons, clat, losp, xlats)
1164            del xlat1d, xlon1d, xlats, xlons
1165        else:
1166            raise ValueError('Unsupported grid')
1167
1168        _latlon_datastore[self._sha1_section3] = dict(latitude=lats,longitude=lons)
1169        try:
1170            _latlon_datastore[self._sha1_section3]['vector_rotation_angles'] = rots
1171        except(NameError):
1172            pass
1173
1174        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):
1177    def map_keys(self):
1178        """
1179        Unpack data grid replacing integer values with strings.
1180
1181        These types of fields are categorical or classifications where data
1182        values do not represent an observable or predictable physical quantity.
1183        An example of such a field would be [Dominant Precipitation Type -
1184        DPTYPE](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-201.shtml)
1185
1186        Returns
1187        -------
1188        map_keys
1189            numpy.ndarray of string values per element.
1190        """
1191        hold_auto_nans = _AUTO_NANS
1192        set_auto_nans(False)
1193        if (np.all(self.section1[0:2]==[7,14]) and self.shortName == 'PWTHER') or \
1194        (np.all(self.section1[0:2]==[8,65535]) and self.shortName == 'WX'):
1195            keys = utils.decode_wx_strings(self.section2)
1196            if hasattr(self,'priMissingValue') and self.priMissingValue not in [None,0]:
1197                keys[int(self.priMissingValue)] = 'Missing'
1198            if hasattr(self,'secMissingValue') and self.secMissingValue not in [None,0]:
1199                keys[int(self.secMissingValue)] = 'Missing'
1200            u,inv = np.unique(self.data,return_inverse=True)
1201            fld = np.array([keys[x] for x in u])[inv].reshape(self.data.shape)
1202        else:
1203            # For data whose units are defined in a code table (i.e. classification or mask)
1204            tblname = re.findall(r'\d\.\d+',self.units,re.IGNORECASE)[0]
1205            fld = self.data.astype(np.int32).astype(str)
1206            tbl = tables.get_table(tblname,expand=True)
1207            for val in np.unique(fld):
1208                fld = np.where(fld==val,tbl[val],fld)
1209        set_auto_nans(hold_auto_nans)
1210        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):
1213    def to_bytes(self, validate: bool=True):
1214        """
1215        Return packed GRIB2 message in bytes format.
1216
1217        This will be useful for exporting data in non-file formats. For example,
1218        can be used to output grib data directly to S3 using the boto3 client
1219        without the need to write a temporary file to upload first.
1220
1221        Parameters
1222        ----------
1223        validate: default=True
1224            If `True` (DEFAULT), validates first/last four bytes for proper
1225            formatting, else returns None. If `False`, message is output as is.
1226
1227        Returns
1228        -------
1229        to_bytes
1230            Returns GRIB2 formatted message as bytes.
1231        """
1232        if hasattr(self,'_msg'):
1233            if validate:
1234                if self.validate():
1235                    return self._msg
1236                else:
1237                    return None
1238            else:
1239                return self._msg
1240        else:
1241            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):
1244    def interpolate(self, method, grid_def_out, method_options=None, drtn=None,
1245                    num_threads=1):
1246        """
1247        Grib2Message Interpolator
1248
1249        Performs spatial interpolation via the [NCEPLIBS-ip
1250        library](https://github.com/NOAA-EMC/NCEPLIBS-ip). This interpolate
1251        method only supports scalar interpolation. If you need to perform
1252        vector interpolation, use the module-level `grib2io.interpolate`
1253        function.
1254
1255        Parameters
1256        ----------
1257        method
1258            Interpolate method to use. This can either be an integer or string
1259            using the following mapping:
1260
1261            | Interpolate Scheme | Integer Value |
1262            | :---:              | :---:         |
1263            | 'bilinear'         | 0             |
1264            | 'bicubic'          | 1             |
1265            | 'neighbor'         | 2             |
1266            | 'budget'           | 3             |
1267            | 'spectral'         | 4             |
1268            | 'neighbor-budget'  | 6             |
1269
1270        grid_def_out : grib2io.Grib2GridDef
1271            Grib2GridDef object of the output grid.
1272        method_options : list of ints, optional
1273            Interpolation options. See the NCEPLIBS-ip documentation for
1274            more information on how these are used.
1275        drtn
1276            Data Representation Template to be used for the returned
1277            interpolated GRIB2 message. When `None`, the data representation
1278            template of the source GRIB2 message is used. Once again, it is the
1279            user's responsibility to properly set the Data Representation
1280            Template attributes.
1281        num_threads : int, optional
1282            Number of OpenMP threads to use for interpolation. The default
1283            value is 1. If grib2io_interp was not built with OpenMP, then
1284            this keyword argument and value will have no impact.
1285
1286        Returns
1287        -------
1288        interpolate
1289            If interpolating to a grid, a new Grib2Message object is returned.
1290            The GRIB2 metadata of the new Grib2Message object is identical to
1291            the input except where required to be different because of the new
1292            grid specs and possibly a new data representation template.
1293
1294            If interpolating to station points, the interpolated data values are
1295            returned as a numpy.ndarray.
1296        """
1297        section0 = self.section0
1298        section0[-1] = 0
1299        gds = [0, grid_def_out.npoints, 0, 255, grid_def_out.gdtn]
1300        section3 = np.concatenate((gds,grid_def_out.gdt))
1301        drtn = self.drtn if drtn is None else drtn
1302
1303        msg = Grib2Message(section0,self.section1,self.section2,section3,
1304                           self.section4,None,self.bitMapFlag.value,drtn=drtn)
1305
1306        msg._msgnum = -1
1307        msg._deflist = self._deflist
1308        msg._coordlist = self._coordlist
1309        shape = (msg.ny,msg.nx)
1310        ndim = 2
1311        if msg.typeOfValues == 0:
1312            dtype = 'float32'
1313        elif msg.typeOfValues == 1:
1314            dtype = 'int32'
1315        msg._data = interpolate(self.data,method,Grib2GridDef.from_section3(self.section3),grid_def_out,
1316                                method_options=method_options).reshape(msg.ny,msg.nx)
1317        msg.section5[0] = grid_def_out.npoints
1318        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 grib2io_interp 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 validate(self):
1321    def validate(self):
1322        """
1323        Validate a complete GRIB2 message.
1324
1325        The g2c library does its own internal validation when g2_gribend() is called, but
1326        we will check in grib2io also. The validation checks if the first 4 bytes in
1327        self._msg is 'GRIB' and '7777' as the last 4 bytes and that the message length in
1328        section 0 equals the length of the packed message.
1329
1330        Returns
1331        -------
1332        `True` if the packed GRIB2 message is complete and well-formed, `False` otherwise.
1333        """
1334        valid = False
1335        if hasattr(self,'_msg'):
1336            if self._msg[0:4]+self._msg[-4:] == b'GRIB7777':
1337                if self.section0[-1] == len(self._msg):
1338                    valid = True
1339        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:
1791@dataclass
1792class Grib2GridDef:
1793    """
1794    Class for Grid Definition Template Number and Template as attributes.
1795
1796    This allows for cleaner looking code when passing these metadata around.
1797    For example, the `grib2io._Grib2Message.interpolate` method and
1798    `grib2io.interpolate` function accepts these objects.
1799    """
1800    gdtn: int
1801    gdt: NDArray
1802
1803    @classmethod
1804    def from_section3(cls, section3):
1805        return cls(section3[4],section3[5:])
1806
1807    @property
1808    def nx(self):
1809        return self.gdt[7]
1810
1811    @property
1812    def ny(self):
1813        return self.gdt[8]
1814
1815    @property
1816    def npoints(self):
1817        return self.gdt[7] * self.gdt[8]
1818
1819    @property
1820    def shape(self):
1821        return (self.ny, self.nx)

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[typing.Any, numpy.dtype[+_ScalarType_co]])
gdtn: int
gdt: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]
@classmethod
def from_section3(cls, section3):
1803    @classmethod
1804    def from_section3(cls, section3):
1805        return cls(section3[4],section3[5:])
nx
1807    @property
1808    def nx(self):
1809        return self.gdt[7]
ny
1811    @property
1812    def ny(self):
1813        return self.gdt[8]
npoints
1815    @property
1816    def npoints(self):
1817        return self.gdt[7] * self.gdt[8]
shape
1819    @property
1820    def shape(self):
1821        return (self.ny, self.nx)