def set_data(source, grib): """ Sets the actual data of a grib message. """ var_name = get_varible_name(source) # treat masked arrays differently if isinstance(source[var_name].values, np.ma.core.MaskedArray): gribapi.grib_set(grib, "bitmapPresent", 1) # use the missing value from the masked array as default missing_value = source[var_name].values.get_fill_value() # but give the netCDF specified missing value preference missing_value = source[var_name].attrs.get('missing_value', missing_value) gribapi.grib_set_double(grib, "missingValue", float(missing_value)) data = source[var_name].values.filled() else: gribapi.grib_set_double(grib, "missingValue", 9999) data = source[var_name].values[:] gribapi.grib_set_long(grib, "bitsPerValue", 12) #gribapi.grib_set_long(grib, "bitsPerValueAndRepack", 12) gribapi.grib_set_long(grib, "decimalPrecision", 2) gribapi.grib_set_long(grib, "decimalScaleFactor", 2) #gribapi.grib_set_long(grib, "binaryScaleFactor", 0) gribapi.grib_set_long(grib, "dataRepresentationType", 0) # get the grib code for the variable code = reverse_codes[conv.to_grib1[var_name]] _, grib_unit = codes[code] # default to the grib default unit unit = source[var_name].attrs.get('units', grib_unit) mult = 1. if not unit == grib_unit: mult = units._speed[unit] / units._speed[grib_unit] # add the data gribapi.grib_set_double_array(grib, "values", mult * data.flatten())
def set_time_range(time_coord, grib): """ Set the time range keys in the specified message based on the bounds of the provided time coordinate. """ if len(time_coord.points) != 1: msg = 'Expected length one time coordinate, got {} points' raise ValueError(msg.format(len(time_coord.points))) if time_coord.nbounds != 2: msg = 'Expected time coordinate with two bounds, got {} bounds' raise ValueError(msg.format(time_coord.nbounds)) # Set type to hours and convert period to this unit. gribapi.grib_set(grib, "indicatorOfUnitForTimeRange", _TIME_RANGE_UNITS['hours']) hours_since_units = cf_units.Unit('hours since epoch', calendar=time_coord.units.calendar) start_hours, end_hours = time_coord.units.convert(time_coord.bounds[0], hours_since_units) # Cast from np.float to Python int. The lengthOfTimeRange key is a # 4 byte integer so we cast to highlight truncation of any floating # point value. The grib_api will do the cast from float to int, but it # cannot handle numpy floats. time_range_in_hours = round(end_hours) - round( start_hours) # required for NCMRWF integer_hours = round(time_range_in_hours) # required for NCMRWF if integer_hours != time_range_in_hours: msg = 'Truncating floating point lengthOfTimeRange {} to ' \ 'integer value {}' warnings.warn(msg.format(time_range_in_hours, integer_hours)) gribapi.grib_set(grib, "lengthOfTimeRange", integer_hours)
def tweak_grib_msg(variable, cube): """ Iterate through cubes and pair with grib messages (using reference table) before saving""" for cube, grib_message in iris_grib.save_pairs_from_cube(cube): gribapi.grib_set_long(grib_message, "centre", ec_grib_centre[variable][0][1]) gribapi.grib_set_long(grib_message, "discipline", ec_grib_centre[variable][1][1]) gribapi.grib_set_long(grib_message, "parameterCategory", ec_grib_centre[variable][1][2]) gribapi.grib_set_long(grib_message, "parameterNumber", ec_grib_centre[variable][1][3]) gribapi.grib_set_long(grib_message, "edition", ec_grib_centre[variable][2][1]) gribapi.grib_set(grib_message, "shortNameECMF", ec_grib_centre[variable][3][1]) gribapi.grib_set_long(grib_message, "indicatorOfParameter", ec_grib_centre[variable][4][1]) if (variable == 'V1' or variable == 'V2' or variable == 'V3' or variable == 'V4' or variable == 'T1' or variable == 'T2' or variable == 'T3' or variable == 'T4'): gribapi.grib_set_long(grib_message, "table2Version", ec_grib_centre[variable][5][1]) gribapi.grib_set_long(grib_message, "topLevel", ec_grib_centre[variable][6][1]) gribapi.grib_set_long(grib_message, "bottomLevel", ec_grib_centre[variable][7][1]) yield grib_message
def set_time_range(time_coord, grib): """ Set the time range keys in the specified message based on the bounds of the provided time coordinate. """ if len(time_coord.points) != 1: msg = 'Expected length one time coordinate, got {} points' raise ValueError(msg.format(len(time_coord.points))) if time_coord.nbounds != 2: msg = 'Expected time coordinate with two bounds, got {} bounds' raise ValueError(msg.format(time_coord.nbounds)) # Set type to hours and convert period to this unit. gribapi.grib_set(grib, "indicatorOfUnitForTimeRange", _TIME_RANGE_UNITS['hours']) hours_since_units = cf_units.Unit('hours since epoch', calendar=time_coord.units.calendar) start_hours, end_hours = time_coord.units.convert(time_coord.bounds[0], hours_since_units) # Cast from np.float to Python int. The lengthOfTimeRange key is a # 4 byte integer so we cast to highlight truncation of any floating # point value. The grib_api will do the cast from float to int, but it # cannot handle numpy floats. time_range_in_hours = end_hours - start_hours integer_hours = int(time_range_in_hours) if integer_hours != time_range_in_hours: msg = 'Truncating floating point lengthOfTimeRange {} to ' \ 'integer value {}' warnings.warn(msg.format(time_range_in_hours, integer_hours)) gribapi.grib_set(grib, "lengthOfTimeRange", integer_hours)
def product_definition_template_8(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.8. Template 4.8 is used to represent an aggregation over a time interval. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 8) _product_definition_template_8_and_11(cube, grib)
def ensure_set_int32_value(grib, key, value): """ Ensure the workaround function :func:`fixup_int32_as_uint32` is applied as necessary to problem keys. """ try: gribapi.grib_set(grib, key, value) except gribapi.GribInternalError: value = fixup_int32_as_uint32(value) gribapi.grib_set(grib, key, value)
def product_definition_template_8(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.8. Template 4.8 is used to represent an aggregation over a time interval. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 8) _product_definition_template_8_10_and_11(cube, grib)
def __setitem__(self, key, value): """ Set value associated with key. If the object is iterable, """ # Passed value is iterable and not string if (isinstance(value, collections.Iterable) and not isinstance(value, basestring)): gribapi.grib_set_array(self.gid, key, value) else: gribapi.grib_set(self.gid, key, value)
def product_definition_template_11(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.11. Template 4.11 is used to represent an aggregation over a time interval for an ensemble member. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 11) set_ensemble(cube, grib) _product_definition_template_8_10_and_11(cube, grib)
def product_definition_template_1(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.1. Template 4.1 is used to represent an individual ensemble forecast, control and perturbed, at a horizontal level or in a horizontal layer at a point in time. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 1) product_definition_template_common(cube, grib) set_ensemble(cube, grib)
def product_definition_template_40(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.40. Template 4.40 is used to represent an analysis or forecast at a horizontal level or in a horizontal layer at a point in time for atmospheric chemical constituents. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 40) product_definition_template_common(cube, grib) constituent_type = cube.attributes['WMO_constituent_type'] gribapi.grib_set(grib, "constituentType", constituent_type)
def product_definition_template_10(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.10. Template 4.10 is used to represent a percentile forecast over a time interval. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 10) if not (cube.coords('percentile_over_time') and len(cube.coord('percentile_over_time').points) == 1): raise ValueError("A cube 'percentile_over_time' coordinate with one " "point is required, but not present.") gribapi.grib_set(grib, "percentileValue", int(cube.coord('percentile_over_time').points[0])) _product_definition_template_8_10_and_11(cube, grib)
def __setitem__(self, key, value): """ Set value associated with key. If the object is iterable, """ # Alternative implemented (TODO: evaluate) # if gribapi.grib_get_size(self.gid, key) > 1: # gribapi.grib_set_array(self.gid, key, value) # else: # gribapi.grib_set(self.gid, key, value) # Passed value is iterable and not string if (isinstance(value, collections.Iterable) and not isinstance(value, basestring)): gribapi.grib_set_array(self.gid, key, value) else: gribapi.grib_set(self.gid, key, value)
def dx_dy(x_coord, y_coord, grib): x_step = regular_step(x_coord) y_step = regular_step(y_coord) # Set x and y step. For degrees, this is encoded as an integer: # 1 * 10^6 * floating point value. # WMO Manual on Codes regulation 92.1.6 if x_coord.units == 'degrees': gribapi.grib_set(grib, "iDirectionIncrement", round(1e6 * float(abs(x_step)))) else: raise ValueError('X coordinate must be in degrees, not {}' '.'.format(x_coord.units)) if y_coord.units == 'degrees': gribapi.grib_set(grib, "jDirectionIncrement", round(1e6 * float(abs(y_step)))) else: raise ValueError('Y coordinate must be in degrees, not {}' '.'.format(y_coord.units))
def set_forecast_time(cube, grib): """ Set the forecast time keys based on the forecast_period coordinate. In the absence of a forecast_period and forecast_reference_time, the forecast time is set to zero. """ try: fp_coord = cube.coord("forecast_period") except iris.exceptions.CoordinateNotFoundError: fp_coord = None if fp_coord is not None: _, _, fp, grib_time_code = _non_missing_forecast_period(cube) else: _, _, fp, grib_time_code = _missing_forecast_period(cube) gribapi.grib_set(grib, "indicatorOfUnitOfTimeRange", grib_time_code) gribapi.grib_set(grib, "forecastTime", fp)
def data_section(cube, grib): # Masked data? if isinstance(cube.data, ma.core.MaskedArray): # What missing value shall we use? if not np.isnan(cube.data.fill_value): # Use the data's fill value. fill_value = float(cube.data.fill_value) else: # We can't use the data's fill value if it's NaN, # the GRIB API doesn't like it. # Calculate an MDI outside the data range. min, max = cube.data.min(), cube.data.max() fill_value = min - (max - min) * 0.1 # Prepare the unmaksed data array, using fill_value as the MDI. data = cube.data.filled(fill_value) else: fill_value = None data = cube.data # units scaling grib2_info = gptx.cf_phenom_to_grib2_info(cube.standard_name, cube.long_name) if grib2_info is None: # for now, just allow this warnings.warn('Unable to determine Grib2 parameter code for cube.\n' 'Message data may not be correctly scaled.') else: if cube.units != grib2_info.units: data = cube.units.convert(data, grib2_info.units) if fill_value is not None: fill_value = cube.units.convert(fill_value, grib2_info.units) if fill_value is None: # Disable missing values in the grib message. gribapi.grib_set(grib, "bitmapPresent", 0) else: # Enable missing values in the grib message. gribapi.grib_set(grib, "bitmapPresent", 1) gribapi.grib_set_double(grib, "missingValue", fill_value) gribapi.grib_set_double_array(grib, "values", data.flatten())
def grid_definition_template_5(cube, grib): """ Set keys within the provided grib message based on Grid Definition Template 3.5. Template 3.5 is used to represent "variable resolution rotated latitude/longitude". The coordinates are irregularly spaced, rotated latitudes and longitudes. """ # NOTE: we must set Ni=Nj=1 before establishing the template. # Without this, setting "gridDefinitionTemplateNumber" = 5 causes an # immediate error. # See: https://software.ecmwf.int/issues/browse/SUP-1095 # This is acceptable, as the subsequent call to 'horizontal_grid_common' # will set these to the correct horizontal dimensions # (by calling 'grid_dims'). gribapi.grib_set(grib, "Ni", 1) gribapi.grib_set(grib, "Nj", 1) gribapi.grib_set(grib, "gridDefinitionTemplateNumber", 5) # Record details of the rotated coordinate system. rotated_pole(cube, grib) # Encode the lat/lon points. horizontal_grid_common(cube, grib) latlon_points_irregular(cube, grib)
def data(cube, grib): # mdi if isinstance(cube.data, ma.core.MaskedArray): gribapi.grib_set(grib, "bitmapPresent", 1) gribapi.grib_set_double(grib, "missingValue", float(cube.data.fill_value)) data = cube.data.filled() else: gribapi.grib_set_double(grib, "missingValue", float(-1e9)) data = cube.data # units scaling grib2_info = gptx.cf_phenom_to_grib2_info(cube.standard_name, cube.long_name) if grib2_info is None: # for now, just allow this warnings.warn('Unable to determine Grib2 parameter code for cube.\n' 'Message data may not be correctly scaled.') else: if cube.units != grib2_info.units: data = cube.units.convert(data, grib2_info.units) # values gribapi.grib_set_double_array(grib, "values", data.flatten())
def latlon_points_irregular(cube, grib): y_coord = cube.coord(dimensions=[0]) x_coord = cube.coord(dimensions=[1]) # Distinguish between true-north and grid-oriented vectors. is_grid_wind = cube.name() in ('x_wind', 'y_wind', 'grid_eastward_wind', 'grid_northward_wind') # Encode in bit "5" of 'resolutionAndComponentFlags' (other bits unused). component_flags = 0 if is_grid_wind: component_flags |= 2**_RESOLUTION_AND_COMPONENTS_GRID_WINDS_BIT gribapi.grib_set(grib, 'resolutionAndComponentFlags', component_flags) # Record the X and Y coordinate values. # NOTE: there is currently a bug in the gribapi which means that the size # of the longitudes array does not equal 'Nj', as it should. # See : https://software.ecmwf.int/issues/browse/SUP-1096 # So, this only works at present if the x and y dimensions are **equal**. lon_values = x_coord.points / _DEFAULT_DEGREES_UNITS lat_values = y_coord.points / _DEFAULT_DEGREES_UNITS gribapi.grib_set_array(grib, 'longitudes', np.array(np.round(lon_values), dtype=np.int64)) gribapi.grib_set_array(grib, 'latitudes', np.array(np.round(lat_values), dtype=np.int64))
def latlon_points_irregular(cube, grib): y_coord = cube.coord(dimensions=[0]) x_coord = cube.coord(dimensions=[1]) # Distinguish between true-north and grid-oriented vectors. is_grid_wind = cube.name() in ('x_wind', 'y_wind', 'grid_eastward_wind', 'grid_northward_wind') # Encode in bit "5" of 'resolutionAndComponentFlags' (other bits unused). component_flags = 0 if is_grid_wind: component_flags |= 2 ** _RESOLUTION_AND_COMPONENTS_GRID_WINDS_BIT gribapi.grib_set(grib, 'resolutionAndComponentFlags', component_flags) # Record the X and Y coordinate values. # NOTE: there is currently a bug in the gribapi which means that the size # of the longitudes array does not equal 'Nj', as it should. # See : https://software.ecmwf.int/issues/browse/SUP-1096 # So, this only works at present if the x and y dimensions are **equal**. lon_values = x_coord.points / _DEFAULT_DEGREES_UNITS lat_values = y_coord.points / _DEFAULT_DEGREES_UNITS gribapi.grib_set_array(grib, 'longitudes', np.array(np.round(lon_values), dtype=np.int64)) gribapi.grib_set_array(grib, 'latitudes', np.array(np.round(lat_values), dtype=np.int64))
def product_definition_template_11(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.8. Template 4.8 is used to represent an aggregation over a time interval. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 11) if not (cube.coords('realization') and len(cube.coord('realization').points) == 1): raise ValueError("A cube 'realization' coordinate with one" "point is required, but not present") gribapi.grib_set(grib, "perturbationNumber", int(cube.coord('realization').points[0])) # no encoding at present in Iris, set to missing gribapi.grib_set(grib, "numberOfForecastsInEnsemble", 255) gribapi.grib_set(grib, "typeOfEnsembleForecast", 255) _product_definition_template_8_and_11(cube, grib)
def product_definition_template_11(cube, grib): """ Set keys within the provided grib message based on Product Definition Template 4.11. Template 4.11 is used to represent an aggregation over a time interval for an ensemble member. """ gribapi.grib_set(grib, "productDefinitionTemplateNumber", 11) if not (cube.coords('realization') and len(cube.coord('realization').points) == 1): raise ValueError("A cube 'realization' coordinate with one" "point is required, but not present") gribapi.grib_set(grib, "perturbationNumber", int(cube.coord('realization').points[0])) # no encoding at present in iris-grib, set to missing gribapi.grib_set(grib, "numberOfForecastsInEnsemble", 255) gribapi.grib_set(grib, "typeOfEnsembleForecast", 255) _product_definition_template_8_10_and_11(cube, grib)
def set_ensemble(cube, grib): """ Set keys in the provided grib based message relating to ensemble information. """ if not (cube.coords('realization') and len(cube.coord('realization').points) == 1): raise ValueError("A cube 'realization' coordinate with one " "point is required, but not present") gribapi.grib_set(grib, "perturbationNumber", int(cube.coord('realization').points[0])) # no encoding at present in iris, set to missing gribapi.grib_set(grib, "numberOfForecastsInEnsemble", 255) gribapi.grib_set(grib, "typeOfEnsembleForecast", 255)
def product_definition_template_common(cube, grib): """ Set keys within the provided grib message that are common across all of the supported product definition templates. """ set_discipline_and_parameter(cube, grib) # Various missing values. gribapi.grib_set(grib, "typeOfGeneratingProcess", 255) gribapi.grib_set(grib, "backgroundProcess", 255) gribapi.grib_set(grib, "generatingProcessIdentifier", 255) # Generic time handling. set_forecast_time(cube, grib) # Handle vertical coords. set_fixed_surfaces(cube, grib)
def product_definition_template_common(cube, grib): """ Set keys within the provided grib message that are common across all of the supported product definition templates. """ set_discipline_and_parameter(cube, grib) # http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table4-3.shtml 2 would be applicable even for analysis (since analysis also short forecast) gribapi.grib_set(grib, "typeOfGeneratingProcess", 2) # required for NCMRWF gribapi.grib_set(grib, "backgroundProcess", 255) # set missing values. # http://www.nco.ncep.noaa.gov/pmb/docs/on388/tablea.html 96 would be more appropriate gribapi.grib_set(grib, "generatingProcessIdentifier", 96) # required for NCMRWF # Generic time handling. set_forecast_time(cube, grib) # Handle vertical coords. set_fixed_surfaces(cube, grib)
def rotated_pole(cube, grib): # Grib encoding of a rotated pole coordinate system. cs = cube.coord(dimensions=[0]).coord_system if cs.north_pole_grid_longitude != 0.0: raise iris.exceptions.TranslationError( 'Grib save does not yet support Rotated-pole coordinates with ' 'a rotated prime meridian.') # XXX Pending #1125 # gribapi.grib_set_double(grib, "latitudeOfSouthernPoleInDegrees", # float(cs.n_pole.latitude)) # gribapi.grib_set_double(grib, "longitudeOfSouthernPoleInDegrees", # float(cs.n_pole.longitude)) # gribapi.grib_set_double(grib, "angleOfRotationInDegrees", 0) # WORKAROUND latitude = cs.grid_north_pole_latitude / _DEFAULT_DEGREES_UNITS longitude = (((cs.grid_north_pole_longitude + 180) % 360) / _DEFAULT_DEGREES_UNITS) gribapi.grib_set(grib, "latitudeOfSouthernPole", - int(round(latitude))) gribapi.grib_set(grib, "longitudeOfSouthernPole", int(round(longitude))) gribapi.grib_set(grib, "angleOfRotation", 0)
def set_time_increment(cell_method, grib): """ Set the time increment keys in the specified message based on the provided cell method. """ # Type of time increment, e.g incrementing forecast period, incrementing # forecast reference time, etc. Set to missing, but we could use the # cell method coord to infer a value (see code table 4.11). gribapi.grib_set(grib, "typeOfTimeIncrement", 255) # Default values for the time increment value and units type. inc = 0 units_type = 255 # Attempt to determine time increment from cell method intervals string. intervals = cell_method.intervals if intervals is not None and len(intervals) == 1: interval, = intervals try: inc, units = interval.split() inc = float(inc) if units in ('hr', 'hour', 'hours'): units_type = _TIME_RANGE_UNITS['hours'] else: raise ValueError('Unable to parse units of interval') except ValueError: # Problem interpreting the interval string. inc = 0 units_type = 255 else: # Cast to int as timeIncrement key is a 4 byte integer. integer_inc = int(inc) if integer_inc != inc: warnings.warn('Truncating floating point timeIncrement {} to ' 'integer value {}'.format(inc, integer_inc)) inc = integer_inc gribapi.grib_set(grib, "indicatorOfUnitForTimeIncrement", units_type) gribapi.grib_set(grib, "timeIncrement", inc)
def set_time_increment(cell_method, grib): """ Set the time increment keys in the specified message based on the provided cell method. """ # Type of time increment, e.g incrementing forecast period, incrementing # forecast reference time, etc. Set to missing, but we could use the # cell method coord to infer a value (see code table 4.11). gribapi.grib_set(grib, "typeOfTimeIncrement", 255) # Default values for the time increment value and units type. inc = 0 units_type = 255 # Attempt to determine time increment from cell method intervals string. intervals = cell_method.intervals if intervals is not None and len(intervals) == 1: interval, = intervals try: inc, units = interval.split() inc = float(inc) if units in ('hr', 'hour', 'hours'): units_type = _TIME_RANGE_UNITS['hours'] else: raise ValueError('Unable to parse units of interval') except ValueError: # Problem interpreting the interval string. inc = 0 units_type = 255 else: # Cast to int as timeIncrement key is a 4 byte integer. integer_inc = round(inc) # required for NCMRWF if integer_inc != inc: warnings.warn('Truncating floating point timeIncrement {} to ' 'integer value {}'.format(inc, integer_inc)) inc = integer_inc gribapi.grib_set(grib, "indicatorOfUnitForTimeIncrement", units_type) gribapi.grib_set(grib, "timeIncrement", inc)
def dx_dy(x_coord, y_coord, grib): x_step = regular_step(x_coord) y_step = regular_step(y_coord) gribapi.grib_set(grib, "DxInDegrees", float(abs(x_step))) gribapi.grib_set(grib, "DyInDegrees", float(abs(y_step)))
def grid_definition_template_30(cube, grib): """ Set keys within the provided grib message based on Grid Definition Template 3.30. Template 3.30 is used to represent a Lambert Conformal grid. """ gribapi.grib_set(grib, "gridDefinitionTemplateNumber", 30) # Retrieve some information from the cube. y_coord = cube.coord(dimensions=[0]) x_coord = cube.coord(dimensions=[1]) cs = y_coord.coord_system # Normalise the coordinate values to millimetres - the resolution # used in the GRIB message. y_mm = points_in_unit(y_coord, 'mm') x_mm = points_in_unit(x_coord, 'mm') # Encode the horizontal points. # NB. Since we're already in millimetres, our tolerance for # discrepancy in the differences is 1. try: x_step = step(x_mm, atol=1) y_step = step(y_mm, atol=1) except ValueError: msg = ('Irregular coordinates not supported for Lambert ' 'Conformal.') raise iris.exceptions.TranslationError(msg) gribapi.grib_set(grib, 'Dx', abs(x_step)) gribapi.grib_set(grib, 'Dy', abs(y_step)) horizontal_grid_common(cube, grib, xy=True) # Transform first point into geographic CS geog = cs.ellipsoid if cs.ellipsoid is not None else GeogCS(1) first_x, first_y = geog.as_cartopy_crs().transform_point( x_coord.points[0], y_coord.points[0], cs.as_cartopy_crs()) first_x = first_x % 360 central_lon = cs.central_lon % 360 gribapi.grib_set(grib, "latitudeOfFirstGridPoint", int(np.round(first_y * 1e6))) gribapi.grib_set(grib, "longitudeOfFirstGridPoint", int(np.round(first_x * 1e6))) gribapi.grib_set(grib, "LaD", cs.central_lat * 1e6) gribapi.grib_set(grib, "LoV", central_lon * 1e6) latin1, latin2 = cs.secant_latitudes gribapi.grib_set(grib, "Latin1", latin1 * 1e6) gribapi.grib_set(grib, "Latin2", latin2 * 1e6) gribapi.grib_set(grib, 'resolutionAndComponentFlags', 0x1 << _RESOLUTION_AND_COMPONENTS_GRID_WINDS_BIT) # Which pole are the parallels closest to? That is the direction # that the cone converges. poliest_sec = latin1 if abs(latin1) > abs(latin2) else latin2 centre_flag = 0x0 if poliest_sec > 0 else 0x1 gribapi.grib_set(grib, 'projectionCentreFlag', centre_flag) gribapi.grib_set(grib, "latitudeOfSouthernPole", 0) gribapi.grib_set(grib, "longitudeOfSouthernPole", 0)
def _product_definition_template_8_10_and_11(cube, grib): """ Set keys within the provided grib message based on common aspects of Product Definition Templates 4.8 and 4.11. Templates 4.8 and 4.11 are used to represent aggregations over a time interval. """ product_definition_template_common(cube, grib) # Check for time coordinate. time_coord = cube.coord('time') if len(time_coord.points) != 1: msg = 'Expected length one time coordinate, got {} points' raise ValueError(msg.format(time_coord.points)) if time_coord.nbounds != 2: msg = 'Expected time coordinate with two bounds, got {} bounds' raise ValueError(msg.format(time_coord.nbounds)) # Extract the datetime-like object corresponding to the end of # the overall processing interval. end = time_coord.units.num2date(time_coord.bounds[0, -1]) # Set the associated keys for the end of the interval (octets 35-41 # in section 4). gribapi.grib_set(grib, "yearOfEndOfOverallTimeInterval", end.year) gribapi.grib_set(grib, "monthOfEndOfOverallTimeInterval", end.month) gribapi.grib_set(grib, "dayOfEndOfOverallTimeInterval", end.day) gribapi.grib_set(grib, "hourOfEndOfOverallTimeInterval", end.hour) gribapi.grib_set(grib, "minuteOfEndOfOverallTimeInterval", end.minute) gribapi.grib_set(grib, "secondOfEndOfOverallTimeInterval", end.second) # Only one time range specification. If there were a series of aggregations # (e.g. the mean of an accumulation) one might set this to a higher value, # but we currently only handle a single time related cell method. gribapi.grib_set(grib, "numberOfTimeRange", 1) gribapi.grib_set(grib, "numberOfMissingInStatisticalProcess", 0) # Period over which statistical processing is performed. set_time_range(time_coord, grib) # Check that there is one and only one cell method related to the # time coord. if cube.cell_methods: time_cell_methods = [ cell_method for cell_method in cube.cell_methods if 'time' in cell_method.coord_names] if not time_cell_methods: raise ValueError("Expected a cell method with a coordinate name " "of 'time'") if len(time_cell_methods) > 1: raise ValueError("Cannot handle multiple 'time' cell methods") cell_method, = time_cell_methods if len(cell_method.coord_names) > 1: raise ValueError("Cannot handle multiple coordinate names in " "the time related cell method. Expected " "('time',), got {!r}".format( cell_method.coord_names)) # Type of statistical process (see code table 4.10) statistic_type = _STATISTIC_TYPE_NAMES.get(cell_method.method, 255) gribapi.grib_set(grib, "typeOfStatisticalProcessing", statistic_type) # Time increment i.e. interval of cell method (if any) set_time_increment(cell_method, grib)
def _product_definition_template_8_and_11(cube, grib): """ Set keys within the provided grib message based on common aspects of Product Definition Templates 4.8 and 4.11. Templates 4.8 and 4.11 are used to represent aggregations over a time interval. """ product_definition_template_common(cube, grib) # Check for time coordinate. time_coord = cube.coord('time') if len(time_coord.points) != 1: msg = 'Expected length one time coordinate, got {} points' raise ValueError(msg.format(time_coord.points)) if time_coord.nbounds != 2: msg = 'Expected time coordinate with two bounds, got {} bounds' raise ValueError(msg.format(time_coord.nbounds)) # Check that there is one and only one cell method related to the # time coord. time_cell_methods = [ cell_method for cell_method in cube.cell_methods if 'time' in cell_method.coord_names ] if not time_cell_methods: raise ValueError("Expected a cell method with a coordinate name " "of 'time'") if len(time_cell_methods) > 1: raise ValueError("Cannot handle multiple 'time' cell methods") cell_method, = time_cell_methods if len(cell_method.coord_names) > 1: raise ValueError("Cannot handle multiple coordinate names in " "the time related cell method. Expected ('time',), " "got {!r}".format(cell_method.coord_names)) # Extract the datetime-like object corresponding to the end of # the overall processing interval. end = time_coord.units.num2date(time_coord.bounds[0, -1]) # Set the associated keys for the end of the interval (octets 35-41 # in section 4). gribapi.grib_set(grib, "yearOfEndOfOverallTimeInterval", end.year) gribapi.grib_set(grib, "monthOfEndOfOverallTimeInterval", end.month) gribapi.grib_set(grib, "dayOfEndOfOverallTimeInterval", end.day) gribapi.grib_set(grib, "hourOfEndOfOverallTimeInterval", end.hour) gribapi.grib_set(grib, "minuteOfEndOfOverallTimeInterval", end.minute) gribapi.grib_set(grib, "secondOfEndOfOverallTimeInterval", end.second) # Only one time range specification. If there were a series of aggregations # (e.g. the mean of an accumulation) one might set this to a higher value, # but we currently only handle a single time related cell method. gribapi.grib_set(grib, "numberOfTimeRange", 1) gribapi.grib_set(grib, "numberOfMissingInStatisticalProcess", 0) # Type of statistical process (see code table 4.10) statistic_type = _STATISTIC_TYPE_NAMES.get(cell_method.method, 255) gribapi.grib_set(grib, "typeOfStatisticalProcessing", statistic_type) # Period over which statistical processing is performed. set_time_range(time_coord, grib) # Time increment i.e. interval of cell method (if any) set_time_increment(cell_method, grib)
def set_fixed_surfaces(cube, grib): # Look for something we can export v_coord = grib_v_code = output_unit = None # pressure if cube.coords("air_pressure") or cube.coords("pressure"): grib_v_code = 100 output_unit = cf_units.Unit("Pa") v_coord = (cube.coords("air_pressure") or cube.coords("pressure"))[0] # altitude elif cube.coords("altitude"): grib_v_code = 102 output_unit = cf_units.Unit("m") v_coord = cube.coord("altitude") # height elif cube.coords("height"): grib_v_code = 103 output_unit = cf_units.Unit("m") v_coord = cube.coord("height") # depth elif cube.coords("depth"): grib_v_code = 106 # required for NCMRWF NCUM 10.2 output_unit = cf_units.Unit("m") v_coord = cube.coord("depth") # depth_below_land_surface elif cube.coords("depth_below_land_surface"): grib_v_code = 106 # required for NCMRWF NCUM 8.5 output_unit = cf_units.Unit("m") v_coord = cube.coord("depth_below_land_surface") elif cube.coords("air_potential_temperature"): grib_v_code = 107 output_unit = cf_units.Unit('K') v_coord = cube.coord("air_potential_temperature") # unknown / absent else: # check for *ANY* height coords at all... v_coords = cube.coords(axis='z') if v_coords: # There are vertical coordinate(s), but we don't understand them... v_coords_str = ' ,'.join( ["'{}'".format(c.name()) for c in v_coords]) raise iris.exceptions.TranslationError( 'The vertical-axis coordinate(s) ({}) ' 'are not recognised or handled.'.format(v_coords_str)) # What did we find? if v_coord is None: # No vertical coordinate: record as 'surface' level (levelType=1). # NOTE: may *not* be truly correct, but seems to be common practice. # Still under investigation : # See https://github.com/SciTools/iris/issues/519 gribapi.grib_set(grib, "typeOfFirstFixedSurface", 1) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", 0) # Set secondary surface = 'missing'. gribapi.grib_set(grib, "typeOfSecondFixedSurface", -1) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 255) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", -1) elif not v_coord.has_bounds(): # No second surface output_v = v_coord.units.convert(v_coord.points[0], output_unit) if output_v - abs(output_v): warnings.warn("Vertical level encoding problem: scaling required.") output_v = round( output_v ) # we must round it, so that WRF/TIGGE able understand! # required for NCMRWF gribapi.grib_set(grib, "typeOfFirstFixedSurface", grib_v_code) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", output_v) gribapi.grib_set(grib, "typeOfSecondFixedSurface", -1) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 255) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", -1) else: # bounded : set lower+upper surfaces output_v = v_coord.units.convert(v_coord.bounds[0], output_unit) if output_v[0] - abs(output_v[0]) or output_v[1] - abs(output_v[1]): warnings.warn("Vertical level encoding problem: scaling required.") gribapi.grib_set(grib, "typeOfFirstFixedSurface", grib_v_code) gribapi.grib_set(grib, "typeOfSecondFixedSurface", grib_v_code) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", int(output_v[0])) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", int(output_v[1]))
def grid_definition_template_12(cube, grib): """ Set keys within the provided grib message based on Grid Definition Template 3.12. Template 3.12 is used to represent a Transverse Mercator grid. """ gribapi.grib_set(grib, "gridDefinitionTemplateNumber", 12) # Retrieve some information from the cube. y_coord = cube.coord(dimensions=[0]) x_coord = cube.coord(dimensions=[1]) cs = y_coord.coord_system # Normalise the coordinate values to centimetres - the resolution # used in the GRIB message. def points_in_cm(coord): points = coord.units.convert(coord.points, 'cm') points = np.around(points).astype(int) return points y_cm = points_in_cm(y_coord) x_cm = points_in_cm(x_coord) # Set some keys specific to GDT12. # Encode the horizontal points. # NB. Since we're already in centimetres, our tolerance for # discrepancy in the differences is 1. def step(points): diffs = points[1:] - points[:-1] mean_diff = np.mean(diffs).astype(points.dtype) if not np.allclose(diffs, mean_diff, atol=1): msg = ('Irregular coordinates not supported for transverse ' 'Mercator.') raise iris.exceptions.TranslationError(msg) return int(mean_diff) gribapi.grib_set(grib, 'Di', abs(step(x_cm))) gribapi.grib_set(grib, 'Dj', abs(step(y_cm))) horizontal_grid_common(cube, grib) # GRIBAPI expects unsigned ints in X1, X2, Y1, Y2 but it should accept # signed ints, so work around this. # See https://software.ecmwf.int/issues/browse/SUP-1101 ensure_set_int32_value(grib, 'Y1', int(y_cm[0])) ensure_set_int32_value(grib, 'Y2', int(y_cm[-1])) ensure_set_int32_value(grib, 'X1', int(x_cm[0])) ensure_set_int32_value(grib, 'X2', int(x_cm[-1])) # Lat and lon of reference point are measured in millionths of a degree. gribapi.grib_set(grib, "latitudeOfReferencePoint", cs.latitude_of_projection_origin / _DEFAULT_DEGREES_UNITS) gribapi.grib_set(grib, "longitudeOfReferencePoint", cs.longitude_of_central_meridian / _DEFAULT_DEGREES_UNITS) # Convert a value in metres into the closest integer number of # centimetres. def m_to_cm(value): return int(round(value * 100)) # False easting and false northing are measured in units of (10^-2)m. gribapi.grib_set(grib, 'XR', m_to_cm(cs.false_easting)) gribapi.grib_set(grib, 'YR', m_to_cm(cs.false_northing)) # GRIBAPI expects a signed int for scaleFactorAtReferencePoint # but it should accept a float, so work around this. # See https://software.ecmwf.int/issues/browse/SUP-1100 value = cs.scale_factor_at_central_meridian key_type = gribapi.grib_get_native_type(grib, "scaleFactorAtReferencePoint") if key_type is not float: value = fixup_float32_as_int32(value) gribapi.grib_set(grib, "scaleFactorAtReferencePoint", value)
def set_discipline_and_parameter(cube, grib): # NOTE: for now, can match by *either* standard_name or long_name. # This allows workarounds for data with no identified standard_name. grib2_info = gptx.cf_phenom_to_grib2_info(cube.standard_name, cube.long_name) if grib2_info is not None: gribapi.grib_set(grib, "discipline", grib2_info.discipline) gribapi.grib_set(grib, "parameterCategory", grib2_info.category) gribapi.grib_set(grib, "parameterNumber", grib2_info.number) else: gribapi.grib_set(grib, "discipline", 255) gribapi.grib_set(grib, "parameterCategory", 255) gribapi.grib_set(grib, "parameterNumber", 255) warnings.warn('Unable to determine Grib2 parameter code for cube.\n' 'discipline, parameterCategory and parameterNumber ' 'have been set to "missing".')
def set_fixed_surfaces(cube, grib): # Look for something we can export v_coord = grib_v_code = output_unit = None # pressure if cube.coords("air_pressure") or cube.coords("pressure"): grib_v_code = 100 output_unit = cf_units.Unit("Pa") v_coord = (cube.coords("air_pressure") or cube.coords("pressure"))[0] # altitude elif cube.coords("altitude"): grib_v_code = 102 output_unit = cf_units.Unit("m") v_coord = cube.coord("altitude") # height elif cube.coords("height"): grib_v_code = 103 output_unit = cf_units.Unit("m") v_coord = cube.coord("height") elif cube.coords("air_potential_temperature"): grib_v_code = 107 output_unit = cf_units.Unit('K') v_coord = cube.coord("air_potential_temperature") # unknown / absent else: # check for *ANY* height coords at all... v_coords = cube.coords(axis='z') if v_coords: # There are vertical coordinate(s), but we don't understand them... v_coords_str = ' ,'.join(["'{}'".format(c.name()) for c in v_coords]) raise iris.exceptions.TranslationError( 'The vertical-axis coordinate(s) ({}) ' 'are not recognised or handled.'.format(v_coords_str)) # What did we find? if v_coord is None: # No vertical coordinate: record as 'surface' level (levelType=1). # NOTE: may *not* be truly correct, but seems to be common practice. # Still under investigation : # See https://github.com/SciTools/iris/issues/519 gribapi.grib_set(grib, "typeOfFirstFixedSurface", 1) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", 0) # Set secondary surface = 'missing'. gribapi.grib_set(grib, "typeOfSecondFixedSurface", -1) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 255) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", -1) elif not v_coord.has_bounds(): # No second surface output_v = v_coord.units.convert(v_coord.points[0], output_unit) if output_v - abs(output_v): warnings.warn("Vertical level encoding problem: scaling required.") output_v = int(output_v) gribapi.grib_set(grib, "typeOfFirstFixedSurface", grib_v_code) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", output_v) gribapi.grib_set(grib, "typeOfSecondFixedSurface", -1) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 255) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", -1) else: # bounded : set lower+upper surfaces output_v = v_coord.units.convert(v_coord.bounds[0], output_unit) if output_v[0] - abs(output_v[0]) or output_v[1] - abs(output_v[1]): warnings.warn("Vertical level encoding problem: scaling required.") gribapi.grib_set(grib, "typeOfFirstFixedSurface", grib_v_code) gribapi.grib_set(grib, "typeOfSecondFixedSurface", grib_v_code) gribapi.grib_set(grib, "scaleFactorOfFirstFixedSurface", 0) gribapi.grib_set(grib, "scaleFactorOfSecondFixedSurface", 0) gribapi.grib_set(grib, "scaledValueOfFirstFixedSurface", int(output_v[0])) gribapi.grib_set(grib, "scaledValueOfSecondFixedSurface", int(output_v[1]))
def _regularise(grib_message): """ Transform a reduced grid to a regular grid using interpolation. Uses 1d linear interpolation at constant latitude to make the grid regular. If the longitude dimension is circular then this is taken into account by the interpolation. If the longitude dimension is not circular then extrapolation is allowed to make sure all end regular grid points get a value. In practice this extrapolation is likely to be minimal. """ # Make sure to read any missing values as NaN. gribapi.grib_set_double(grib_message, "missingValue", np.nan) # Get full longitude values, these describe the longitude value of # *every* point in the grid, they are not 1d monotonic coordinates. lons = gribapi.grib_get_double_array(grib_message, "longitudes") # Compute the new longitude coordinate for the regular grid. new_nx = max(gribapi.grib_get_long_array(grib_message, "pl")) new_x_step = (max(lons) - min(lons)) / (new_nx - 1) if gribapi.grib_get_long(grib_message, "iScansNegatively"): new_x_step *= -1 new_lons = np.arange(new_nx) * new_x_step + lons[0] # Get full latitude and data values, these describe the latitude and # data values of *every* point in the grid, they are not 1d monotonic # coordinates. lats = gribapi.grib_get_double_array(grib_message, "latitudes") values = gribapi.grib_get_double_array(grib_message, "values") # Retrieve the distinct latitudes from the GRIB message. GRIBAPI docs # don't specify if these points are guaranteed to be oriented correctly so # the safe option is to sort them into ascending (south-to-north) order # and then reverse the order if necessary. new_lats = gribapi.grib_get_double_array(grib_message, "distinctLatitudes") new_lats.sort() if not gribapi.grib_get_long(grib_message, "jScansPositively"): new_lats = new_lats[::-1] ny = new_lats.shape[0] # Use 1d linear interpolation along latitude circles to regularise the # reduced data. cyclic = _longitude_is_cyclic(new_lons) new_values = np.empty([ny, new_nx], dtype=values.dtype) for ilat, lat in enumerate(new_lats): idx = np.where(lats == lat) llons = lons[idx] vvalues = values[idx] if cyclic: # For cyclic data we insert dummy points at each end to ensure # we can interpolate to all output longitudes using pure # interpolation. cgap = (360 - llons[-1] - llons[0]) llons = np.concatenate( (llons[0:1] - cgap, llons, llons[-1:] + cgap)) vvalues = np.concatenate( (vvalues[-1:], vvalues, vvalues[0:1])) fixed_latitude_interpolator = scipy.interpolate.interp1d( llons, vvalues) else: # Allow extrapolation for non-cyclic data sets to ensure we can # interpolate to all output longitudes. fixed_latitude_interpolator = Linear1dExtrapolator( scipy.interpolate.interp1d(llons, vvalues)) new_values[ilat] = fixed_latitude_interpolator(new_lons) new_values = new_values.flatten() # Set flags for the regularised data. if np.isnan(new_values).any(): # Account for any missing data. gribapi.grib_set_double(grib_message, "missingValue", np.inf) gribapi.grib_set(grib_message, "bitmapPresent", 1) new_values = np.where(np.isnan(new_values), np.inf, new_values) gribapi.grib_set_long(grib_message, "Nx", int(new_nx)) gribapi.grib_set_double(grib_message, "iDirectionIncrementInDegrees", float(new_x_step)) gribapi.grib_set_double_array(grib_message, "values", new_values) gribapi.grib_set_long(grib_message, "jPointsAreConsecutive", 0) gribapi.grib_set_long(grib_message, "PLPresent", 0)
def set_field(self, name, value): gribapi.grib_set(self.record, name, value)