Beispiel #1
0
def grib1_convert(grib):
    """
    Converts a GRIB1 message into the corresponding items of Cube metadata.

    Args:

    * grib:
        A :class:`~iris_grib.GribWrapper` object.

    Returns:
        A :class:`iris.fileformats.rules.ConversionMetadata` object.

    """
    if grib.edition != 1:
        emsg = 'GRIB edition {} is not supported by {!r}.'
        raise TranslationError(emsg.format(grib.edition,
                                           type(grib).__name__))

    factories = []
    references = []
    standard_name = None
    long_name = None
    units = None
    attributes = {}
    cell_methods = []
    dim_coords_and_dims = []
    aux_coords_and_dims = []

    if \
            (grib.gridType=="reduced_gg"):
        aux_coords_and_dims.append((AuxCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 0))
        aux_coords_and_dims.append((AuxCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system), 0))

    if \
            (grib.gridType=="regular_ll") and \
            (grib.jPointsAreConsecutive == 0):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 0))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 1))

    if \
            (grib.gridType=="regular_ll") and \
            (grib.jPointsAreConsecutive == 1):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 1))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 0))

    if \
            (grib.gridType=="regular_gg") and \
            (grib.jPointsAreConsecutive == 0):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 0))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 1))

    if \
            (grib.gridType=="regular_gg") and \
            (grib.jPointsAreConsecutive == 1):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 1))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 0))

    if \
            (grib.gridType=="rotated_ll") and \
            (grib.jPointsAreConsecutive == 0):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 0))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 1))

    if \
            (grib.gridType=="rotated_ll") and \
            (grib.jPointsAreConsecutive == 1):
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units='degrees', coord_system=grib._coord_system), 1))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units='degrees', coord_system=grib._coord_system, circular=grib._x_circular), 0))

    if grib.gridType in ["polar_stereographic", "lambert"]:
        dim_coords_and_dims.append((DimCoord(grib._y_points, grib._y_coord_name, units="m", coord_system=grib._coord_system), 0))
        dim_coords_and_dims.append((DimCoord(grib._x_points, grib._x_coord_name, units="m", coord_system=grib._coord_system), 1))

    if \
            (grib.table2Version < 128) and \
            (grib.indicatorOfParameter == 11) and \
            (grib._cf_data is None):
        standard_name = "air_temperature"
        units = "kelvin"

    if \
            (grib.table2Version < 128) and \
            (grib.indicatorOfParameter == 33) and \
            (grib._cf_data is None):
        standard_name = "x_wind"
        units = "m s-1"

    if \
            (grib.table2Version < 128) and \
            (grib.indicatorOfParameter == 34) and \
            (grib._cf_data is None):
        standard_name = "y_wind"
        units = "m s-1"

    if \
            (grib._cf_data is not None):
        standard_name = grib._cf_data.standard_name
        long_name = grib._cf_data.standard_name or grib._cf_data.long_name
        units = grib._cf_data.units

    if \
            (grib.table2Version >= 128) and \
            (grib._cf_data is None):
        long_name = "UNKNOWN LOCAL PARAM " + str(grib.indicatorOfParameter) + "." + str(grib.table2Version)
        units = "???"

    if \
            (grib.table2Version == 1) and \
            (grib.indicatorOfParameter >= 128):
        long_name = "UNKNOWN LOCAL PARAM " + str(grib.indicatorOfParameter) + "." + str(grib.table2Version)
        units = "???"

    if \
            (grib._phenomenonDateTime != -1.0):
        aux_coords_and_dims.append((DimCoord(points=grib.startStep, standard_name='forecast_period', units=grib._forecastTimeUnit), None))
        aux_coords_and_dims.append((DimCoord(points=grib.phenomenon_points('hours'), standard_name='time', units=Unit('hours since epoch', CALENDAR_GREGORIAN)), None))

    def add_bounded_time_coords(aux_coords_and_dims, grib):
        t_bounds = grib.phenomenon_bounds('hours')
        period = Unit('hours').convert(t_bounds[1] - t_bounds[0],
                                       grib._forecastTimeUnit)
        aux_coords_and_dims.append((
            DimCoord(standard_name='forecast_period',
                     units=grib._forecastTimeUnit,
                     points=grib._forecastTime + 0.5 * period,
                     bounds=[grib._forecastTime, grib._forecastTime + period]),
            None))
        aux_coords_and_dims.append((
            DimCoord(standard_name='time',
                     units=Unit('hours since epoch', CALENDAR_GREGORIAN),
                     points=0.5 * (t_bounds[0] + t_bounds[1]),
                     bounds=t_bounds),
            None))

    if \
            (grib.timeRangeIndicator == 2):
        add_bounded_time_coords(aux_coords_and_dims, grib)

    if \
            (grib.timeRangeIndicator == 3):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 4):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("sum", coords="time"))

    if \
            (grib.timeRangeIndicator == 5):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("_difference", coords="time"))

    if \
            (grib.timeRangeIndicator == 51):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 113):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 114):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("sum", coords="time"))

    if \
            (grib.timeRangeIndicator == 115):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 116):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("sum", coords="time"))

    if \
            (grib.timeRangeIndicator == 117):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 118):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("_covariance", coords="time"))

    if \
            (grib.timeRangeIndicator == 123):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("mean", coords="time"))

    if \
            (grib.timeRangeIndicator == 124):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("sum", coords="time"))

    if \
            (grib.timeRangeIndicator == 125):
        add_bounded_time_coords(aux_coords_and_dims, grib)
        cell_methods.append(CellMethod("standard_deviation", coords="time"))

    if \
            (grib.levelType == 'pl'):
        aux_coords_and_dims.append((DimCoord(points=grib.level,  long_name="pressure", units="hPa"), None))

    if \
            (grib.levelType == 'sfc'):

            if (grib._cf_data is not None) and \
            (grib._cf_data.set_height is not None):
                aux_coords_and_dims.append((DimCoord(points=grib._cf_data.set_height,  long_name="height", units="m", attributes={'positive':'up'}), None))
            elif grib.typeOfLevel == 'heightAboveGround': # required for NCAR
                aux_coords_and_dims.append((DimCoord(points=grib.level,  long_name="height", units="m", attributes={'positive':'up'}), None))

    if \
            (grib.levelType == 'ml') and \
            (hasattr(grib, 'pv')):
        aux_coords_and_dims.append((AuxCoord(grib.level, standard_name='model_level_number', attributes={'positive': 'up'}), None))
        aux_coords_and_dims.append((DimCoord(grib.pv[grib.level], long_name='level_pressure', units='Pa'), None))
        aux_coords_and_dims.append((AuxCoord(grib.pv[grib.numberOfCoordinatesValues//2 + grib.level], long_name='sigma'), None))
        factories.append(Factory(HybridPressureFactory, [{'long_name': 'level_pressure'}, {'long_name': 'sigma'}, Reference('surface_pressure')]))

    if grib._originatingCentre != 'unknown':
        aux_coords_and_dims.append((AuxCoord(points=grib._originatingCentre, long_name='originating_centre', units='no_unit'), None))

    return ConversionMetadata(factories, references, standard_name, long_name,
                              units, attributes, cell_methods,
                              dim_coords_and_dims, aux_coords_and_dims)
def load_NAMEIII_trajectory(filename):
    """
    Load a NAME III trajectory file returning a
    generator of :class:`iris.cube.Cube` instances.

    Args:

    * filename (string):
        Name of file to load.

    Returns:
        A generator :class:`iris.cube.Cube` instances.

    """
    time_unit = cf_units.Unit('hours since epoch',
                              calendar=cf_units.CALENDAR_GREGORIAN)

    with open(filename, 'r') as infile:
        header = read_header(infile)

        # read the column headings
        for line in infile:
            if line.startswith("    "):
                break
        headings = [heading.strip() for heading in line.split(",")]

        # read the columns
        columns = [[] for i in range(len(headings))]
        for line in infile:
            values = [v.strip() for v in line.split(",")]
            for c, v in enumerate(values):
                if "UTC" in v:
                    v = datetime.datetime.strptime(v, NAMETRAJ_DATETIME_FORMAT)
                else:
                    try:
                        v = float(v)
                    except ValueError:
                        pass
                columns[c].append(v)

    # Sort columns according to PP Index
    columns_t = list(map(list, zip(*columns)))
    columns_t.sort(key=itemgetter(1))
    columns = list(map(list, zip(*columns_t)))

    # Where's the Z column?
    z_column = None
    for i, heading in enumerate(headings):
        if heading.startswith("Z "):
            z_column = i
            break
    if z_column is None:
        raise TranslationError("Expected a Z column")

    # Every column up to Z becomes a coordinate.
    coords = []
    for name, values in zip(headings[:z_column + 1], columns[:z_column + 1]):
        values = np.array(values)
        if np.all(np.array(values) == values[0]):
            values = [values[0]]

        standard_name = long_name = units = None
        if isinstance(values[0], datetime.datetime):
            values = time_unit.date2num(values)
            units = time_unit
            if name == "Time":
                name = "time"
        elif " (Lat-Long)" in name:
            if name.startswith("X"):
                name = "longitude"
            elif name.startswith("Y"):
                name = "latitude"
            units = "degrees"
        elif name == "Z (m asl)":
            name = "altitude"
            units = "m"
            long_name = "altitude above sea level"
        elif name == "Z (m agl)":
            name = 'height'
            units = "m"
            long_name = "height above ground level"
        elif name == "Z (FL)":
            name = "flight_level"
            long_name = name

        try:
            coord = DimCoord(values, units=units)
        except ValueError:
            coord = AuxCoord(values, units=units)
        coord.rename(name)
        if coord.long_name is None and long_name is not None:
            coord.long_name = long_name
        coords.append(coord)

    # Every numerical column after the Z becomes a cube.
    for name, values in zip(headings[z_column + 1:], columns[z_column + 1:]):
        try:
            float(values[0])
        except ValueError:
            continue
        # units embedded in column heading?
        name, units = _split_name_and_units(name)
        cube = iris.cube.Cube(values, units=units)
        cube.rename(name)
        # Add the Main Headings as attributes.
        for key, value in six.iteritems(header):
            if value is not None and value != '' and \
                    key not in headings:
                cube.attributes[key] = value
        # Add coordinates
        for coord in coords:
            dim = 0 if len(coord.points) > 1 else None
            if dim == 0 and coord.name() == "time":
                cube.add_dim_coord(coord.copy(), dim)
            elif dim == 0 and coord.name() == 'PP Index':
                cube.add_dim_coord(coord.copy(), dim)
            else:
                cube.add_aux_coord(coord.copy(), dim)
        yield cube
Beispiel #3
0
def _convert_collation(collation):
    """
    Converts a FieldCollation into the corresponding items of Cube
    metadata.

    Args:

    * collation:
        A FieldCollation object.

    Returns:
        A :class:`iris.fileformats.rules.ConversionMetadata` object.

    """
    # For all the scalar conversions all fields in the collation will
    # give the same result, so the choice is arbitrary.
    field = collation.fields[0]

    # All the "other" rules.
    (references, standard_name, long_name, units, attributes, cell_methods,
     dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field)

    # Adjust any dimension bindings to account for the extra leading
    # dimensions added by the collation.
    if collation.vector_dims_shape:
        n_collation_dims = len(collation.vector_dims_shape)
        dim_coords_and_dims = _adjust_dims(dim_coords_and_dims,
                                           n_collation_dims)
        aux_coords_and_dims = _adjust_dims(aux_coords_and_dims,
                                           n_collation_dims)

    # "Normal" (non-cross-sectional) time values
    vector_headers = collation.element_arrays_and_dims
    # If the collation doesn't define a vector of values for a
    # particular header then it must be constant over all fields in the
    # collation. In which case it's safe to get the value from any field.
    t1, t1_dims = vector_headers.get('t1', (field.t1, ()))
    t2, t2_dims = vector_headers.get('t2', (field.t2, ()))
    lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ()))
    coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim,
                                           field.time_unit('hours'), t1, t2,
                                           lbft, t1_dims, t2_dims, lbft_dims)
    dim_coord_dims = set()
    _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims,
                 aux_coords_and_dims)

    # "Normal" (non-cross-sectional) vertical levels
    blev, blev_dims = vector_headers.get('blev', (field.blev, ()))
    lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ()))
    bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ()))
    bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ()))
    brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ()))
    brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ()))
    brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ()))
    # Find all the non-trivial dimension values
    dims = set(
        filter(None, [
            blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims,
            brsvd2_dims, brlev_dims
        ]))
    if len(dims) > 1:
        raise TranslationError('Unsupported multiple values for vertical '
                               'dimension.')
    if dims:
        v_dims = dims.pop()
        if len(v_dims) > 1:
            raise TranslationError('Unsupported multi-dimension vertical '
                                   'headers.')
    else:
        v_dims = ()
    coords_and_dims, factories = _convert_vertical_coords(
        field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev,
        brsvd1, brsvd2, brlev, v_dims)
    _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims,
                 aux_coords_and_dims)

    # Realization (aka ensemble) (--> scalar coordinates)
    aux_coords_and_dims.extend(
        _convert_scalar_realization_coords(lbrsvd4=field.lbrsvd[3]))

    # Pseudo-level coordinate (--> scalar coordinates)
    aux_coords_and_dims.extend(
        _convert_scalar_pseudo_level_coords(lbuser5=field.lbuser[4]))

    return ConversionMetadata(factories, references, standard_name, long_name,
                              units, attributes, cell_methods,
                              dim_coords_and_dims, aux_coords_and_dims)
