diff --git a/src/CSET/operators/_utils.py b/src/CSET/operators/_utils.py index 8522c6ebe..2de961ee2 100644 --- a/src/CSET/operators/_utils.py +++ b/src/CSET/operators/_utils.py @@ -51,7 +51,7 @@ def get_cube_yxcoordname(cube: iris.cube.Cube) -> tuple[str, str]: Y_COORD_NAMES = ["latitude", "grid_latitude", "projection_y_coordinate", "y"] # Get a list of coordinate names for the cube - coord_names = [coord.name() for coord in cube.coords()] + coord_names = [coord.name() for coord in cube.dim_coords] # Check which x-coordinate we have, if any x_coords = [coord for coord in coord_names if coord in X_COORD_NAMES] diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 73f439ec3..dcd5f6c73 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -198,6 +198,8 @@ def callback(cube: iris.cube.Cube, field, filename: str): _lfric_normalise_callback(cube, field, filename) _lfric_time_coord_fix_callback(cube, field, filename) _longitude_fix_callback(cube, field, filename) + _fix_spatialcoord_name_callback(cube) + _fix_pressurecoord_name_callback(cube) return callback @@ -333,6 +335,44 @@ def _longitude_fix_callback(cube: iris.cube.Cube, field, filename): return cube +def _fix_spatialcoord_name_callback(cube: iris.cube.Cube): + """Check latitude and longitude coordinates name. + + This is necessary as some models define their grid as 'grid_latitude' and 'grid_longitude' + and this means that recipes will fail - particularly if the user is comparing multiple models + where the spatial coordinate names differ. + """ + import CSET.operators._utils as utils + + try: + y_name, x_name = utils.get_cube_yxcoordname(cube) + except ValueError: + # Don't modify non-spatial cubes. + return cube + + # We only want to modify instances where the coordinate system is actually + # latitude/longitude, and not touch the cube if the coordinate system is say + # meters. + if y_name in ["latitude"] and cube.coord(y_name).units == "degrees": + cube.coord(y_name).rename("grid_latitude") + if x_name in ["longitude"] and cube.coord(x_name).units == "degrees": + cube.coord(x_name).rename("grid_longitude") + + return cube + + +def _fix_pressurecoord_name_callback(cube: iris.cube.Cube): + """Rename pressure_level coordinate to pressure if it exists.""" + # We only want to modify instances where the coordinate system is actually + # latitude/longitude, and not touch the cube if the coordinate system is say + # meters. + for coord in cube.dim_coords: + if coord.name() == "pressure_level": + coord.rename("pressure") + + return cube + + def _check_input_files(input_path: Path | str, filename_pattern: str) -> Iterable[Path]: """Get an iterable of files to load, and check that they all exist. diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 9197cb919..a215c54cb 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -240,3 +240,36 @@ def test_lfric_time_coord_fix_callback_no_time(): cube = iris.cube.Cube([0, 0, 0], aux_coords_and_dims=[(length_coord, 0)]) read._lfric_time_coord_fix_callback(cube, None, None) assert len(cube.coords("time")) == 0 + + +def test_pressurecoordfix_callback(): + """Check that pressure_level is renamed to pressure if it exists.""" + cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") + cube.coord("pressure").rename("pressure_level") + read._fix_pressurecoord_name_callback(cube) + assert ( + str(cube.coords) + == ">" + ) + + +def test_spatialcoordrename_callback(): + """Check that spatial coord gets renamed if it is not grid_latitude.""" + # This cube contains 'latitude' and 'longitude' + cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") + read._fix_spatialcoord_name_callback(cube) + assert ( + str(cube.coords) + == ">" + ) + + +def test_spatialcoordnotexist_callback(): + """Check that spatial coord returns cube if cube does not contain spatial coordinates.""" + cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") + cube = cube[:, :, 0, 0] # Remove spatial dimcoords + read._fix_spatialcoord_name_callback(cube) + assert ( + str(cube.coords) + == ">" + ) diff --git a/tests/operators/test_utils.py b/tests/operators/test_utils.py index 7270a3962..3269282d9 100644 --- a/tests/operators/test_utils.py +++ b/tests/operators/test_utils.py @@ -21,14 +21,14 @@ def test_missing_coord_get_cube_yxcoordname_x(regrid_rectilinear_cube): """Missing X coordinate raises error.""" - regrid_rectilinear_cube.remove_coord("longitude") + regrid_rectilinear_cube.remove_coord("grid_longitude") with pytest.raises(ValueError): operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube) def test_missing_coord_get_cube_yxcoordname_y(regrid_rectilinear_cube): """Missing Y coordinate raises error.""" - regrid_rectilinear_cube.remove_coord("longitude") + regrid_rectilinear_cube.remove_coord("grid_longitude") with pytest.raises(ValueError): operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube) @@ -36,8 +36,8 @@ def test_missing_coord_get_cube_yxcoordname_y(regrid_rectilinear_cube): def test_get_cube_yxcoordname(regrid_rectilinear_cube): """Check that function returns tuple containing horizontal dimension names.""" assert (operator_utils.get_cube_yxcoordname(regrid_rectilinear_cube)) == ( - "latitude", - "longitude", + "grid_latitude", + "grid_longitude", )