Example #1
0
def grid_definition_section(cube, grib):
    """
    Set keys within the grid definition section of the provided grib message,
    based on the properties of the cube.

    """
    x_coord = cube.coord(dimensions=[1])
    y_coord = cube.coord(dimensions=[0])
    cs = x_coord.coord_system  # N.B. already checked same cs for x and y.
    regular_x_and_y = is_regular(x_coord) and is_regular(y_coord)

    if isinstance(cs, GeogCS):
        if not regular_x_and_y:
            raise iris.exceptions.TranslationError(
                'Saving an irregular latlon grid to GRIB (PDT3.4) is not '
                'yet supported.')

        grid_definition_template_0(cube, grib)

    elif isinstance(cs, RotatedGeogCS):
        # Rotated coordinate system cases.
        # Choose between GDT 3.1 and 3.5 according to coordinate regularity.
        if regular_x_and_y:
            grid_definition_template_1(cube, grib)
        else:
            grid_definition_template_5(cube, grib)

    elif isinstance(cs, TransverseMercator):
        # Transverse Mercator coordinate system (template 3.12).
        grid_definition_template_12(cube, grib)

    else:
        raise ValueError('Grib saving is not supported for coordinate system: '
                         '{}'.format(cs))
Example #2
0
def grid_definition_section(cube, grib):
    """
    Set keys within the grid definition section of the provided grib message,
    based on the properties of the cube.

    """
    x_coord = cube.coord(dimensions=[1])
    y_coord = cube.coord(dimensions=[0])
    cs = x_coord.coord_system  # N.B. already checked same cs for x and y.
    regular_x_and_y = is_regular(x_coord) and is_regular(y_coord)

    if isinstance(cs, GeogCS):
        if not regular_x_and_y:
            raise iris.exceptions.TranslationError(
                'Saving an irregular latlon grid to GRIB (PDT3.4) is not '
                'yet supported.')

        grid_definition_template_0(cube, grib)

    elif isinstance(cs, RotatedGeogCS):
        # Rotated coordinate system cases.
        # Choose between GDT 3.1 and 3.5 according to coordinate regularity.
        if regular_x_and_y:
            grid_definition_template_1(cube, grib)
        else:
            grid_definition_template_5(cube, grib)

    elif isinstance(cs, TransverseMercator):
        # Transverse Mercator coordinate system (template 3.12).
        grid_definition_template_12(cube, grib)

    else:
        raise ValueError('Grib saving is not supported for coordinate system: '
                         '{}'.format(cs))