Beispiel #4
0
    def _get_verification_date(self):
        reference_date_time = self._referenceDateTime

        # calculate start time (edition-dependent)
        if self.edition == 1:
            time_range_indicator = self.timeRangeIndicator
            P1 = self.P1
            P2 = self.P2
            if time_range_indicator == 0:
                time_diff = P1  #Forecast product valid at reference time + P1 P1>0), or Uninitialized analysis product for reference time (P1=0). Or Image product for reference time (P1=0)
            elif time_range_indicator == 1:
                time_diff = P1  #Initialized analysis product for reference time (P1=0).
            elif time_range_indicator == 2:
                time_diff = (
                    P1 + P2
                ) * 0.5  #Product with a valid time ranging between reference time + P1 and reference time + P2
            elif time_range_indicator == 3:
                time_diff = (
                    P1 + P2
                ) * 0.5  #Average(reference time + P1 to reference time + P2)
            elif time_range_indicator == 4:
                time_diff = P2  #Accumulation (reference time + P1 to reference time + P2) product considered valid at reference time + P2
            elif time_range_indicator == 5:
                time_diff = P2  #Difference(reference time + P2 minus reference time + P1) product considered valid at reference time + P2
            elif time_range_indicator == 10:
                time_diff = P1 * 256 + P2  #P1 occupies octets 19 and 20; product valid at reference time + P1
            elif time_range_indicator == 51:  #Climatological Mean Value: multiple year averages of quantities which are themselves means over some period of time (P2) less than a year. The reference time (R) indicates the date and time of the start of a period of time, given by R to R + P2, over which a mean is formed; N indicates the number of such period-means that are averaged together to form the climatological value, assuming that the N period-mean fields are separated by one year. The reference time indicates the start of the N-year climatology. N is given in octets 22-23 of the PDS. If P1 = 0 then the data averaged in the basic interval P2 are assumed to be continuous, i.e., all available data are simply averaged together. If P1 = 1 (the units of time - octet 18, code table 4 - are not relevant here) then the data averaged together in the basic interval P2 are valid only at the time (hour, minute) given in the reference time, for all the days included in the P2 period. The units of P2 are given by the contents of octet 18 and Table 4.
                raise TranslationError("unhandled grib1 timeRangeIndicator "
                                       "= 51 (avg of avgs)")
            elif time_range_indicator == 113:
                time_diff = P1  #Average of N forecasts (or initialized analyses); each product has forecast period of P1 (P1=0 for initialized analyses); products have reference times at intervals of P2, beginning at the given reference time.
            elif time_range_indicator == 114:
                time_diff = P1  #Accumulation of N forecasts (or initialized analyses); each product has forecast period of P1 (P1=0 for initialized analyses); products have reference times at intervals of P2, beginning at the given reference time.
            elif time_range_indicator == 115:
                time_diff = P1  #Average of N forecasts, all with the same reference time; the first has a forecast period of P1, the remaining forecasts follow at intervals of P2.
            elif time_range_indicator == 116:
                time_diff = P1  #Accumulation of N forecasts, all with the same reference time; the first has a forecast period of P1, the remaining follow at intervals of P2.
            elif time_range_indicator == 117:
                time_diff = P1  #Average of N forecasts, the first has a period of P1, the subsequent ones have forecast periods reduced from the previous one by an interval of P2; the reference time for the first is given in octets 13-17, the subsequent ones have reference times increased from the previous one by an interval of P2. Thus all the forecasts have the same valid time, given by the initial reference time + P1.
            elif time_range_indicator == 118:
                time_diff = P1  #Temporal variance, or covariance, of N initialized analyses; each product has forecast period P1=0; products have reference times at intervals of P2, beginning at the given reference time.
            elif time_range_indicator == 123:
                time_diff = P1  #Average of N uninitialized analyses, starting at the reference time, at intervals of P2.
            elif time_range_indicator == 124:
                time_diff = P1  #Accumulation of N uninitialized analyses, starting at the reference time, at intervals of P2.
            else:
                raise TranslationError("unhandled grib1 timeRangeIndicator "
                                       "= %i" % time_range_indicator)
        elif self.edition == 2:
            time_diff = int(self.stepRange)  # gribapi gives us a string!

        else:
            raise TranslationError("unhandled grib edition = {}".format(
                self.edition))

        # Get the timeunit interval.
        interval_secs = self._timeunit_seconds()
        # Multiply by start-offset and convert to a timedelta.
        #     NOTE: a 'float' conversion is required here, as time_diff may be
        #     a numpy scalar, which timedelta will not accept.
        interval_delta = datetime.timedelta(seconds=float(time_diff *
                                                          interval_secs))
        # Return validity_time = (reference_time + start_offset*time_unit).
        return reference_date_time + interval_delta
