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))
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
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)
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
def test_scalar_coord(self): # Check that a `ValueError` is captured. coord = DimCoord(5) result = is_regular(coord) self.assertFalse(result)
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)
def test_coord_with_regular_step(self): coord = DimCoord(np.arange(5)) result = is_regular(coord) self.assertTrue(result)
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)
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)