import grib2io
import numpy as np
Note that nothing has changed between v1 and v2 with respect to to opening GRIB2 files and the same attributes exist.
g = grib2io.open("gfs.t00z.pgrb2.0p25.f006")
print(g)
mode = rb name = /home/ericengle/Downloads/gfs.t00z.pgrb2.0p25.f006 messages = 743 current_message = 0 size = 544359352 closed = False variables = ('4LFTX', 'ABSV', 'ACPCP', 'ALBDO', 'APCP', 'APTMP', 'CAPE', 'CFRZR', 'CICEP', 'CIN', 'CLMR', 'CNWAT', 'CPOFP', 'CPRAT', 'CRAIN', 'CSNOW', 'CWAT', 'CWORK', 'DLWRF', 'DPT', 'DSWRF', 'DZDT', 'FLDCP', 'FRICV', 'GFLUX', 'GRLE', 'GUST', 'HCDC', 'HGT', 'HINDEX', 'HLCY', 'HPBL', 'ICAHT', 'ICEC', 'ICEG', 'ICETK', 'ICETMP', 'ICMR', 'LAND', 'LCDC', 'LFT X', 'LHTFL', 'MCDC', 'MSLET', 'O3MR', 'PEVPR', 'PLPL', 'POT', 'PRATE', 'PRES', 'PRMSL', 'PWAT', 'REFC', 'REFD', 'RH', 'RWMR', 'SFCR', 'SHTFL', 'SNMR', 'SNOD', 'SOILL', 'SOILW', 'SOTYP', 'SPFH', 'SUNSD', 'TCDC', 'TMAX', 'TMIN', 'TMP', 'TOZNE', 'TSOIL', 'U-GWD', 'UFLX', 'UGRD', 'ULWRF', 'USTM', 'USWRF', 'V-GWD', 'VEG', 'VFLX', 'VGRD', 'VIS', 'VRATE', 'VSTM', 'VVEL', 'VWSH', 'WATR', 'WEASD', 'WILT') levels = ('0-0.1 m underground', '0.01 mb', '0.02 mb', '0.04 mb', '0.07 mb', '0.1 mb', '0.1-0.4 m underground', '0.2 mb', '0.33-1 sigma layer', '0.4 mb', '0.4-1 m underground', '0.44-0.72 sigma layer', '0.44-1 sigma layer', '0.7 mb', '0.72-0.94 sigma layer', '0.995 sigma level', '0C isotherm', '1 hybrid level', '1 mb', '1-2 m underground', '10 m above ground', '10 m above mean sea level', '10 mb', '100 m above ground', '100 mb', '1000 m above ground', '1000 mb', '15 mb', '150 mb', '180-0 mb above ground', '1829 m above mean sea level', '2 hybrid level', '2 m above ground', '2 mb', '20 m above ground', '20 mb', '200 mb', '250 mb', '255-0 mb above ground', '2743 m above mean sea level', '3 mb', '30 m above ground', '30 mb', '30-0 mb above ground', '300 mb', '3000-0 m above ground', '350 mb', '3658 m above mean sea level', '40 m above ground', '40 mb', '400 mb', '4000 m above ground', '450 mb', '5 mb', '50 m above ground', '50 mb', '500 mb', '550 mb', '600 mb', '6000-0 m above ground', '650 mb', '7 mb', '70 mb', '700 mb', '750 mb', '80 m above ground', '800 mb', '850 mb', '90-0 mb above ground', '900 mb', '925 mb', '950 mb', '975 mb', 'PV=-2e-06 (Km^2/kg/s) surface', 'PV=2e-06 (Km^2/kg/s) surface', 'boundary layer cloud layer', 'cloud ceiling', 'convective cloud bottom level', 'convective cloud layer', 'convective cloud top level', 'entire atmosphere', 'entire atmosphere (considered as a single layer)', 'high cloud bottom level', 'high cloud layer', 'high cloud top level', 'highest tropospheric freezing level', 'low cloud bottom level', 'low cloud layer', 'low cloud top level', 'max wind', 'mean sea level', 'middle cloud bottom level', 'middle cloud layer', 'middle cloud top level', 'planetary boundary layer', 'surface', 'top of atmosphere', 'tropopause')
Underneath the hood, the indexing has changed significantly. There no longer is GRIB2-specific metadata from messages, but just information about location and size of messages. We are now storing the GRIB2 message object in the index in key 'msg'.
One might think that storing all of these objects in the index would negatively impact performance, but the new GRIB2 message object is extremely lightweight (more on that later) and acts like a templated container of metadata. At this point, the data section is not a part of the GRIB2 message object.
print(g._index.keys())
dict_keys(['offset', 'bitmap_offset', 'data_offset', 'size', 'data_size', 'submessageOffset', 'submessageBeginSection', 'isSubmessage', 'messageNumber', 'msg'])
The grib2io.open
class provides custom implementations of read(), seek(), and tell(). While these methods are traditionally used for traversing bytes, these custom implementations operate on units of GRIB2 messages. Note that when these methods are used, you are not acutally performning any I/O to the GRIB2 file. You are positioning inside the list contained by grib2io.open._index['msg']
. The following example puts the "file object" pointer to the 100th message and reads the next 15 messages (from message 100). In this example, there is no file IO -- just communicating with the Grib2Message objects stored in the index.
g.seek(100)
print('GRIB2 file poistion: ',g.tell())
msgs = g.read(15)
print('Number of messages read: ',len(msgs))
for msg in msgs:
print(msg)
GRIB2 file poistion: 100 Number of messages read: 15 100:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):1 mb:6:00:00 101:d=2022-10-28 00:00:00:VGRD:V-Component of Wind (m s-1):1 mb:6:00:00 102:d=2022-10-28 00:00:00:ABSV:Absolute Vorticity (s-1):1 mb:6:00:00 103:d=2022-10-28 00:00:00:O3MR:Ozone Mixing Ratio (kg kg-1):1 mb:6:00:00 104:d=2022-10-28 00:00:00:HGT:Geopotential Height (gpm):2 mb:6:00:00 105:d=2022-10-28 00:00:00:TMP:Temperature (K):2 mb:6:00:00 106:d=2022-10-28 00:00:00:RH:Relative Humidity (%):2 mb:6:00:00 107:d=2022-10-28 00:00:00:SPFH:Specific Humidity (kg kg-1):2 mb:6:00:00 108:d=2022-10-28 00:00:00:VVEL:Vertical Velocity (Pressure) (Pa s-1):2 mb:6:00:00 109:d=2022-10-28 00:00:00:DZDT:Vertical Velocity (Geometric) (m s-1):2 mb:6:00:00 110:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):2 mb:6:00:00 111:d=2022-10-28 00:00:00:VGRD:V-Component of Wind (m s-1):2 mb:6:00:00 112:d=2022-10-28 00:00:00:ABSV:Absolute Vorticity (s-1):2 mb:6:00:00 113:d=2022-10-28 00:00:00:O3MR:Ozone Mixing Ratio (kg kg-1):2 mb:6:00:00 114:d=2022-10-28 00:00:00:HGT:Geopotential Height (gpm):3 mb:6:00:00
IMPORTANT: In grib2io v2.0, you no longer have to specify a 2nd index when select 1 message !!
# Index
msg = g[50]
print(msg)
50:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):0.07 mb:6:00:00
IMPORTANT: In grib2io v2.0, a 0th message now exists !!
# Index
msg = g[0]
print(msg)
0:d=2022-10-28 00:00:00:PRMSL:Pressure Reduced to MSL (Pa):mean sea level:6:00:00
# Slice
msgs = g[80:90]
for msg in msgs:
print(msg)
80:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):0.4 mb:6:00:00 81:d=2022-10-28 00:00:00:VGRD:V-Component of Wind (m s-1):0.4 mb:6:00:00 82:d=2022-10-28 00:00:00:ABSV:Absolute Vorticity (s-1):0.4 mb:6:00:00 83:d=2022-10-28 00:00:00:O3MR:Ozone Mixing Ratio (kg kg-1):0.4 mb:6:00:00 84:d=2022-10-28 00:00:00:HGT:Geopotential Height (gpm):0.7 mb:6:00:00 85:d=2022-10-28 00:00:00:TMP:Temperature (K):0.7 mb:6:00:00 86:d=2022-10-28 00:00:00:RH:Relative Humidity (%):0.7 mb:6:00:00 87:d=2022-10-28 00:00:00:SPFH:Specific Humidity (kg kg-1):0.7 mb:6:00:00 88:d=2022-10-28 00:00:00:VVEL:Vertical Velocity (Pressure) (Pa s-1):0.7 mb:6:00:00 89:d=2022-10-28 00:00:00:DZDT:Vertical Velocity (Geometric) (m s-1):0.7 mb:6:00:00
# Key - where string is the GRIB2 shortName attribute
msgs = g['CAPE']
for msg in msgs:
print(msg)
623:d=2022-10-28 00:00:00:CAPE:Convective Available Potential Energy (J kg-1):surface:6:00:00 709:d=2022-10-28 00:00:00:CAPE:Convective Available Potential Energy (J kg-1):180-0 mb above ground:6:00:00 722:d=2022-10-28 00:00:00:CAPE:Convective Available Potential Energy (J kg-1):90-0 mb above ground:6:00:00 724:d=2022-10-28 00:00:00:CAPE:Convective Available Potential Energy (J kg-1):255-0 mb above ground:6:00:00
Use any Grib2Message object attribute name as a keyword for the file object select() method. NOTE: The select() method will always return a list.
msgs = g.select(shortName='TMP',productDefinitionTemplateNumber=8)
for msg in msgs:
print(msg)
print('\t',msg.duration,msg.statisticalProcess)
646:d=2022-10-28 00:00:00:TMP:Temperature (K):low cloud top level:6:00:00 6:00:00 0 - Average 647:d=2022-10-28 00:00:00:TMP:Temperature (K):middle cloud top level:6:00:00 6:00:00 0 - Average 648:d=2022-10-28 00:00:00:TMP:Temperature (K):high cloud top level:6:00:00 6:00:00 0 - Average
msgs = g.select(shortName='UGRD',typeOfFirstFixedSurface=103)
for msg in msgs:
print(msg)
print('\t',msg.typeOfFirstFixedSurface)
587:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):10 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 674:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):20 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 676:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):30 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 678:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):40 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 680:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):50 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 685:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):80 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm'] 688:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):100 m above ground:6:00:00 103 - ['Specified Height Level Above Ground', 'm']
The Grib2Message object, attributes, and metadata storage have changed significantly from v1.0.0 or v2.0.0. The Grib2Message class inherits from a Grib2Message base class that contains attributes for sections that are not templates. GRIB2 Sections 3, 4, and 5 are defined by templates and therefore the attributes can be different between templates of the same section. All template classes are defined as a dataclasses.dataclass
and each attribute within each template class is a class variable and defined to be a dataclasses.field
object. Further, each attribute, defined as a field has a default value that is a unique descriptor class with custom implementations of the __get__
and __set__
protocols.
Below is an example of all of the metadata associated with a GRIB2 message for a GFS 500mb HGT field. Each metadata item you see is being created dynamically from the numeric GRIB2 section metadata. For example, in section 4, there is the attribute parameterCategory
. This attribute has a custom descriptor class, grib2io.templates.ParameterCategory
, whose __get__
protocol points to the appropriate index location in the section 4 array. If we wanted to change this attribute value, you can perform msg.parameterCategory = 5
. This would trigger the __set__
protocol for the grib2io.templates.ParameterCategory
descriptor class which would put that new value into the appropriate index location in the section 4 array.
msg = g.select(shortName='HGT',level='500 mb')[0]
msg
Section 0: discipline = 0 - Meteorological Products Section 1: originatingCenter = 7 - US National Weather Service - NCEP (WMC) Section 1: originatingSubCenter = 0 - None Section 1: masterTableInfo = 2 - Version Implemented on 4 November 2003 Section 1: localTableInfo = 1 - Number of local table version used. Section 1: significanceOfReferenceTime = 1 - Start of Forecast Section 1: year = 2022 Section 1: month = 10 Section 1: day = 28 Section 1: hour = 0 Section 1: minute = 0 Section 1: second = 0 Section 1: refDate = 2022-10-28 00:00:00 Section 1: productionStatus = 0 - Operational Products Section 1: typeOfData = 1 - Forecast Products Section 3: interpretationOfListOfNumbers = 0 - There is no appended list Section 3: gridDefinitionTemplateNumber = 0 - Latitude/Longitude Section 3: shapeOfEarth = 6 - Earth assumed spherical with radius = 6,371,229.0 m Section 3: earthRadius = 6371229.0 Section 3: earthMajorAxis = None Section 3: earthMinorAxis = None Section 3: resolutionAndComponentFlags = [0, 0, 1, 1, 0, 0, 0, 0] Section 3: ny = 721 Section 3: nx = 1440 Section 3: scanModeFlags = [0, 0, 0, 0, 0, 0, 0, 0] Section 3: latitudeFirstGridpoint = 90.0 Section 3: longitudeFirstGridpoint = 0.0 Section 3: latitudeLastGridpoint = -90.0 Section 3: longitudeLastGridpoint = 359.75 Section 3: gridlengthXDirection = 0.25 Section 3: gridlengthYDirection = -0.25 Section 4: productDefinitionTemplateNumber = 0 - Analysis or forecast at a horizontal level or in a horizontal layer at a point in time. (see Template 4.0) Section 4: fullName = Geopotential Height Section 4: units = gpm Section 4: shortName = HGT Section 4: leadTime = 6:00:00 Section 4: unitOfFirstFixedSurface = Pa Section 4: valueOfFirstFixedSurface = 50000.0 Section 4: unitOfSecondFixedSurface = None Section 4: valueOfSecondFixedSurface = 50000.0 Section 4: validDate = 2022-10-28 06:00:00 Section 4: duration = None Section 4: level = 500 mb Section 4: parameterCategory = 3 Section 4: parameterNumber = 5 Section 4: typeOfGeneratingProcess = 2 - Forecast Section 4: generatingProcess = 96 - Global Forecast System Model T1534 - Forecast hours 00-384 T574 - Forecast hours 00-192 T190 - Forecast hours 204-384 Section 4: backgroundGeneratingProcessIdentifier = 0 Section 4: hoursAfterDataCutoff = 0 Section 4: minutesAfterDataCutoff = 0 Section 4: unitOfTimeRange = 1 - Hour Section 4: forecastTime = 6 Section 4: typeOfFirstFixedSurface = 100 - ['Isobaric Surface', 'Pa'] Section 4: scaleFactorOfFirstFixedSurface = 0 Section 4: scaledValueOfFirstFixedSurface = 50000 Section 4: typeOfSecondFixedSurface = 255 - ['Missing', 'unknown'] Section 4: scaleFactorOfSecondFixedSurface = 0 Section 4: scaledValueOfSecondFixedSurface = 0 Section 5: dataRepresentationTemplateNumber = 3 - Grid Point Data - Complex Packing and Spatial Differencing (see Template 5.3) Section 5: numberOfPackedValues = 1038240 Section 5: typeOfValues = 0 - Floating Point Section 5: refValue = 482101.0625 Section 5: binScaleFactor = 1 Section 5: decScaleFactor = 2 Section 5: nBitsPacking = 13 Section 5: groupSplittingMethod = 1 - General Group Splitting Section 5: typeOfMissingValueManagement = 0 - No explicit missing values included within the data values Section 5: priMissingValue = None Section 5: secMissingValue = None Section 5: nGroups = 28103 Section 5: refGroupWidth = 0 Section 5: nBitsGroupWidth = 4 Section 5: refGroupLength = 1 Section 5: groupLengthIncrement = 1 Section 5: lengthOfLastGroup = 41 Section 5: nBitsScaledGroupLength = 7 Section 5: spatialDifferenceOrder = 2 - Second-Order Spatial Differencing Section 5: nBytesSpatialDifference = 2 Section 6: bitMapFlag = 255 - A bit map does not apply to this product.
__str__
¶The Grib2Message object provides a wgrib2-like 1-liner string for __str__
(as shown above). The Grib2Message object __repr__
will provide all metadata (see above).
print(msg)
348:d=2022-10-28 00:00:00:HGT:Geopotential Height (gpm):500 mb:6:00:00
print('Section 0: ',msg.section0)
print('Section 1: ',msg.section1)
print('Section 2: ',msg.section2)
print('Section 3: ',msg.section3)
print('Section 4: ',msg.section4)
print('Section 5: ',msg.section5)
Section 0: [1196575042 0 0 2 811530] Section 1: [ 7 0 2 1 1 2022 10 28 0 0 0 0 1] Section 2: b'' Section 3: [ 0 1038240 0 0 0 6 0 0 0 0 0 0 1440 721 0 -1 90000000 0 48 -90000000 359750000 250000 250000 0] Section 4: [ 0 0 3 5 2 0 96 0 0 1 6 100 0 50000 255 0 0] Section 5: [ 1038240 3 1223386786 1 2 13 0 1 0 1649987994 4294967295 28103 0 4 1 1 41 7 2 2]
The Grib2Message class is dynamically created and is unique to GRIB2's grid definition, production definition, and data representation template numbers. These templates contain metadata attributes unique to the definition of the given template.
Here we can use the class method resolution order (__mro__
) to show the various classes that make up the Grib2Message class.
for c in msg.__class__.__mro__:
print(c)
<class 'grib2io._grib2io.Grib2Message.__new__.<locals>.Msg'> <class 'grib2io._grib2io._Grib2Message'> <class 'grib2io.templates.GridDefinitionTemplate0'> <class 'grib2io.templates.ProductDefinitionTemplate0'> <class 'grib2io.templates.DataRepresentationTemplate3'> <class 'object'>
The template classes listed above contain the attributes as class variables that are unique to its template.
You will notice that the ProductDefinitionTemplate0
is blank. This is because it contains the common attributes used by the rest of the PTDNs, so they are defined in class _Grib2Message
.
print(grib2io.templates.GridDefinitionTemplate0._attrs)
print(grib2io.templates.ProductDefinitionTemplate0._attrs)
print(grib2io.templates.DataRepresentationTemplate3._attrs)
['latitudeFirstGridpoint', 'longitudeFirstGridpoint', 'latitudeLastGridpoint', 'longitudeLastGridpoint', 'gridlengthXDirection', 'gridlengthYDirection'] ['parameterCategory', 'parameterNumber', 'typeOfGeneratingProcess', 'generatingProcess', 'backgroundGeneratingProcessIdentifier', 'hoursAfterDataCutoff', 'minutesAfterDataCutoff', 'unitOfTimeRange', 'forecastTime', 'typeOfFirstFixedSurface', 'scaleFactorOfFirstFixedSurface', 'scaledValueOfFirstFixedSurface', 'typeOfSecondFixedSurface', 'scaleFactorOfSecondFixedSurface', 'scaledValueOfSecondFixedSurface'] ['refValue', 'binScaleFactor', 'decScaleFactor', 'nBitsPacking', 'groupSplittingMethod', 'typeOfMissingValue', 'priMissingValue', 'secMissingValue', 'nGroups', 'refGroupWidth', 'nBitsGroupWidth', 'refGroupLength', 'groupLengthIncrement', 'lengthOfLastGroup', 'nBitsScaledGroupLength', 'spatialDifferenceOrder', 'nBytesSpatialDifference']
Now lets take a look at how the metadata attributes are linked to their origin locations in a given section. Here we will look at the Grib2Message refDate
attribute, whose components are stored in Section 1.
To reiterate, msg.refDate
does not hold a value that would be a datetime.datetime
object. The refDate
attribute is defined as a descriptor class, specifically grib2io.templates.RefDate()
, whose __get__
protocol returns a datetime.datetime
object generated from the date components in section 1.
print(msg.section1)
print(msg.refDate)
print(msg.year,msg.month,msg.day,msg.hour,msg.minute,msg.second)
[ 7 0 2 1 1 2022 10 28 0 0 0 0 1] 2022-10-28 00:00:00 2022 10 28 0 0 0
To demonstrate that these attributes are dynamically created, lets change the year and minute. We do this by setting the attributes to new values. You will see that values have changed in section1 and other attributes.
msg.year = 2023
msg.minute = 55
print(msg.section1)
print(msg.refDate)
[ 7 0 2 1 1 2023 10 28 0 55 0 0 1] 2023-10-28 00:55:00
IMPORTANT: In grib2io v2.0, all date and time attributes are datetime.datetime
or datetime.timedelta
objects. Let use precipitation as an example.
msg = g['APCP'][0]
print(msg)
print(msg.refDate,type(msg.refDate))
print(msg.validDate,type(msg.validDate))
print(msg.leadTime,type(msg.leadTime))
print(msg.duration,type(msg.duration))
595:d=2022-10-28 00:00:00:APCP:Total Precipitation (kg m-2):surface:6:00:00 2022-10-28 00:00:00 <class 'datetime.datetime'> 2022-10-28 06:00:00 <class 'datetime.datetime'> 6:00:00 <class 'datetime.timedelta'> 6:00:00 <class 'datetime.timedelta'>
This allows for datetime arithmetic
The Grib2Message object defines the property pdy
that provides the reference date as a string in 'YYYYMMDD'
format.
print(msg.refDate + msg.leadTime)
print(msg.validDate)
print(msg.refDate+msg.leadTime==msg.validDate)
print(msg.pdy)
2022-10-28 06:00:00 2022-10-28 06:00:00 True 20221028
The Grib2Message object provides a method, attrs_by_section()
to get attribute names and values (if desired) for a given GRIB2 section.
for k,v in msg.attrs_by_section(3,values=True).items():
print(k,v)
gridDefinitionTemplateNumber 0 - Latitude/Longitude shapeOfEarth 6 - Earth assumed spherical with radius = 6,371,229.0 m earthRadius 6371229.0 earthMajorAxis None earthMinorAxis None resolutionAndComponentFlags [0, 0, 1, 1, 0, 0, 0, 0] ny 721 nx 1440 scanModeFlags [0, 0, 0, 0] latitudeFirstGridpoint 90.0 longitudeFirstGridpoint 0.0 latitudeLastGridpoint -90.0 longitudeLastGridpoint 359.75 gridlengthXDirection 0.25 gridlengthYDirection -0.25
Any GRIB2 metadata attribute that is a code value means that it is associated with a code table. That attribute is likely to be of type grib2io.templates.Grib2Metadata
which is a class to hold the code value, the definition given its lookup table.
Let use the GRIB2 metadata attribute discipline
as an example.
print(type(msg.discipline))
print(msg.discipline)
print(msg.discipline.value)
print(msg.discipline.definition)
print(msg.discipline.table)
<class 'grib2io.templates.Grib2Metadata'> 0 - Meteorological Products 0 Meteorological Products 0.0
grib2io contains the NCEP GRIB2 Tables stored as python dictionaries in grib2io.tables
. You are able to access them directly,
print(grib2io.tables.section0.table_0_0)
print(grib2io.tables.section4.table_4_6)
{'0': 'Meteorological Products', '1': 'Hydrological Products', '2': 'Land Surface Products', '3': 'Satellite Remote Sensing Products', '4': 'Space Weather Products', '5-9': 'Reserved', '10': 'Oceanographic Products', '11-19': 'Reserved', '20': 'Health and Socioeconomic Impacts', '21-191': 'Reserved', '192-254': 'Reserved for Local Use', '255': 'Missing'} {'0': 'Unperturbed High-Resolution Control Forecast', '1': 'Unperturbed Low-Resolution Control Forecast', '2': 'Negatively Perturbed Forecast', '3': 'Positively Perturbed Forecast', '4': 'Multi-Model Forecast', '5-191': 'Reserved', '192-254': 'Reserved for Local Use', '192': 'Perturbed Ensemble Member', '255': 'Missing'}
However, it is more useful to use the various helper functions in grib2io.tables
to view the NCEP GRIB2 code tables and lookup values.
for k,v in grib2io.tables.get_table('0.0').items():
print(f'{k}: {v}')
0: Meteorological Products 1: Hydrological Products 2: Land Surface Products 3: Satellite Remote Sensing Products 4: Space Weather Products 5-9: Reserved 10: Oceanographic Products 11-19: Reserved 20: Health and Socioeconomic Impacts 21-191: Reserved 192-254: Reserved for Local Use 255: Missing
print('Value 2 from table 4.6 means',grib2io.tables.get_value_from_table(2,'4.6'))
Value 2 from table 4.6 means Negatively Perturbed Forecast
discipline, parmcat, parmnum = 0, 1, 10
varinfo = grib2io.tables.get_varinfo_from_table(discipline,parmcat,parmnum)
print(f'Discipline value of {discipline} with paramater category and number of {parmcat} and {parmnum} defines variable {varinfo}')
Discipline value of 0 with paramater category and number of 1 and 10 defines variable ['Convective Precipitation', 'kg m-2', 'ACPCP']
Some metadata attributes exist for more easier understanding of information from the Grib2Message.
The level
attribute will provide a layer/level string that is exactly the same as wgrib2 and is generated by grib2io.tables.get_wgrib2_level_string()
.
print(msg.level)
print(msg.units) # Units of the variable
print(msg.unitOfFirstFixedSurface) # Units of the level (or layer)
surface kg m-2 unknown
Lets go back to the 500 mb HGT example.
Data are accessible via the data
attribute.
IMPORTANT: If you unfamiliar with Python/NumPy, by default, multi-dimensional array data are ordered in row-major order. This is how C/C++ order array data in memory. Fortran orders array data in column-major order. So 2D array will be ordered (ny, nx).
msg = g.select(shortName='HGT',level='500 mb')[0]
print(msg)
hgtdata = msg.data
print(hgtdata)
print(hgtdata.shape)
348:d=2023-10-28 00:55:00:HGT:Geopotential Height (gpm):500 mb:6:00:00 [[5097.0503 5097.0503 5097.0503 ... 5097.0503 5097.0503 5097.0503] [5095.2505 5095.2505 5095.2505 ... 5095.2505 5095.2505 5095.2505] [5092.9307 5092.9307 5092.9307 ... 5092.9106 5092.9106 5092.9106] ... [5086.5303 5086.5107 5086.5107 ... 5086.5703 5086.5503 5086.5303] [5086.4106 5086.4106 5086.3906 ... 5086.4307 5086.4307 5086.4106] [5085.7905 5085.7905 5085.7905 ... 5085.7905 5085.7905 5085.7905]] (721, 1440)
GRIB2 Section 3 contains all of the geospatial information about the message.
print(msg.section3)
for k,v in msg.attrs_by_section(3,values=True).items():
print(f'{k}: {v}')
[ 0 1038240 0 0 0 6 0 0 0 0 0 0 1440 721 0 -1 90000000 0 48 -90000000 359750000 250000 250000 0] gridDefinitionTemplateNumber: 0 - Latitude/Longitude shapeOfEarth: 6 - Earth assumed spherical with radius = 6,371,229.0 m earthRadius: 6371229.0 earthMajorAxis: None earthMinorAxis: None resolutionAndComponentFlags: [0, 0, 1, 1, 0, 0, 0, 0] ny: 721 nx: 1440 scanModeFlags: [0, 0, 0, 0] latitudeFirstGridpoint: 90.0 longitudeFirstGridpoint: 0.0 latitudeLastGridpoint: -90.0 longitudeLastGridpoint: 359.75 gridlengthXDirection: 0.25 gridlengthYDirection: -0.25
grib2io also provides the coorindate system in terms of PROJ parameters.
print(msg.projParameters)
{'a': 6371229.0, 'b': 6371229.0, 'proj': 'longlat'}
Latitude and Longitude values can be generated via grid()
or latlons()
methods.
lats, lons = msg.grid()
print(lats)
print(lons)
msg.grid() == msg.latlons() # latlons() is an alias of grid()
[[ 90. 90. 90. ... 90. 90. 90. ] [ 89.75 89.75 89.75 ... 89.75 89.75 89.75] [ 89.5 89.5 89.5 ... 89.5 89.5 89.5 ] ... [-89.5 -89.5 -89.5 ... -89.5 -89.5 -89.5 ] [-89.75 -89.75 -89.75 ... -89.75 -89.75 -89.75] [-90. -90. -90. ... -90. -90. -90. ]] [[0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02] [0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02] [0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02] ... [0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02] [0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02] [0.0000e+00 2.5000e-01 5.0000e-01 ... 3.5925e+02 3.5950e+02 3.5975e+02]]
True
NEW In grib2io v2.0, computed lats and lons are stored so they do not have to regenerated if you call the methods from another message with the same coordinate system. This is determined by taking the SHA-1 of section 3 and storing that as a key in a dictionary where lats and lons are the values.
print(msg._sha1_section3)
print(grib2io._grib2io._latlon_datastore)
e8e4ad7e93f0f476804d00043204634232ebe9aa {'e8e4ad7e93f0f476804d00043204634232ebe9aa': (array([[ 90. , 90. , 90. , ..., 90. , 90. , 90. ], [ 89.75, 89.75, 89.75, ..., 89.75, 89.75, 89.75], [ 89.5 , 89.5 , 89.5 , ..., 89.5 , 89.5 , 89.5 ], ..., [-89.5 , -89.5 , -89.5 , ..., -89.5 , -89.5 , -89.5 ], [-89.75, -89.75, -89.75, ..., -89.75, -89.75, -89.75], [-90. , -90. , -90. , ..., -90. , -90. , -90. ]]), array([[0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02], [0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02], [0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02], ..., [0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02], [0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02], [0.0000e+00, 2.5000e-01, 5.0000e-01, ..., 3.5925e+02, 3.5950e+02, 3.5975e+02]]))}
NEW In grib2io v2.0 is the ability to perform spatial interpolation from one grid to another by interfacing to the NCEPLIBS-ip library.
The Grib2Message object already contains the necessary information about the input grid. So all we need to go here is to define the output grid (i.e. the grid that we want to interpolate to). We do that by specifcy the GRIB2 Grid Definition Template Number and Template values. Here we will interpolate to the NBM CONUS 2.5km Lambert Conformal grid.
Here we place the template number and template into a Grib2GridDef
object.
nbm_co_gdtn = 30
nbm_co_gdt = [ 1, 0, 6371200, 255, 255, 255,
255, 2345, 1597, 19229000, 233723400, 48,
25000000, 265000000, 2539703, 2539703, 0, 80,
25000000, 25000000, -90000000, 0]
grid_def_out = grib2io.Grib2GridDef(nbm_co_gdtn,nbm_co_gdt)
print(grid_def_out.gdtn)
print(grid_def_out.gdt)
30 [1, 0, 6371200, 255, 255, 255, 255, 2345, 1597, 19229000, 233723400, 48, 25000000, 265000000, 2539703, 2539703, 0, 80, 25000000, 25000000, -90000000, 0]
To interpolate a Grib2Message object, call the interpolate()
method. The return from the method is a new Grib2Message object. All metadata remain the same except for section 3 metadata.
NOTE 1: The interpolation option can be a string or numeric value determined by the NCEPLIBS-ip library.
NOTE 2: The message number of interpolated messages is -1. This is becasue this message does not originate from a physical file. Any Grib2Message object created from scatch will have message number of -1 until it is associated with a file object.
new = msg.interpolate('bilinear',grid_def_out)
print(new)
print(new.section3)
for k,v in msg.attrs_by_section(3,values=True).items():
print(f'{k}: {v}')
print(new.data)
-1:d=2023-10-28 00:55:00:HGT:Geopotential Height (gpm):500 mb:6:00:00 [ 0 3744965 0 255 30 1 0 6371200 255 255 255 255 2345 1597 19229000 233723400 48 25000000 265000000 2539703 2539703 0 80 25000000 25000000 -90000000 0] gridDefinitionTemplateNumber: 0 - Latitude/Longitude shapeOfEarth: 6 - Earth assumed spherical with radius = 6,371,229.0 m earthRadius: 6371229.0 earthMajorAxis: None earthMinorAxis: None resolutionAndComponentFlags: [0, 0, 1, 1, 0, 0, 0, 0] ny: 721 nx: 1440 scanModeFlags: [0, 0, 0, 0] latitudeFirstGridpoint: 90.0 longitudeFirstGridpoint: 0.0 latitudeLastGridpoint: -90.0 longitudeLastGridpoint: 359.75 gridlengthXDirection: 0.25 gridlengthYDirection: -0.25 [[5885.6235 5885.614 5885.574 ... 5849.2563 5849.1816 5849.145 ] [5885.62 5885.6123 5885.5825 ... 5849.261 5849.1973 5849.1465] [5885.6167 5885.611 5885.592 ... 5849.278 5849.207 5849.1426] ... [5264.842 5264.8643 5264.8887 ... 5419.2915 5421.1416 5422.9863] [5264.0977 5264.1206 5264.1484 ... 5418.9565 5420.8115 5422.6636] [5263.35 5263.378 5263.4053 ... 5418.6245 5420.4863 5422.3438]]
The majority of the time, you will most likely have an existing Grib2Message object that you could use a guideline or template in building a new Grib2Message object. To demo, this we are going to use a bit of information from scratch and from an existing Grib2Message.
We will construct a Grib2Message object containing the 10-m Wind Speed from the U- and V-wind components.
uwind = g.select(shortName='UGRD',level='10 m above ground')[0]
vwind = g.select(shortName='VGRD',level='10 m above ground')[0]
print(uwind)
print(vwind)
587:d=2022-10-28 00:00:00:UGRD:U-Component of Wind (m s-1):10 m above ground:6:00:00 588:d=2022-10-28 00:00:00:VGRD:V-Component of Wind (m s-1):10 m above ground:6:00:00
Create new Grib2Message object.
Here we generate the message by only passing Grid Definition Template Number, Product Definition Template Number, and Data Representation Template Number which we can borrow from either of the uwind
or vwind
objects.
wspd = grib2io.Grib2Message(gdtn=uwind.gdtn,pdtn=uwind.pdtn,drtn=uwind.drtn)
print(wspd)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[36], line 2 1 wspd = grib2io.Grib2Message(gdtn=uwind.gdtn,pdtn=uwind.pdtn,drtn=uwind.drtn) ----> 2 print(wspd) File ~/Projects/GitHub-NOAA/grib2io/build/lib.linux-x86_64-cpython-311/grib2io/_grib2io.py:698, in _Grib2Message.__str__(self) 697 def __str__(self): --> 698 return (f'{self._msgnum}:d={self.refDate}:{self.shortName}:' 699 f'{self.fullName} ({self.units}):{self.level}:' 700 f'{self.leadTime}') File ~/Projects/GitHub-NOAA/grib2io/build/lib.linux-x86_64-cpython-311/grib2io/templates.py:188, in RefDate.__get__(self, obj, objtype) 187 def __get__(self, obj, objtype=None): --> 188 return datetime.datetime(*obj.section1[5:11]) ValueError: year 0 is out of range
Notice how printing the new object results in an error, specifically the refDate
. Since the GRIB2 message object was created from scratch it does have the appropriate metadata filled in yet.
Lets take at the sections 0-5. Lets get a list of attributes and then populate them. To perform this correctly, you need to some familiarity with the NCEP GRIB2 tables.
print(wspd.attrs_by_section(0))
wspd.discipline = 0 # 0 = Meteorological Products
print(wspd.attrs_by_section(0, values=True))
print(wspd.section0)
['discipline'] {'discipline': Grib2Metadata(0, table = '0.0')} [1196575042 0 0 2 0]
Here we are going to cheat a bit by populating section 1 attributes from the uwind variable. The following simulates populating the attributes by hand
for attr in wspd.attrs_by_section(1):
try:
cmd = 'wspd.'+attr+' = uwind.'+attr+'.value'
exec(cmd)
except(AttributeError):
cmd = cmd.replace('.value','')
exec(cmd)
for k,v in wspd.attrs_by_section(1,values=True).items():
print(f'{k}: {v}')
originatingCenter: 7 - US National Weather Service - NCEP (WMC) originatingSubCenter: 0 - None masterTableInfo: 2 - Version Implemented on 4 November 2003 localTableInfo: 1 - Number of local table version used. significanceOfReferenceTime: 1 - Start of Forecast year: 2022 month: 10 day: 28 hour: 0 minute: 0 second: 0 refDate: 2022-10-28 00:00:00 productionStatus: 0 - Operational Products typeOfData: 1 - Forecast Products
For the rest of the sections, we will simply populate from uwind
variable and adjust as needed.
wspd.section3 = uwind.section3
for k,v in wspd.attrs_by_section(3,values=True).items():
print(f'{k}: {v}')
gridDefinitionTemplateNumber: 0 - Latitude/Longitude shapeOfEarth: 6 - Earth assumed spherical with radius = 6,371,229.0 m earthRadius: 6371229.0 earthMajorAxis: None earthMinorAxis: None resolutionAndComponentFlags: [0, 0, 1, 1, 0, 0, 0, 0] ny: 721 nx: 1440 scanModeFlags: [0, 0, 0, 0] latitudeFirstGridpoint: 90.0 longitudeFirstGridpoint: 0.0 latitudeLastGridpoint: -90.0 longitudeLastGridpoint: 359.75 gridlengthXDirection: 0.25 gridlengthYDirection: -0.25
Here we need to modify the parameter Category and Number to define the Wind Speed
wspd.section4 = uwind.section4
wspd.parameterCategory = 2 # Momentum, https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-1.shtml
wspd.parameterNumber = 1 # Wind Speed, https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-2-0-2.shtml
for k,v in wspd.attrs_by_section(4,values=True).items():
print(f'{k}: {v}')
productDefinitionTemplateNumber: 0 - Analysis or forecast at a horizontal level or in a horizontal layer at a point in time. (see Template 4.0) fullName: Wind Speed units: m s-1 shortName: WIND leadTime: 6:00:00 unitOfFirstFixedSurface: m valueOfFirstFixedSurface: 10.0 unitOfSecondFixedSurface: None valueOfSecondFixedSurface: 10.0 validDate: 2022-10-28 06:00:00 duration: None level: 10 m above ground parameterCategory: 2 parameterNumber: 1 typeOfGeneratingProcess: 2 - Forecast generatingProcess: 96 - Global Forecast System Model T1534 - Forecast hours 00-384 T574 - Forecast hours 00-192 T190 - Forecast hours 204-384 backgroundGeneratingProcessIdentifier: 0 hoursAfterDataCutoff: 0 minutesAfterDataCutoff: 0 unitOfTimeRange: 1 - Hour forecastTime: 6 typeOfFirstFixedSurface: 103 - ['Specified Height Level Above Ground', 'm'] scaleFactorOfFirstFixedSurface: 0 scaledValueOfFirstFixedSurface: 10 typeOfSecondFixedSurface: 255 - ['Missing', 'unknown'] scaleFactorOfSecondFixedSurface: 0 scaledValueOfSecondFixedSurface: 0
wspd.section5 = uwind.section5
for k,v in wspd.attrs_by_section(5,values=True).items():
print(f'{k}: {v}')
print(wspd.section5)
dataRepresentationTemplateNumber: 3 - Grid Point Data - Complex Packing and Spatial Differencing (see Template 5.3) numberOfPackedValues: 1038240 typeOfValues: 0 - Floating Point refValue: -2310.03564453125 binScaleFactor: 0 decScaleFactor: 2 nBitsPacking: 12 groupSplittingMethod: 1 - General Group Splitting typeOfMissingValue: 0 - No explicit missing values included within the data values priMissingValue: None secMissingValue: None nGroups: 30222 refGroupWidth: 0 nBitsGroupWidth: 4 refGroupLength: 1 groupLengthIncrement: 1 lengthOfLastGroup: 37 nBitsScaledGroupLength: 7 spatialDifferenceOrder: 2 - Second-Order Spatial Differencing nBytesSpatialDifference: 2 [ 1038240 3 3306184850 0 2 12 0 1 0 1649987994 4294967295 30222 0 4 1 1 37 7 2 2]
IMPORANT: So we set section 5 of the wspd
variable to section 5 of uwind
variable. This might look harmless, but notice that there is specific information in section 5 related to the packing of the U-wind data. Having this data present in section 5 for wspd
could lead to undesirable effects when packing. The majority of attributes in section 5 will be set by the packing code. So the following will we will sanitze section 5.
NOTE: The sanitization will likely become an under-the-hood feature of grib2io.
wspd.section5 = np.zeros(wspd.section5.shape,dtype=wspd.section5.dtype)
wspd.section5[0] = wspd.nx * wspd.ny
wspd.section5[1] = uwind.dataRepresentationTemplateNumber.value
wspd.typeOfValues = 0 # Float
wspd.binScaleFactor = 0 # Binary Scale Factor
wspd.decScaleFactor = 3 # Decimal Scale Factor
wspd.groupSplittingMethod = 1 # Group Split
wspd.spatialDifferenceOrder = 2 # Second order spatial differencing
print(wspd.section5)
[1038240 3 0 0 3 0 0 1 0 0 0 0 0 0 0 0 0 0 2 0]
We have now defined the packing of the wspd
variable. We will use the same packing scheme as the uwind
(Complex Packing with Spatial Differencing)
First we need to compute the wind speed from the U- and V-Wind components.
wspd.data = np.sqrt(uwind.data**2.0+vwind.data**2.0)
print(np.amin(wspd.data),np.amax(wspd.data))
0.0025712133 26.308275
Next, we can call the pack()
method to pack the data and create the packed GRIB2 binary message.
wspd.pack()
We now have a packed GRIB2 message. It is stored in the Grib2Message object in the _msg
attribute. We can take a look at the first 4 and last 4 bytes of the packages message to see that it is a complete messages. The first 4 bytes should be 'GRIB'
and last 4 '7777'
.
NOTE: When we print the message, notice the message number of -1. This is because this Grib2Message object is not associated with a GRIB2 file object (grib2io.open
).
print(wspd._msg[0:4],wspd._msg[-4:])
print(wspd)
wspd
b'GRIB' b'7777' -1:d=2022-10-28 00:00:00:WIND:Wind Speed (m s-1):10 m above ground:6:00:00
Section 0: discipline = 0 - Meteorological Products Section 1: originatingCenter = 7 - US National Weather Service - NCEP (WMC) Section 1: originatingSubCenter = 0 - None Section 1: masterTableInfo = 2 - Version Implemented on 4 November 2003 Section 1: localTableInfo = 1 - Number of local table version used. Section 1: significanceOfReferenceTime = 1 - Start of Forecast Section 1: year = 2022 Section 1: month = 10 Section 1: day = 28 Section 1: hour = 0 Section 1: minute = 0 Section 1: second = 0 Section 1: refDate = 2022-10-28 00:00:00 Section 1: productionStatus = 0 - Operational Products Section 1: typeOfData = 1 - Forecast Products Section 3: gridDefinitionTemplateNumber = 0 - Latitude/Longitude Section 3: shapeOfEarth = 6 - Earth assumed spherical with radius = 6,371,229.0 m Section 3: earthRadius = 6371229.0 Section 3: earthMajorAxis = None Section 3: earthMinorAxis = None Section 3: resolutionAndComponentFlags = [0, 0, 1, 1, 0, 0, 0, 0] Section 3: ny = 721 Section 3: nx = 1440 Section 3: scanModeFlags = [0, 0, 0, 0] Section 3: latitudeFirstGridpoint = 90.0 Section 3: longitudeFirstGridpoint = 0.0 Section 3: latitudeLastGridpoint = -90.0 Section 3: longitudeLastGridpoint = 359.75 Section 3: gridlengthXDirection = 0.25 Section 3: gridlengthYDirection = -0.25 Section 4: productDefinitionTemplateNumber = 0 - Analysis or forecast at a horizontal level or in a horizontal layer at a point in time. (see Template 4.0) Section 4: fullName = Wind Speed Section 4: units = m s-1 Section 4: shortName = WIND Section 4: leadTime = 6:00:00 Section 4: unitOfFirstFixedSurface = m Section 4: valueOfFirstFixedSurface = 10.0 Section 4: unitOfSecondFixedSurface = None Section 4: valueOfSecondFixedSurface = 10.0 Section 4: validDate = 2022-10-28 06:00:00 Section 4: duration = None Section 4: level = 10 m above ground Section 4: parameterCategory = 2 Section 4: parameterNumber = 1 Section 4: typeOfGeneratingProcess = 2 - Forecast Section 4: generatingProcess = 96 - Global Forecast System Model T1534 - Forecast hours 00-384 T574 - Forecast hours 00-192 T190 - Forecast hours 204-384 Section 4: backgroundGeneratingProcessIdentifier = 0 Section 4: hoursAfterDataCutoff = 0 Section 4: minutesAfterDataCutoff = 0 Section 4: unitOfTimeRange = 1 - Hour Section 4: forecastTime = 6 Section 4: typeOfFirstFixedSurface = 103 - ['Specified Height Level Above Ground', 'm'] Section 4: scaleFactorOfFirstFixedSurface = 0 Section 4: scaledValueOfFirstFixedSurface = 10 Section 4: typeOfSecondFixedSurface = 255 - ['Missing', 'unknown'] Section 4: scaleFactorOfSecondFixedSurface = 0 Section 4: scaledValueOfSecondFixedSurface = 0 Section 5: dataRepresentationTemplateNumber = 3 - Grid Point Data - Complex Packing and Spatial Differencing (see Template 5.3) Section 5: numberOfPackedValues = 1038240 Section 5: typeOfValues = 0 - Floating Point Section 5: refValue = 3.0 Section 5: binScaleFactor = 0 Section 5: decScaleFactor = 3 Section 5: nBitsPacking = 15 Section 5: groupSplittingMethod = 1 - General Group Splitting Section 5: typeOfMissingValue = 0 - No explicit missing values included within the data values Section 5: priMissingValue = None Section 5: secMissingValue = None Section 5: nGroups = 74433 Section 5: refGroupWidth = 0 Section 5: nBitsGroupWidth = 5 Section 5: refGroupLength = 1 Section 5: groupLengthIncrement = 1 Section 5: lengthOfLastGroup = 14 Section 5: nBitsScaledGroupLength = 5 Section 5: spatialDifferenceOrder = 2 - Second-Order Spatial Differencing Section 5: nBytesSpatialDifference = 2 Section 6: bitMapFlag = 255 - A bit map does not apply to this product.
Now that we have created a new Grib2Message object, we can write that message to a new file.
Here we create a new GRIB2 file object to write to
gout = grib2io.open('wind-speed-demo.grib2',mode='w')
print(gout)
mode = wb name = /home/ericengle/Projects/GitHub-NOAA/grib2io/build/lib.linux-x86_64-cpython-311/wind-speed-demo.grib2 messages = 0 current_message = 0 size = 0 closed = False variables = None levels = None
We can use the write()
method to write a Grib2Message object to file. We will use the wind speed message we created above.
gout.write(wspd)
print(gout)
mode = wb name = /home/ericengle/Projects/GitHub-NOAA/grib2io/build/lib.linux-x86_64-cpython-311/wind-speed-demo.grib2 messages = 1 current_message = 1 size = 1464114 closed = False variables = None levels = None
NOTE: Even though we wrote to the new file, the variables
and levels
attributes have not been updated. This feature will be added in the near future.
gout.close()
g.close()