Exemple #1
0
    def _regularise_shape(grib_message):
        """
        Calculate the regularised shape of the reduced message and push
        dummy regularised values into the message to force the gribapi
        to update the message grid type from reduced to regular.

        """
        # 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

        gribapi.grib_set_long(grib_message, "Nx", int(new_nx))
        gribapi.grib_set_double(grib_message, "iDirectionIncrementInDegrees",
                                float(new_x_step))
        # Spoof gribapi with false regularised values.
        nj = gribapi.grib_get_long(grib_message, 'Nj')
        temp = np.zeros((nj * new_nx,), dtype=np.float)
        gribapi.grib_set_double_array(grib_message, 'values', temp)
        gribapi.grib_set_long(grib_message, "jPointsAreConsecutive", 0)
        gribapi.grib_set_long(grib_message, "PLPresent", 0)
Exemple #2
0
    def __getattr__(self, key):
        """Return a grib key, or one of our extra keys."""

        # is it in the grib message?
        try:
            # we just get <type 'float'> as the type of the "values" array...special case here...
            if key in ["values", "pv", "latitudes", "longitudes"]:
                res = gribapi.grib_get_double_array(self.grib_message, key)
            elif key in ('typeOfFirstFixedSurface', 'typeOfSecondFixedSurface'):
                res = np.int32(gribapi.grib_get_long(self.grib_message, key))
            else:
                key_type = gribapi.grib_get_native_type(self.grib_message, key)
                if key_type == int:
                    res = np.int32(gribapi.grib_get_long(self.grib_message, key))
                elif key_type == float:
                    # Because some computer keys are floats, like
                    # longitudeOfFirstGridPointInDegrees, a float32 is not always enough...
                    res = np.float64(gribapi.grib_get_double(self.grib_message, key))
                elif key_type == str:
                    res = gribapi.grib_get_string(self.grib_message, key)
                else:
                    raise ValueError("Unknown type for %s : %s" % (key, str(key_type)))
        except gribapi.GribInternalError:
            res = None

        #...or is it in our list of extras?
        if res is None:
            if key in self.extra_keys:
                res = self.extra_keys[key]
            else:
                #must raise an exception for the hasattr() mechanism to work
                raise AttributeError("Cannot find GRIB key %s" % key)

        return res
Exemple #3
0
    def _regularise_shape(grib_message):
        """
        Calculate the regularised shape of the reduced message and push
        dummy regularised values into the message to force the gribapi
        to update the message grid type from reduced to regular.

        """
        # 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

        gribapi.grib_set_long(grib_message, "Nx", int(new_nx))
        gribapi.grib_set_double(grib_message, "iDirectionIncrementInDegrees",
                                float(new_x_step))
        # Spoof gribapi with false regularised values.
        nj = gribapi.grib_get_long(grib_message, 'Nj')
        temp = np.zeros((nj * new_nx,), dtype=np.float)
        gribapi.grib_set_double_array(grib_message, 'values', temp)
        gribapi.grib_set_long(grib_message, "jPointsAreConsecutive", 0)
        gribapi.grib_set_long(grib_message, "PLPresent", 0)
Exemple #4
0
    def __getattr__(self, key):
        """Return a grib key, or one of our extra keys."""

        # is it in the grib message?
        try:
            # we just get <type 'float'> as the type of the "values" array...special case here...
            if key in ["values", "pv", "latitudes", "longitudes"]:
                res = gribapi.grib_get_double_array(self.grib_message, key)
            elif key in ('typeOfFirstFixedSurface', 'typeOfSecondFixedSurface'):
                res = np.int32(gribapi.grib_get_long(self.grib_message, key))
            else:
                key_type = gribapi.grib_get_native_type(self.grib_message, key)
                if key_type == int:
                    res = np.int32(gribapi.grib_get_long(self.grib_message, key))
                elif key_type == float:
                    # Because some computer keys are floats, like
                    # longitudeOfFirstGridPointInDegrees, a float32 is not always enough...
                    res = np.float64(gribapi.grib_get_double(self.grib_message, key))
                elif key_type == str:
                    res = gribapi.grib_get_string(self.grib_message, key)
                else:
                    raise ValueError("Unknown type for %s : %s" % (key, str(key_type)))
        except gribapi.GribInternalError:
            res = None

        #...or is it in our list of extras?
        if res is None:
            if key in self.extra_keys:
                res = self.extra_keys[key]
            else:
                #must raise an exception for the hasattr() mechanism to work
                raise AttributeError("Cannot find GRIB key %s" % key)

        return res