Beispiel #5
0
    def _compute_extra_keys(self):
        """Compute our extra keys."""
        global unknown_string

        self.extra_keys = {}

        # work out stuff based on these values from the message
        edition = self.edition

        # time-processed forcast time is from reference time to start of period
        if edition == 2:
            forecastTime = self.forecastTime

            uft = np.uint32(forecastTime)
            BILL = 2**30

            # Workaround grib api's assumption that forecast time is positive.
            # Handles correctly encoded -ve forecast times up to one -1 billion.
            if hindcast_workaround:
                if 2 * BILL < uft < 3 * BILL:
                    msg = "Re-interpreting negative forecastTime from " \
                            + str(forecastTime)
                    forecastTime = -(uft - 2 * BILL)
                    msg += " to " + str(forecastTime)
                    warnings.warn(msg)

        else:
            forecastTime = self.startStep

        #regular or rotated grid?
        try:
            longitudeOfSouthernPoleInDegrees = self.longitudeOfSouthernPoleInDegrees
            latitudeOfSouthernPoleInDegrees = self.latitudeOfSouthernPoleInDegrees
        except AttributeError:
            longitudeOfSouthernPoleInDegrees = 0.0
            latitudeOfSouthernPoleInDegrees = 90.0

        centre = gribapi.grib_get_string(self.grib_message, "centre")

        #default values
        self.extra_keys = {
            '_referenceDateTime': -1.0,
            '_phenomenonDateTime': -1.0,
            '_periodStartDateTime': -1.0,
            '_periodEndDateTime': -1.0,
            '_levelTypeName': unknown_string,
            '_levelTypeUnits': unknown_string,
            '_firstLevelTypeName': unknown_string,
            '_firstLevelTypeUnits': unknown_string,
            '_firstLevel': -1.0,
            '_secondLevelTypeName': unknown_string,
            '_secondLevel': -1.0,
            '_originatingCentre': unknown_string,
            '_forecastTime': None,
            '_forecastTimeUnit': unknown_string,
            '_coord_system': None,
            '_x_circular': False,
            '_x_coord_name': unknown_string,
            '_y_coord_name': unknown_string,
            # These are here to avoid repetition in the rules files,
            # and reduce the very long line lengths.
            '_x_points': None,
            '_y_points': None,
            '_cf_data': None
        }

        # cf phenomenon translation
        if edition == 1:
            # Get centre code (N.B. self.centre has default type = string)
            centre_number = gribapi.grib_get_long(self.grib_message, "centre")
            # Look for a known grib1-to-cf translation (or None).
            cf_data = gptx.grib1_phenom_to_cf_info(
                table2_version=self.table2Version,
                centre_number=centre_number,
                param_number=self.indicatorOfParameter)
            self.extra_keys['_cf_data'] = cf_data
        elif edition == 2:
            # Don't attempt to interpret params if 'master tables version' is
            # 255, as local params may then have same codes as standard ones.
            if self.tablesVersion != 255:
                # Look for a known grib2-to-cf translation (or None).
                cf_data = gptx.grib2_phenom_to_cf_info(
                    param_discipline=self.discipline,
                    param_category=self.parameterCategory,
                    param_number=self.parameterNumber)
                self.extra_keys['_cf_data'] = cf_data

        #reference date
        self.extra_keys['_referenceDateTime'] = \
            datetime.datetime(int(self.year), int(self.month), int(self.day),
                              int(self.hour), int(self.minute))

        # forecast time with workarounds
        self.extra_keys['_forecastTime'] = forecastTime

        #verification date
        processingDone = self._get_processing_done()
        #time processed?
        if processingDone.startswith("time"):
            if self.edition == 1:
                validityDate = str(self.validityDate)
                validityTime = "{:04}".format(int(self.validityTime))
                endYear = int(validityDate[:4])
                endMonth = int(validityDate[4:6])
                endDay = int(validityDate[6:8])
                endHour = int(validityTime[:2])
                endMinute = int(validityTime[2:4])
            elif self.edition == 2:
                endYear = self.yearOfEndOfOverallTimeInterval
                endMonth = self.monthOfEndOfOverallTimeInterval
                endDay = self.dayOfEndOfOverallTimeInterval
                endHour = self.hourOfEndOfOverallTimeInterval
                endMinute = self.minuteOfEndOfOverallTimeInterval

            # fixed forecastTime in hours
            self.extra_keys['_periodStartDateTime'] = \
                (self.extra_keys['_referenceDateTime'] +
                 datetime.timedelta(hours=int(forecastTime)))
            self.extra_keys['_periodEndDateTime'] = \
                datetime.datetime(endYear, endMonth, endDay, endHour, endMinute)
        else:
            self.extra_keys[
                '_phenomenonDateTime'] = self._get_verification_date()

        #originating centre
        #TODO #574 Expand to include sub-centre
        self.extra_keys['_originatingCentre'] = CENTRE_TITLES.get(
            centre, "unknown centre %s" % centre)

        #forecast time unit as a cm string
        #TODO #575 Do we want PP or GRIB style forecast delta?
        self.extra_keys['_forecastTimeUnit'] = self._timeunit_string()

        #shape of the earth

        #pre-defined sphere
        if self.shapeOfTheEarth == 0:
            geoid = coord_systems.GeogCS(semi_major_axis=6367470)

        #custom sphere
        elif self.shapeOfTheEarth == 1:
            geoid = coord_systems.GeogCS(
                self.scaledValueOfRadiusOfSphericalEarth *
                10**-self.scaleFactorOfRadiusOfSphericalEarth)

        #IAU65 oblate sphere
        elif self.shapeOfTheEarth == 2:
            geoid = coord_systems.GeogCS(6378160, inverse_flattening=297.0)

        #custom oblate spheroid (km)
        elif self.shapeOfTheEarth == 3:
            geoid = coord_systems.GeogCS(
                semi_major_axis=self.scaledValueOfEarthMajorAxis *
                10**-self.scaleFactorOfEarthMajorAxis * 1000.,
                semi_minor_axis=self.scaledValueOfEarthMinorAxis *
                10**-self.scaleFactorOfEarthMinorAxis * 1000.)

        #IAG-GRS80 oblate spheroid
        elif self.shapeOfTheEarth == 4:
            geoid = coord_systems.GeogCS(6378137, None, 298.257222101)

        #WGS84
        elif self.shapeOfTheEarth == 5:
            geoid = \
                coord_systems.GeogCS(6378137, inverse_flattening=298.257223563)

        #pre-defined sphere
        elif self.shapeOfTheEarth == 6:
            geoid = coord_systems.GeogCS(6371229)

        #custom oblate spheroid (m)
        elif self.shapeOfTheEarth == 7:
            geoid = coord_systems.GeogCS(
                semi_major_axis=self.scaledValueOfEarthMajorAxis *
                10**-self.scaleFactorOfEarthMajorAxis,
                semi_minor_axis=self.scaledValueOfEarthMinorAxis *
                10**-self.scaleFactorOfEarthMinorAxis)

        elif self.shapeOfTheEarth == 8:
            raise ValueError("unhandled shape of earth : grib earth shape = 8")

        else:
            raise ValueError("undefined shape of earth")

        gridType = gribapi.grib_get_string(self.grib_message, "gridType")

        if gridType in [
                "regular_ll", "regular_gg", "reduced_ll", "reduced_gg"
        ]:
            self.extra_keys['_x_coord_name'] = "longitude"
            self.extra_keys['_y_coord_name'] = "latitude"
            self.extra_keys['_coord_system'] = geoid
        elif gridType == 'rotated_ll':
            # TODO: Confirm the translation from angleOfRotation to
            # north_pole_lon (usually 0 for both)
            self.extra_keys['_x_coord_name'] = "grid_longitude"
            self.extra_keys['_y_coord_name'] = "grid_latitude"
            southPoleLon = longitudeOfSouthernPoleInDegrees
            southPoleLat = latitudeOfSouthernPoleInDegrees
            self.extra_keys['_coord_system'] = \
                iris.coord_systems.RotatedGeogCS(
                                        -southPoleLat,
                                        math.fmod(southPoleLon + 180.0, 360.0),
                                        self.angleOfRotation, geoid)
        elif gridType == 'polar_stereographic':
            self.extra_keys['_x_coord_name'] = "projection_x_coordinate"
            self.extra_keys['_y_coord_name'] = "projection_y_coordinate"

            if self.projectionCentreFlag == 0:
                pole_lat = 90
            elif self.projectionCentreFlag == 1:
                pole_lat = -90
            else:
                raise TranslationError("Unhandled projectionCentreFlag")

            # Note: I think the grib api defaults LaDInDegrees to 60 for grib1.
            self.extra_keys['_coord_system'] = \
                iris.coord_systems.Stereographic(
                    pole_lat, self.orientationOfTheGridInDegrees, 0, 0,
                    self.LaDInDegrees, ellipsoid=geoid)

        elif gridType == 'lambert':
            self.extra_keys['_x_coord_name'] = "projection_x_coordinate"
            self.extra_keys['_y_coord_name'] = "projection_y_coordinate"

            if self.edition == 1:
                flag_name = "projectionCenterFlag"
            else:
                flag_name = "projectionCentreFlag"

            if getattr(self, flag_name) == 0:
                pole_lat = 90
            elif getattr(self, flag_name) == 1:
                pole_lat = -90
            else:
                raise TranslationError("Unhandled projectionCentreFlag")

            LambertConformal = iris.coord_systems.LambertConformal
            self.extra_keys['_coord_system'] = LambertConformal(
                self.LaDInDegrees,
                self.LoVInDegrees,
                0,
                0,
                secant_latitudes=(self.Latin1InDegrees, self.Latin2InDegrees),
                ellipsoid=geoid)
        else:
            raise TranslationError("unhandled grid type: {}".format(gridType))

        if gridType in ["regular_ll", "rotated_ll"]:
            self._regular_longitude_common()
            j_step = self.jDirectionIncrementInDegrees
            if not self.jScansPositively:
                j_step = -j_step
            self._y_points = (np.arange(self.Nj, dtype=np.float64) * j_step +
                              self.latitudeOfFirstGridPointInDegrees)

        elif gridType in ['regular_gg']:
            # longitude coordinate is straight-forward
            self._regular_longitude_common()
            # get the distinct latitudes, and make sure they are sorted
            # (south-to-north) and then put them in the right direction
            # depending on the scan direction
            latitude_points = gribapi.grib_get_double_array(
                self.grib_message, 'distinctLatitudes').astype(np.float64)
            latitude_points.sort()
            if not self.jScansPositively:
                # we require latitudes north-to-south
                self._y_points = latitude_points[::-1]
            else:
                self._y_points = latitude_points

        elif gridType in ["polar_stereographic", "lambert"]:
            # convert the starting latlon into meters
            cartopy_crs = self.extra_keys['_coord_system'].as_cartopy_crs()
            x1, y1 = cartopy_crs.transform_point(
                self.longitudeOfFirstGridPointInDegrees,
                self.latitudeOfFirstGridPointInDegrees, ccrs.Geodetic())

            if not np.all(np.isfinite([x1, y1])):
                raise TranslationError("Could not determine the first latitude"
                                       " and/or longitude grid point.")

            self._x_points = x1 + self.DxInMetres * np.arange(self.Nx,
                                                              dtype=np.float64)
            self._y_points = y1 + self.DyInMetres * np.arange(self.Ny,
                                                              dtype=np.float64)

        elif gridType in ["reduced_ll", "reduced_gg"]:
            self._x_points = self.longitudes
            self._y_points = self.latitudes

        else:
            raise TranslationError("unhandled grid type")