Example #3
0
def roll_cube_pm180(cube_in, coord_name=UM_LATLON[1], inplace=False):
    """
    Take a cube spanning 0...360 degrees in longitude and roll it to -180...180 degrees.

    Works with global model output, and in some cases for regional.

    Parameters
    ----------
    cube: iris.cube.Cube
        Cube with longitude and latitude coordinates.
    coord_name: str, optional
        Name of the longitude coordinate.
    inplace: bool, optional
        Do this in-place or copy the cube.

    Returns
    -------
    iris.cube.Cube

    See also
    --------
    aeolus.grid.roll_cube_0_360
    """
    if inplace:
        cube = cube_in
    else:
        cube = cube_in.copy()
    xcoord = cube.coord(coord_name)
    if (xcoord.points >= 0.0).all():
        assert is_regular(
            xcoord
        ), "Operation is only valid for a regularly spaced coordinate."
        if _is_longitude_global(xcoord.points):
            # Shift data symmetrically only when dealing with global cubes
            cube.data = np.roll(cube.data, len(xcoord.points) // 2, axis=-1)

        if xcoord.has_bounds():
            bounds = wrap_lons(xcoord.bounds, -180, 360)  # + subtract
            bounds = bounds[bounds[:, 0].argsort(axis=0)]
        else:
            bounds = None
        cube.replace_coord(
            xcoord.copy(points=np.sort(wrap_lons(xcoord.points, -180, 360)),
                        bounds=bounds))
    else:
        # Nothing to do, the cube is already centered on 0 longitude
        # unless there is something wrong with longitude
        msg = f"Incorrect {coord_name} values: from {xcoord.points.min()} to {xcoord.points.max()}"
        assert ((xcoord.points >= -180.0) &
                (xcoord.points <= 180.0)).all(), msg
    if not inplace:
        return cube
Example #4
0
    def test_save_load(self):
        # Load sample UKV data (variable-resolution rotated grid).
        path = tests.get_data_path(('PP', 'ukV1', 'ukVpmslont.pp'))
        cube = load_cube(path)

        # Extract a single 2D field, for simplicity.
        self.assertEqual(cube.ndim, 3)
        self.assertEqual(cube.coord_dims('time'), (0,))
        cube = cube[0]

        # FOR NOW: **also** fix the data so that it is square, i.e. nx=ny.
        # This is needed because of a bug in the gribapi.
        # See : https://software.ecmwf.int/issues/browse/SUP-1096
        ny, nx = cube.shape
        nn = min(nx, ny)
        cube = cube[:nn, :nn]

        # Check that it has a rotated-pole variable-spaced grid, as expected.
        x_coord = cube.coord(axis='x')
        self.assertIsInstance(x_coord.coord_system, RotatedGeogCS)
        self.assertFalse(is_regular(x_coord))

        # Write to temporary file, check grib_dump output, and load back in.
        with self.temp_filename('ukv_sample.grib2') as temp_file_path:
            save(cube, temp_file_path)

            # Get a grib_dump of the output file.
            dump_text = check_output(('grib_dump -O -wcount=1 ' +
                                      temp_file_path),
                                     shell=True).decode()

            # Check that various aspects of the saved file are as expected.
            expect_strings = (
                'editionNumber = 2',
                'gridDefinitionTemplateNumber = 5',
                'Ni = {:d}'.format(cube.shape[-1]),
                'Nj = {:d}'.format(cube.shape[-2]),
                'shapeOfTheEarth = 1',
                'scaledValueOfRadiusOfSphericalEarth = {:d}'.format(
                    int(UM_DEFAULT_EARTH_RADIUS)),
                'resolutionAndComponentFlags = 0',
                'latitudeOfSouthernPole = -37500000',
                'longitudeOfSouthernPole = 357500000',
                'angleOfRotation = 0')
            for expect in expect_strings:
                self.assertIn(expect, dump_text)

            # Load the Grib file back into a new cube.
            with FUTURE.context(strict_grib_load=True):
                cube_loaded_from_saved = load_cube(temp_file_path)
                # Also load data, before the temporary file gets deleted.
                cube_loaded_from_saved.data

        # The re-loaded result will not match the original in every respect:
        #  * cube attributes are discarded
        #  * horizontal coordinates are rounded to an integer representation
        #  * bounds on horizontal coords are lost
        # Thus the following "equivalence tests" are rather piecemeal..

        # Check those re-loaded properties which should match the original.
        for test_cube in (cube, cube_loaded_from_saved):
            self.assertEqual(test_cube.standard_name,
                             'air_pressure_at_sea_level')
            self.assertEqual(test_cube.units, 'Pa')
            self.assertEqual(test_cube.shape, (744, 744))
            self.assertEqual(test_cube.cell_methods, ())

        # Check no cube attributes on the re-loaded cube.
        # Note: this does *not* match the original, but is as expected.
        self.assertEqual(cube_loaded_from_saved.attributes, {})

        # Now remaining to check: coordinates + data...

        # Check they have all the same coordinates.
        co_names = [coord.name() for coord in cube.coords()]
        co_names_reload = [coord.name()
                           for coord in cube_loaded_from_saved.coords()]
        self.assertEqual(sorted(co_names_reload), sorted(co_names))

        # Check all the coordinates.
        for coord_name in co_names:
            try:
                co_orig = cube.coord(coord_name)
                co_load = cube_loaded_from_saved.coord(coord_name)

                # Check shape.
                self.assertEqual(co_load.shape, co_orig.shape,
                                 'Shape of re-loaded "{}" coord is {} '
                                 'instead of {}'.format(coord_name,
                                                        co_load.shape,
                                                        co_orig.shape))

                # Check coordinate points equal, within a tolerance.
                self.assertArrayAllClose(co_load.points, co_orig.points,
                                         rtol=1.0e-6)

                # Check all coords are unbounded.
                # (NOTE: this is not so for the original X and Y coordinates,
                # but Grib does not store those bounds).
                self.assertIsNone(co_load.bounds)

            except AssertionError as err:
                self.assertTrue(False,
                                'Failed on coordinate "{}" : {}'.format(
                                    coord_name, str(err)))

        # Check that main data array also matches.
        self.assertArrayAllClose(cube.data, cube_loaded_from_saved.data)
    def test_save_load(self):
        # Load sample UKV data (variable-resolution rotated grid).
        path = tests.get_data_path(('PP', 'ukV1', 'ukVpmslont.pp'))
        cube = load_cube(path)

        # Extract a single 2D field, for simplicity.
        self.assertEqual(cube.ndim, 3)
        self.assertEqual(cube.coord_dims('time'), (0,))
        cube = cube[0]

        # FOR NOW: **also** fix the data so that it is square, i.e. nx=ny.
        # This is needed because of a bug in the gribapi.
        # See : https://software.ecmwf.int/issues/browse/SUP-1096
        ny, nx = cube.shape
        nn = min(nx, ny)
        cube = cube[:nn, :nn]

        # Check that it has a rotated-pole variable-spaced grid, as expected.
        x_coord = cube.coord(axis='x')
        self.assertIsInstance(x_coord.coord_system, RotatedGeogCS)
        self.assertFalse(is_regular(x_coord))

        # Write to temporary file, check that key contents are in the file,
        # then load back in.
        with self.temp_filename('ukv_sample.grib2') as temp_file_path:
            save(cube, temp_file_path)

            # Check that various aspects of the saved file are as expected.
            expect_values = (
                (0, 'editionNumber', 2),
                (3, 'gridDefinitionTemplateNumber', 5),
                (3, 'Ni', cube.shape[-1]),
                (3, 'Nj', cube.shape[-2]),
                (3, 'shapeOfTheEarth', 1),
                (3, 'scaledValueOfRadiusOfSphericalEarth',
                 int(UM_DEFAULT_EARTH_RADIUS)),
                (3, 'resolutionAndComponentFlags', 0),
                (3, 'latitudeOfSouthernPole', -37500000),
                (3, 'longitudeOfSouthernPole', 357500000),
                (3, 'angleOfRotation', 0))
            self.assertGribMessageContents(temp_file_path, expect_values)

            # Load the Grib file back into a new cube.
            with FUTURE.context(strict_grib_load=True):
                cube_loaded_from_saved = load_cube(temp_file_path)
                # Also load data, before the temporary file gets deleted.
                cube_loaded_from_saved.data

        # The re-loaded result will not match the original in every respect:
        #  * cube attributes are discarded
        #  * horizontal coordinates are rounded to an integer representation
        #  * bounds on horizontal coords are lost
        # Thus the following "equivalence tests" are rather piecemeal..

        # Check those re-loaded properties which should match the original.
        for test_cube in (cube, cube_loaded_from_saved):
            self.assertEqual(test_cube.standard_name,
                             'air_pressure_at_sea_level')
            self.assertEqual(test_cube.units, 'Pa')
            self.assertEqual(test_cube.shape, (744, 744))
            self.assertEqual(test_cube.cell_methods, ())

        # Check no cube attributes on the re-loaded cube.
        # Note: this does *not* match the original, but is as expected.
        self.assertEqual(cube_loaded_from_saved.attributes, {})

        # Now remaining to check: coordinates + data...

        # Check they have all the same coordinates.
        co_names = [coord.name() for coord in cube.coords()]
        co_names_reload = [coord.name()
                           for coord in cube_loaded_from_saved.coords()]
        self.assertEqual(sorted(co_names_reload), sorted(co_names))

        # Check all the coordinates.
        for coord_name in co_names:
            try:
                co_orig = cube.coord(coord_name)
                co_load = cube_loaded_from_saved.coord(coord_name)

                # Check shape.
                self.assertEqual(co_load.shape, co_orig.shape,
                                 'Shape of re-loaded "{}" coord is {} '
                                 'instead of {}'.format(coord_name,
                                                        co_load.shape,
                                                        co_orig.shape))

                # Check coordinate points equal, within a tolerance.
                self.assertArrayAllClose(co_load.points, co_orig.points,
                                         rtol=1.0e-6)

                # Check all coords are unbounded.
                # (NOTE: this is not so for the original X and Y coordinates,
                # but Grib does not store those bounds).
                self.assertIsNone(co_load.bounds)

            except AssertionError as err:
                self.assertTrue(False,
                                'Failed on coordinate "{}" : {}'.format(
                                    coord_name, str(err)))

        # Check that main data array also matches.
        self.assertArrayAllClose(cube.data, cube_loaded_from_saved.data)
Example #6
0
def _grid_and_pole_rules(cube, pp):
    """
    Rules for setting the horizontal grid and pole location of the PP field.

    Args:
        cube: the cube being saved as a series of PP fields.
        pp: the current PP field having save rules applied.

    Returns:
        The PP field with updated metadata.

    """
    lon_coord = vector_coord(cube, 'longitude')
    grid_lon_coord = vector_coord(cube, 'grid_longitude')
    lat_coord = vector_coord(cube, 'latitude')
    grid_lat_coord = vector_coord(cube, 'grid_latitude')

    if lon_coord and not is_regular(lon_coord):
        pp.bzx = 0
        pp.bdx = 0
        pp.lbnpt = lon_coord.shape[0]
        pp.x = lon_coord.points
    elif grid_lon_coord and not is_regular(grid_lon_coord):
        pp.bzx = 0
        pp.bdx = 0
        pp.lbnpt = grid_lon_coord.shape[0]
        pp.x = grid_lon_coord.points
    elif lon_coord and is_regular(lon_coord):
        pp.bzx = lon_coord.points[0] - regular_step(lon_coord)
        pp.bdx = regular_step(lon_coord)
        pp.lbnpt = len(lon_coord.points)
    elif grid_lon_coord and is_regular(grid_lon_coord):
        pp.bzx = grid_lon_coord.points[0] - regular_step(grid_lon_coord)
        pp.bdx = regular_step(grid_lon_coord)
        pp.lbnpt = len(grid_lon_coord.points)

    if lat_coord and not is_regular(lat_coord):
        pp.bzy = 0
        pp.bdy = 0
        pp.lbrow = lat_coord.shape[0]
        pp.y = lat_coord.points
    elif grid_lat_coord and not is_regular(grid_lat_coord):
        pp.bzy = 0
        pp.bdy = 0
        pp.lbrow = grid_lat_coord.shape[0]
        pp.y = grid_lat_coord.points
    elif lat_coord and is_regular(lat_coord):
        pp.bzy = lat_coord.points[0] - regular_step(lat_coord)
        pp.bdy = regular_step(lat_coord)
        pp.lbrow = len(lat_coord.points)
    elif grid_lat_coord and is_regular(grid_lat_coord):
        pp.bzy = grid_lat_coord.points[0] - regular_step(grid_lat_coord)
        pp.bdy = regular_step(grid_lat_coord)
        pp.lbrow = len(grid_lat_coord.points)

    # Check if we have a rotated coord system.
    if cube.coord_system("RotatedGeogCS") is not None:
        pp.lbcode = int(pp.lbcode) + 100

    # Check if we have a circular x-coord.
    for lon_coord in (lon_coord, grid_lon_coord):
        if lon_coord is not None:
            if lon_coord.circular:
                pp.lbhem = 0
            else:
                pp.lbhem = 3

    return pp
Example #7
0
def _grid_and_pole_rules(cube, pp):
    """
    Rules for setting the horizontal grid and pole location of the PP field.

    Args:
        cube: the cube being saved as a series of PP fields.
        pp: the current PP field having save rules applied.

    Returns:
        The PP field with updated metadata.

    """
    lon_coord = vector_coord(cube, 'longitude')
    grid_lon_coord = vector_coord(cube, 'grid_longitude')
    lat_coord = vector_coord(cube, 'latitude')
    grid_lat_coord = vector_coord(cube, 'grid_latitude')

    if lon_coord and not is_regular(lon_coord):
        pp.bzx = 0
        pp.bdx = 0
        pp.lbnpt = lon_coord.shape[0]
        pp.x = lon_coord.points
    elif grid_lon_coord and not is_regular(grid_lon_coord):
        pp.bzx = 0
        pp.bdx = 0
        pp.lbnpt = grid_lon_coord.shape[0]
        pp.x = grid_lon_coord.points
    elif lon_coord and is_regular(lon_coord):
        pp.bzx = lon_coord.points[0] - regular_step(lon_coord)
        pp.bdx = regular_step(lon_coord)
        pp.lbnpt = len(lon_coord.points)
    elif grid_lon_coord and is_regular(grid_lon_coord):
        pp.bzx = grid_lon_coord.points[0] - regular_step(grid_lon_coord)
        pp.bdx = regular_step(grid_lon_coord)
        pp.lbnpt = len(grid_lon_coord.points)

    if lat_coord and not is_regular(lat_coord):
        pp.bzy = 0
        pp.bdy = 0
        pp.lbrow = lat_coord.shape[0]
        pp.y = lat_coord.points
    elif grid_lat_coord and not is_regular(grid_lat_coord):
        pp.bzy = 0
        pp.bdy = 0
        pp.lbrow = grid_lat_coord.shape[0]
        pp.y = grid_lat_coord.points
    elif lat_coord and is_regular(lat_coord):
        pp.bzy = lat_coord.points[0] - regular_step(lat_coord)
        pp.bdy = regular_step(lat_coord)
        pp.lbrow = len(lat_coord.points)
    elif grid_lat_coord and is_regular(grid_lat_coord):
        pp.bzy = grid_lat_coord.points[0] - regular_step(grid_lat_coord)
        pp.bdy = regular_step(grid_lat_coord)
        pp.lbrow = len(grid_lat_coord.points)

    # Check if we have a rotated coord system.
    if cube.coord_system("RotatedGeogCS") is not None:
        pp.lbcode = int(pp.lbcode) + 100

    # Check if we have a circular x-coord.
    for lon_coord in (lon_coord, grid_lon_coord):
        if lon_coord is not None:
            if lon_coord.circular:
                pp.lbhem = 0
            else:
                pp.lbhem = 3

    return pp
Example #8
0
 def test_scalar_coord(self):
     # Check that a `ValueError` is captured.
     coord = DimCoord(5)
     result = is_regular(coord)
     self.assertFalse(result)
Example #9
0
 def test_coord_with_string_points(self):
     # Check that a `TypeError` is captured.
     coord = AuxCoord(['a', 'b', 'c'])
     result = is_regular(coord)
     self.assertFalse(result)
Example #10
0
 def test_coord_with_regular_step(self):
     coord = DimCoord(np.arange(5))
     result = is_regular(coord)
     self.assertTrue(result)
Example #11
0
 def test_coord_with_irregular_step(self):
     # Check that a `CoordinateNotRegularError` is captured.
     coord = AuxCoord(np.array([2, 5, 1, 4]))
     result = is_regular(coord)
     self.assertFalse(result)
Example #12
0
 def test_coord_with_string_points(self):
     # Check that a `TypeError` is captured.
     coord = AuxCoord(['a', 'b', 'c'])
     result = is_regular(coord)
     self.assertFalse(result)
Example #13
0
 def test_scalar_coord(self):
     # Check that a `ValueError` is captured.
     coord = DimCoord(5)
     result = is_regular(coord)
     self.assertFalse(result)
Example #14
0
 def test_coord_with_irregular_step(self):
     # Check that a `CoordinateNotRegularError` is captured.
     coord = AuxCoord(np.array([2, 5, 1, 4]))
     result = is_regular(coord)
     self.assertFalse(result)
Example #15
0
 def test_coord_with_regular_step(self):
     coord = DimCoord(np.arange(5))
     result = is_regular(coord)
     self.assertTrue(result)
Example #16
0
    def test_save_load(self):
        # Load sample UKV data (variable-resolution rotated grid).
        path = tests.get_data_path(("PP", "ukV1", "ukVpmslont.pp"))
        cube = load_cube(path)

        # Extract a single 2D field, for simplicity.
        self.assertEqual(cube.ndim, 3)
        self.assertEqual(cube.coord_dims("time"), (0,))
        cube = cube[0]

        # Check that it has a rotated-pole variable-spaced grid, as expected.
        x_coord = cube.coord(axis="x")
        self.assertIsInstance(x_coord.coord_system, RotatedGeogCS)
        self.assertFalse(is_regular(x_coord))

        # Write to temporary file, check that key contents are in the file,
        # then load back in.
        with self.temp_filename("ukv_sample.grib2") as temp_file_path:
            save(cube, temp_file_path)

            # Check that various aspects of the saved file are as expected.
            expect_values = (
                (0, "editionNumber", 2),
                (3, "gridDefinitionTemplateNumber", 5),
                (3, "Ni", cube.shape[-1]),
                (3, "Nj", cube.shape[-2]),
                (3, "shapeOfTheEarth", 1),
                (
                    3,
                    "scaledValueOfRadiusOfSphericalEarth",
                    int(UM_DEFAULT_EARTH_RADIUS),
                ),
                (3, "resolutionAndComponentFlags", 0),
                (3, "latitudeOfSouthernPole", -37500000),
                (3, "longitudeOfSouthernPole", 357500000),
                (3, "angleOfRotation", 0),
            )
            self.assertGribMessageContents(temp_file_path, expect_values)

            # Load the Grib file back into a new cube.
            cube_loaded_from_saved = load_cube(temp_file_path)
            # Also load data, before the temporary file gets deleted.
            cube_loaded_from_saved.data

        # The re-loaded result will not match the original in every respect:
        #  * cube attributes are discarded
        #  * horizontal coordinates are rounded to an integer representation
        #  * bounds on horizontal coords are lost
        # Thus the following "equivalence tests" are rather piecemeal..

        # Check those re-loaded properties which should match the original.
        for test_cube in (cube, cube_loaded_from_saved):
            self.assertEqual(
                test_cube.standard_name, "air_pressure_at_sea_level"
            )
            self.assertEqual(test_cube.units, "Pa")
            self.assertEqual(test_cube.shape, (928, 744))
            self.assertEqual(test_cube.cell_methods, ())

        # Check only the GRIB_PARAM attribute exists on the re-loaded cube.
        # Note: this does *not* match the original, but is as expected.
        self.assertEqual(
            cube_loaded_from_saved.attributes,
            {"GRIB_PARAM": GRIBCode("GRIB2:d000c003n001")},
        )

        # Now remaining to check: coordinates + data...

        # Check they have all the same coordinates.
        co_names = [coord.name() for coord in cube.coords()]
        co_names_reload = [
            coord.name() for coord in cube_loaded_from_saved.coords()
        ]
        self.assertEqual(sorted(co_names_reload), sorted(co_names))

        # Check all the coordinates.
        for coord_name in co_names:
            try:
                co_orig = cube.coord(coord_name)
                co_load = cube_loaded_from_saved.coord(coord_name)

                # Check shape.
                self.assertEqual(
                    co_load.shape,
                    co_orig.shape,
                    'Shape of re-loaded "{}" coord is {} '
                    "instead of {}".format(
                        coord_name, co_load.shape, co_orig.shape
                    ),
                )

                # Check coordinate points equal, within a tolerance.
                self.assertArrayAllClose(
                    co_load.points, co_orig.points, rtol=1.0e-6
                )

                # Check all coords are unbounded.
                # (NOTE: this is not so for the original X and Y coordinates,
                # but Grib does not store those bounds).
                self.assertIsNone(co_load.bounds)

            except AssertionError as err:
                self.assertTrue(
                    False,
                    'Failed on coordinate "{}" : {}'.format(
                        coord_name, str(err)
                    ),
                )

        # Check that main data array also matches.
        self.assertArrayAllClose(cube.data, cube_loaded_from_saved.data)