Exemple #5
0
    def __init__(self, grib_message, grib_fh=None):
        """Store the grib message and compute our extra keys."""
        self.grib_message = grib_message
        self.realised_dtype = np.array([0.]).dtype

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

        deferred = grib_fh is not None

        # Store the file pointer and message length from the current
        # grib message before it's changed by calls to the grib-api.
        if deferred:
            # Note that, the grib-api has already read this message and
            # advanced the file pointer to the end of the message.
            offset = grib_fh.tell()
            message_length = gribapi.grib_get_long(grib_message, 'totalLength')

        # Initialise the key-extension dictionary.
        # NOTE: this attribute *must* exist, or the the __getattr__ overload
        # can hit an infinite loop.
        self.extra_keys = {}
        self._confirm_in_scope()
        self._compute_extra_keys()

        # Calculate the data payload shape.
        shape = (gribapi.grib_get_long(grib_message, 'numberOfValues'), )

        if not self.gridType.startswith('reduced'):
            ni, nj = self.Ni, self.Nj
            j_fast = gribapi.grib_get_long(grib_message,
                                           'jPointsAreConsecutive')
            shape = (nj, ni) if j_fast == 0 else (ni, nj)

        if deferred:
            # Wrap the reference to the data payload within the data proxy
            # in order to support deferred data loading.
            # The byte offset requires to be reset back to the first byte
            # of this message. The file pointer offset is always at the end
            # of the current message due to the grib-api reading the message.
            proxy = GribDataProxy(shape, self.realised_dtype, grib_fh.name,
                                  offset - message_length)
            self._data = as_lazy_data(proxy)
        else:
            values_array = _message_values(grib_message, shape)
            # mask where the values are nan
            self.data = convert_nans_array(values_array,
                                           nans_replacement=ma.masked)
Exemple #6
0
    def __init__(self, grib_message, grib_fh=None, auto_regularise=True):
        warn_deprecated('Deprecated at version 1.10')
        """Store the grib message and compute our extra keys."""
        self.grib_message = grib_message
        deferred = grib_fh is not None

        # Store the file pointer and message length from the current
        # grib message before it's changed by calls to the grib-api.
        if deferred:
            # Note that, the grib-api has already read this message and 
            # advanced the file pointer to the end of the message.
            offset = grib_fh.tell()
            message_length = gribapi.grib_get_long(grib_message, 'totalLength')

        if auto_regularise and _is_quasi_regular_grib(grib_message):
            warnings.warn('Regularising GRIB message.')
            if deferred:
                self._regularise_shape(grib_message)
            else:
                _regularise(grib_message)

        # Initialise the key-extension dictionary.
        # NOTE: this attribute *must* exist, or the the __getattr__ overload
        # can hit an infinite loop.
        self.extra_keys = {}
        self._confirm_in_scope()
        self._compute_extra_keys()

        # Calculate the data payload shape.
        shape = (gribapi.grib_get_long(grib_message, 'numberOfValues'),)

        if not self.gridType.startswith('reduced'):
            ni, nj = self.Ni, self.Nj
            j_fast = gribapi.grib_get_long(grib_message,
                                           'jPointsAreConsecutive')
            shape = (nj, ni) if j_fast == 0 else (ni, nj)

        if deferred:
            # Wrap the reference to the data payload within the data proxy
            # in order to support deferred data loading.
            # The byte offset requires to be reset back to the first byte
            # of this message. The file pointer offset is always at the end 
            # of the current message due to the grib-api reading the message.
            proxy = GribDataProxy(shape, np.zeros(.0).dtype, np.nan,
                                  grib_fh.name,
                                  offset - message_length,
                                  auto_regularise)
            self._data = biggus.NumpyArrayAdapter(proxy)
        else:
            self.data = _message_values(grib_message, shape)