Beispiel #6
0
    def _compute_extra_keys(self):
        """Compute our extra keys."""
        global unknown_string

        self.extra_keys = {}
        forecastTime = self.startStep

        # regular or rotated grid?
        try:
            longitudeOfSouthernPoleInDegrees = \
                self.longitudeOfSouthernPoleInDegrees
            latitudeOfSouthernPoleInDegrees = \
                self.latitudeOfSouthernPoleInDegrees
        except AttributeError:
            longitudeOfSouthernPoleInDegrees = 0.0
            latitudeOfSouthernPoleInDegrees = 90.0

        centre = gribapi.grib_get_string(self.grib_message, "centre")

        # default values
        self.extra_keys = {'_referenceDateTime': -1.0,
                           '_phenomenonDateTime': -1.0,
                           '_periodStartDateTime': -1.0,
                           '_periodEndDateTime': -1.0,
                           '_levelTypeName': unknown_string,
                           '_levelTypeUnits': unknown_string,
                           '_firstLevelTypeName': unknown_string,
                           '_firstLevelTypeUnits': unknown_string,
                           '_firstLevel': -1.0,
                           '_secondLevelTypeName': unknown_string,
                           '_secondLevel': -1.0,
                           '_originatingCentre': unknown_string,
                           '_forecastTime': None,
                           '_forecastTimeUnit': unknown_string,
                           '_coord_system': None,
                           '_x_circular': False,
                           '_x_coord_name': unknown_string,
                           '_y_coord_name': unknown_string,
                           # These are here to avoid repetition in the rules
                           # files, and reduce the very long line lengths.
                           '_x_points': None,
                           '_y_points': None,
                           '_cf_data': None}

        # cf phenomenon translation
        # Get centre code (N.B. self.centre has default type = string)
        centre_number = gribapi.grib_get_long(self.grib_message, "centre")
        # Look for a known grib1-to-cf translation (or None).
        cf_data = gptx.grib1_phenom_to_cf_info(
            table2_version=self.table2Version,
            centre_number=centre_number,
            param_number=self.indicatorOfParameter)
        self.extra_keys['_cf_data'] = cf_data

        # reference date
        self.extra_keys['_referenceDateTime'] = \
            datetime.datetime(int(self.year), int(self.month), int(self.day),
                              int(self.hour), int(self.minute))

        # forecast time with workarounds
        self.extra_keys['_forecastTime'] = forecastTime

        # verification date
        processingDone = self._get_processing_done()
        # time processed?
        if processingDone.startswith("time"):
            validityDate = str(self.validityDate)
            validityTime = "{:04}".format(int(self.validityTime))
            endYear = int(validityDate[:4])
            endMonth = int(validityDate[4:6])
            endDay = int(validityDate[6:8])
            endHour = int(validityTime[:2])
            endMinute = int(validityTime[2:4])

            # fixed forecastTime in hours
            self.extra_keys['_periodStartDateTime'] = \
                (self.extra_keys['_referenceDateTime'] +
                 datetime.timedelta(hours=int(forecastTime)))
            self.extra_keys['_periodEndDateTime'] = \
                datetime.datetime(endYear, endMonth, endDay, endHour,
                                  endMinute)
        else:
            self.extra_keys['_phenomenonDateTime'] = \
                self._get_verification_date()

        # originating centre
        # TODO #574 Expand to include sub-centre
        self.extra_keys['_originatingCentre'] = CENTRE_TITLES.get(
            centre, "unknown centre %s" % centre)

        # forecast time unit as a cm string
        # TODO #575 Do we want PP or GRIB style forecast delta?
        self.extra_keys['_forecastTimeUnit'] = self._timeunit_string()

        # shape of the earth
        soe_code = self.shapeOfTheEarth
        # As this class is now *only* for GRIB1, 'shapeOfTheEarth' is not a
        # value read from the actual file :  It is really a GRIB2 param, and
        # the value is merely what eccodes (gribapi) gives as the default.
        # This was always = 6, until eccodes 0.19, when it changed to 0.
        # See https://jira.ecmwf.int/browse/ECC-811
        # The two represent different sized spherical earths.
        if soe_code not in (6, 0):
            raise ValueError('Unexpected shapeOfTheEarth value =', soe_code)

        soe_code = 6
        # *FOR NOW* maintain the old behaviour (radius=6371229) in all cases,
        # for backwards compatibility.
        # However, this does not match the 'radiusOfTheEarth' default from the
        # gribapi so is probably incorrect (see above issue ECC-811).
        # So we may change this in future.

        if soe_code == 0:
            # New supposedly-correct default value, matches 'radiusOfTheEarth'.
            geoid = coord_systems.GeogCS(semi_major_axis=6367470)
        elif soe_code == 6:
            # Old value, does *not* match the 'radiusOfTheEarth' parameter.
            geoid = coord_systems.GeogCS(6371229)

        gridType = gribapi.grib_get_string(self.grib_message, "gridType")

        if gridType in ["regular_ll", "regular_gg", "reduced_ll",
                        "reduced_gg"]:
            self.extra_keys['_x_coord_name'] = "longitude"
            self.extra_keys['_y_coord_name'] = "latitude"
            self.extra_keys['_coord_system'] = geoid
        elif gridType == 'rotated_ll':
            # TODO: Confirm the translation from angleOfRotation to
            # north_pole_lon (usually 0 for both)
            self.extra_keys['_x_coord_name'] = "grid_longitude"
            self.extra_keys['_y_coord_name'] = "grid_latitude"
            southPoleLon = longitudeOfSouthernPoleInDegrees
            southPoleLat = latitudeOfSouthernPoleInDegrees
            self.extra_keys['_coord_system'] = \
                coord_systems.RotatedGeogCS(
                    -southPoleLat,
                    math.fmod(southPoleLon + 180.0, 360.0),
                    self.angleOfRotation, geoid)
        elif gridType == 'polar_stereographic':
            self.extra_keys['_x_coord_name'] = "projection_x_coordinate"
            self.extra_keys['_y_coord_name'] = "projection_y_coordinate"

            if self.projectionCentreFlag == 0:
                pole_lat = 90
            elif self.projectionCentreFlag == 1:
                pole_lat = -90
            else:
                raise TranslationError("Unhandled projectionCentreFlag")

            # Note: I think the grib api defaults LaDInDegrees to 60 for grib1.
            self.extra_keys['_coord_system'] = \
                coord_systems.Stereographic(
                    pole_lat, self.orientationOfTheGridInDegrees, 0, 0,
                    self.LaDInDegrees, ellipsoid=geoid)

        elif gridType == 'lambert':
            self.extra_keys['_x_coord_name'] = "projection_x_coordinate"
            self.extra_keys['_y_coord_name'] = "projection_y_coordinate"

            flag_name = "projectionCenterFlag"

            if getattr(self, flag_name) == 0:
                pole_lat = 90
            elif getattr(self, flag_name) == 1:
                pole_lat = -90
            else:
                raise TranslationError("Unhandled projectionCentreFlag")

            LambertConformal = coord_systems.LambertConformal
            self.extra_keys['_coord_system'] = LambertConformal(
                self.LaDInDegrees, self.LoVInDegrees, 0, 0,
                secant_latitudes=(self.Latin1InDegrees, self.Latin2InDegrees),
                ellipsoid=geoid)
        else:
            raise TranslationError("unhandled grid type: {}".format(gridType))

        if gridType in ["regular_ll", "rotated_ll"]:
            self._regular_longitude_common()
            j_step = self.jDirectionIncrementInDegrees
            if not self.jScansPositively:
                j_step = -j_step
            self._y_points = (np.arange(self.Nj, dtype=np.float64) * j_step +
                              self.latitudeOfFirstGridPointInDegrees)

        elif gridType in ['regular_gg']:
            # longitude coordinate is straight-forward
            self._regular_longitude_common()
            # get the distinct latitudes, and make sure they are sorted
            # (south-to-north) and then put them in the right direction
            # depending on the scan direction
            latitude_points = gribapi.grib_get_double_array(
                    self.grib_message, 'distinctLatitudes').astype(np.float64)
            latitude_points.sort()
            if not self.jScansPositively:
                # we require latitudes north-to-south
                self._y_points = latitude_points[::-1]
            else:
                self._y_points = latitude_points

        elif gridType in ["polar_stereographic", "lambert"]:
            # convert the starting latlon into meters
            cartopy_crs = self.extra_keys['_coord_system'].as_cartopy_crs()
            x1, y1 = cartopy_crs.transform_point(
                self.longitudeOfFirstGridPointInDegrees,
                self.latitudeOfFirstGridPointInDegrees,
                ccrs.Geodetic())

            if not np.all(np.isfinite([x1, y1])):
                raise TranslationError("Could not determine the first latitude"
                                       " and/or longitude grid point.")

            self._x_points = x1 + self.DxInMetres * np.arange(self.Nx,
                                                              dtype=np.float64)
            self._y_points = y1 + self.DyInMetres * np.arange(self.Ny,
                                                              dtype=np.float64)

        elif gridType in ["reduced_ll", "reduced_gg"]:
            self._x_points = self.longitudes
            self._y_points = self.latitudes

        else:
            raise TranslationError("unhandled grid type")