Exemple #7
0
    def __init__(self, grib_message, grib_fh=None):
        """Store the grib message and compute our extra keys."""
        self.grib_message = grib_message
        self.realised_dtype = np.array([0.]).dtype

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

        deferred = grib_fh is not None

        # Store the file pointer and message length from the current
        # grib message before it's changed by calls to the grib-api.
        if deferred:
            # Note that, the grib-api has already read this message and
            # advanced the file pointer to the end of the message.
            offset = grib_fh.tell()
            message_length = gribapi.grib_get_long(grib_message, 'totalLength')

        # Initialise the key-extension dictionary.
        # NOTE: this attribute *must* exist, or the the __getattr__ overload
        # can hit an infinite loop.
        self.extra_keys = {}
        self._confirm_in_scope()
        self._compute_extra_keys()

        # Calculate the data payload shape.
        shape = (gribapi.grib_get_long(grib_message, 'numberOfValues'),)

        if not self.gridType.startswith('reduced'):
            ni, nj = self.Ni, self.Nj
            j_fast = gribapi.grib_get_long(grib_message,
                                           'jPointsAreConsecutive')
            shape = (nj, ni) if j_fast == 0 else (ni, nj)

        if deferred:
            # Wrap the reference to the data payload within the data proxy
            # in order to support deferred data loading.
            # The byte offset requires to be reset back to the first byte
            # of this message. The file pointer offset is always at the end
            # of the current message due to the grib-api reading the message.
            proxy = GribDataProxy(shape, self.realised_dtype, grib_fh.name,
                                  offset - message_length)
            self._data = as_lazy_data(proxy)
        else:
            values_array = _message_values(grib_message, shape)
            # mask where the values are nan
            self.data = convert_nans_array(values_array,
                                           nans_replacement=ma.masked)
Exemple #8
0
    def __init__(self, grib_message, grib_fh=None, auto_regularise=True):
        """Store the grib message and compute our extra keys."""
        self.grib_message = grib_message
        deferred = grib_fh is not None

        # Store the file pointer and message length from the current
        # grib message before it's changed by calls to the grib-api.
        if deferred:
            # Note that, the grib-api has already read this message and 
            # advanced the file pointer to the end of the message.
            offset = grib_fh.tell()
            message_length = gribapi.grib_get_long(grib_message, 'totalLength')

        if auto_regularise and _is_quasi_regular_grib(grib_message):
            warnings.warn('Regularising GRIB message.')
            if deferred:
                self._regularise_shape(grib_message)
            else:
                _regularise(grib_message)

        # Initialise the key-extension dictionary.
        # NOTE: this attribute *must* exist, or the the __getattr__ overload
        # can hit an infinite loop.
        self.extra_keys = {}
        self._confirm_in_scope()
        self._compute_extra_keys()

        # Calculate the data payload shape.
        shape = (gribapi.grib_get_long(grib_message, 'numberOfValues'),)

        if not self.gridType.startswith('reduced'):
            ni, nj = self.Ni, self.Nj
            j_fast = gribapi.grib_get_long(grib_message,
                                           'jPointsAreConsecutive')
            shape = (nj, ni) if j_fast == 0 else (ni, nj)

        if deferred:
            # Wrap the reference to the data payload within the data proxy
            # in order to support deferred data loading.
            # The byte offset requires to be reset back to the first byte
            # of this message. The file pointer offset is always at the end 
            # of the current message due to the grib-api reading the message.
            proxy = GribDataProxy(shape, np.zeros(.0).dtype, np.nan,
                                  grib_fh.name,
                                  offset - message_length,
                                  auto_regularise)
            self._data = biggus.NumpyArrayAdapter(proxy)
        else:
            self.data = _message_values(grib_message, shape)
Exemple #9
0
 def test_as_messages(self):
     realization = 2
     type_of_process = 4
     coord = DimCoord(realization, standard_name='realization', units='1')
     self.cube.add_aux_coord(coord)
     messages = grib.as_messages(self.cube)
     for message in messages:
         self.assertEqual(gribapi.grib_get_long(message,
                                                'typeOfProcessedData'),
                          type_of_process)
 def test_bounded_altitude_feet(self):
     cube = iris.cube.Cube([0])
     cube.add_aux_coord(
         iris.coords.AuxCoord(1500.0,
                              long_name='altitude',
                              units='ft',
                              bounds=np.array([1000.0, 2000.0])))
     grib = gribapi.grib_new_from_samples("GRIB2")
     set_fixed_surfaces(cube, grib)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfFirstFixedSurface"),
         304.0)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfSecondFixedSurface"),
         609.0)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfFirstFixedSurface"), 102)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfSecondFixedSurface"), 102)
Exemple #11
0
 def test_as_messages(self):
     realization = 2
     type_of_process = 4
     coord = DimCoord(realization, standard_name='realization', units='1')
     self.cube.add_aux_coord(coord)
     messages = grib.as_messages(self.cube)
     for message in messages:
         self.assertEqual(
             gribapi.grib_get_long(message, 'typeOfProcessedData'),
             type_of_process)
 def test_bounded_altitude_feet(self):
     cube = iris.cube.Cube([0])
     cube.add_aux_coord(iris.coords.AuxCoord(
         1500.0, long_name='altitude', units='ft',
         bounds=np.array([1000.0, 2000.0])))
     grib = gribapi.grib_new_from_samples("GRIB2")
     set_fixed_surfaces(cube, grib)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfFirstFixedSurface"),
         304.0)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfSecondFixedSurface"),
         609.0)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfFirstFixedSurface"),
         102)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfSecondFixedSurface"),
         102)
 def test_theta_level(self):
     cube = iris.cube.Cube([0])
     cube.add_aux_coord(
         iris.coords.AuxCoord(230.0,
                              standard_name='air_potential_temperature',
                              units='K',
                              attributes={'positive': 'up'},
                              bounds=np.array([220.0, 240.0])))
     grib = gribapi.grib_new_from_samples("GRIB2")
     set_fixed_surfaces(cube, grib)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfFirstFixedSurface"),
         220.0)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfSecondFixedSurface"),
         240.0)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfFirstFixedSurface"), 107)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfSecondFixedSurface"), 107)
 def test_depth(self):
     cube = iris.cube.Cube([0])
     cube.add_aux_coord(
         iris.coords.AuxCoord(1,
                              long_name='depth',
                              units='m',
                              bounds=np.array([0., 2]),
                              attributes={'positive': 'down'}))
     grib = gribapi.grib_new_from_samples("GRIB2")
     set_fixed_surfaces(cube, grib)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfFirstFixedSurface"),
         0.)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfSecondFixedSurface"),
         2)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfFirstFixedSurface"), 106)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfSecondFixedSurface"), 106)
Exemple #15
0
 def test_as_pairs(self):
     realization = 2
     type_of_process = 4
     coord = DimCoord(realization, standard_name='realization', units='1')
     self.cube.add_aux_coord(coord)
     slices_and_messages = grib.as_pairs(self.cube)
     for aslice, message in slices_and_messages:
         self.assertEqual(aslice.shape, (9, 11))
         self.assertEqual(gribapi.grib_get_long(message,
                                                'typeOfProcessedData'),
                          type_of_process)
 def test_theta_level(self):
     cube = iris.cube.Cube([0])
     cube.add_aux_coord(iris.coords.AuxCoord(
         230.0, standard_name='air_potential_temperature',
         units='K', attributes={'positive': 'up'},
         bounds=np.array([220.0, 240.0])))
     grib = gribapi.grib_new_from_samples("GRIB2")
     set_fixed_surfaces(cube, grib)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfFirstFixedSurface"),
         220.0)
     self.assertEqual(
         gribapi.grib_get_double(grib, "scaledValueOfSecondFixedSurface"),
         240.0)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfFirstFixedSurface"),
         107)
     self.assertEqual(
         gribapi.grib_get_long(grib, "typeOfSecondFixedSurface"),
         107)
 def test_as_pairs(self):
     realization = 2
     type_of_process = 4
     coord = DimCoord(realization, standard_name='realization', units='1')
     self.cube.add_aux_coord(coord)
     slices_and_messages = grib.as_pairs(self.cube)
     for aslice, message in slices_and_messages:
         self.assertEqual(aslice.shape, (9, 11))
         self.assertEqual(
             gribapi.grib_get_long(message, 'typeOfProcessedData'),
             type_of_process)
Exemple #18
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")
Exemple #19
0
 def get_field(self, name):
     return gribapi.grib_get_long(self.record, name)
Exemple #20
0
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)
Exemple #21
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

        # 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'] = \
                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")
Exemple #22
0
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)
Exemple #23
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")