Beispiel #7
0
def _convert_collation(collation):
    """
    Converts a FieldCollation into the corresponding items of Cube
    metadata.

    Args:

    * collation:
        A FieldCollation object.

    Returns:
        A :class:`iris.fileformats.rules.ConversionMetadata` object.

    .. note:
        This is the 'loader.converter', in the control structure passed to the
        generic rules code, :meth:`iris.fileformats.rules.load_cubes`.

    """
    from iris.fileformats.rules import ConversionMetadata
    from iris.fileformats.pp_load_rules import \
        (_convert_time_coords,
         _convert_vertical_coords,
         _convert_scalar_realization_coords,
         _convert_scalar_pseudo_level_coords,
         _all_other_rules)

    # For all the scalar conversions, all fields in the collation will
    # give the same result, so the choice is arbitrary.
    field = collation.fields[0]

    # Call "all other" rules.
    (references, standard_name, long_name, units, attributes, cell_methods,
     dim_coords_and_dims, aux_coords_and_dims) = _all_other_rules(field)

    # Adjust any dimension bindings to account for the extra leading
    # dimensions added by the collation.
    if collation.vector_dims_shape:

        def _adjust_dims(coords_and_dims, n_dims):
            def adjust(dims):
                if dims is not None:
                    dims += n_dims
                return dims

            return [(coord, adjust(dims)) for coord, dims in coords_and_dims]

        n_collation_dims = len(collation.vector_dims_shape)
        dim_coords_and_dims = _adjust_dims(dim_coords_and_dims,
                                           n_collation_dims)
        aux_coords_and_dims = _adjust_dims(aux_coords_and_dims,
                                           n_collation_dims)

    # Dimensions to which we've already assigned dimension coordinates.
    dim_coord_dims = set()

    # Helper call to choose which coords are dimensions and which auxiliary.
    def _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims,
                     aux_coords_and_dims):
        def key_func(item):
            return _HINTS.get(item[0].name(), len(_HINTS))

        # Target the first DimCoord for a dimension at dim_coords,
        # and target everything else at aux_coords.
        for coord, dims in sorted(coords_and_dims, key=key_func):
            if (isinstance(coord, DimCoord) and dims is not None
                    and len(dims) == 1 and dims[0] not in dim_coord_dims):
                dim_coords_and_dims.append((coord, dims))
                dim_coord_dims.add(dims[0])
            else:
                aux_coords_and_dims.append((coord, dims))

    # Call "time" rules.
    #
    # For "normal" (non-cross-sectional) time values.
    vector_headers = collation.element_arrays_and_dims
    # If the collation doesn't define a vector of values for a
    # particular header then it must be constant over all fields in the
    # collation. In which case it's safe to get the value from any field.
    t1, t1_dims = vector_headers.get('t1', (field.t1, ()))
    t2, t2_dims = vector_headers.get('t2', (field.t2, ()))
    lbft, lbft_dims = vector_headers.get('lbft', (field.lbft, ()))
    coords_and_dims = _convert_time_coords(field.lbcode, field.lbtim,
                                           field.time_unit('hours'), t1, t2,
                                           lbft, t1_dims, t2_dims, lbft_dims)
    # Bind resulting coordinates to dimensions, where suitable.
    _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims,
                 aux_coords_and_dims)

    # Call "vertical" rules.
    #
    # "Normal" (non-cross-sectional) vertical levels
    blev, blev_dims = vector_headers.get('blev', (field.blev, ()))
    lblev, lblev_dims = vector_headers.get('lblev', (field.lblev, ()))
    bhlev, bhlev_dims = vector_headers.get('bhlev', (field.bhlev, ()))
    bhrlev, bhrlev_dims = vector_headers.get('bhrlev', (field.bhrlev, ()))
    brsvd1, brsvd1_dims = vector_headers.get('brsvd1', (field.brsvd[0], ()))
    brsvd2, brsvd2_dims = vector_headers.get('brsvd2', (field.brsvd[1], ()))
    brlev, brlev_dims = vector_headers.get('brlev', (field.brlev, ()))
    # Find all the non-trivial dimension values
    dims = set(
        filter(None, [
            blev_dims, lblev_dims, bhlev_dims, bhrlev_dims, brsvd1_dims,
            brsvd2_dims, brlev_dims
        ]))
    if len(dims) > 1:
        raise TranslationError('Unsupported multiple values for vertical '
                               'dimension.')
    if dims:
        v_dims = dims.pop()
        if len(v_dims) > 1:
            raise TranslationError('Unsupported multi-dimension vertical '
                                   'headers.')
    else:
        v_dims = ()
    coords_and_dims, factories = _convert_vertical_coords(
        field.lbcode, field.lbvc, blev, lblev, field.stash, bhlev, bhrlev,
        brsvd1, brsvd2, brlev, v_dims)
    # Bind resulting coordinates to dimensions, where suitable.
    _bind_coords(coords_and_dims, dim_coord_dims, dim_coords_and_dims,
                 aux_coords_and_dims)

    # Realization (aka ensemble) (--> scalar coordinates)
    aux_coords_and_dims.extend(
        _convert_scalar_realization_coords(lbrsvd4=field.lbrsvd[3]))

    # Pseudo-level coordinate (--> scalar coordinates)
    aux_coords_and_dims.extend(
        _convert_scalar_pseudo_level_coords(lbuser5=field.lbuser[4]))

    return ConversionMetadata(factories, references, standard_name, long_name,
                              units, attributes, cell_methods,
                              dim_coords_and_dims, aux_coords_and_dims)