From f9b9429029d8fd6e2124f0c79e4b5bf0da86c2f3 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 13:47:49 +0000 Subject: [PATCH 01/17] new callbacks --- src/CSET/operators/read.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 73f439ec3..969c904d9 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"]: + cube.coord(y_name).rename("grid_latitude") + if x_name in ["longitude"]: + 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. From 8625379b86bfe4850aec20a75572fc2bf24f8431 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 13:56:23 +0000 Subject: [PATCH 02/17] additional unit check --- src/CSET/operators/read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 969c904d9..dcd5f6c73 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -353,9 +353,9 @@ def _fix_spatialcoord_name_callback(cube: iris.cube.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"]: + if y_name in ["latitude"] and cube.coord(y_name).units == "degrees": cube.coord(y_name).rename("grid_latitude") - if x_name in ["longitude"]: + if x_name in ["longitude"] and cube.coord(x_name).units == "degrees": cube.coord(x_name).rename("grid_longitude") return cube From 9191a9cf983c161747f24669b69267beeee8b573 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 14:05:42 +0000 Subject: [PATCH 03/17] fix some affected tests --- tests/operators/test_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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", ) From e6e981500360a31eaa3ab436422470165f6181c4 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 14:25:35 +0000 Subject: [PATCH 04/17] add new tests --- tests/operators/test_read.py | 18 ++++++++++++++++++ tests/test_data/test_nospatialcoords.nc | Bin 0 -> 13233 bytes tests/test_data/test_renameplev.nc | Bin 0 -> 13920 bytes 3 files changed, 18 insertions(+) create mode 100644 tests/test_data/test_nospatialcoords.nc create mode 100644 tests/test_data/test_renameplev.nc diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 9197cb919..e15b5ca2e 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -240,3 +240,21 @@ 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 = read.read_cube("tests/test_data/test_renameplev.nc") + assert ( + str(cube.coords) + == ">" + ) + + +def test_spatialcoordrename_callback(): + """Check that spatial coord rename returns unchanged cube if no spatial coords.""" + cube = read.read_cube("tests/test_data/test_renameplev.nc") + assert ( + str(cube.coords) + == ">" + ) diff --git a/tests/test_data/test_nospatialcoords.nc b/tests/test_data/test_nospatialcoords.nc new file mode 100644 index 0000000000000000000000000000000000000000..ef3693bd161f29ff378c77f98139be9cb99cafae GIT binary patch literal 13233 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 J*)4&|{{Td%^tS*2 literal 0 HcmV?d00001 From e15c3afa1124ef83b084a7edf723b9393fce9cdf Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 14:31:54 +0000 Subject: [PATCH 05/17] fix tests --- tests/operators/test_read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index e15b5ca2e..2bc1fae56 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -247,13 +247,13 @@ def test_pressurecoordfix_callback(): cube = read.read_cube("tests/test_data/test_renameplev.nc") assert ( str(cube.coords) - == ">" + == ">" ) def test_spatialcoordrename_callback(): """Check that spatial coord rename returns unchanged cube if no spatial coords.""" - cube = read.read_cube("tests/test_data/test_renameplev.nc") + cube = read.read_cube("tests/test_data/test_nospatialcoords.nc") assert ( str(cube.coords) == ">" From 5a4c0b85d0407dac157bea3d9e96c6f93d702da2 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 14:33:36 +0000 Subject: [PATCH 06/17] fix tests --- tests/operators/test_read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 2bc1fae56..aedb8816d 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -247,7 +247,7 @@ def test_pressurecoordfix_callback(): cube = read.read_cube("tests/test_data/test_renameplev.nc") assert ( str(cube.coords) - == ">" + == ">" ) @@ -256,5 +256,5 @@ def test_spatialcoordrename_callback(): cube = read.read_cube("tests/test_data/test_nospatialcoords.nc") assert ( str(cube.coords) - == ">" + == ">" ) From 647f12aeb3d9950744b393bf2b8e145cf3062439 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 14:35:03 +0000 Subject: [PATCH 07/17] fix tests again --- tests/operators/test_read.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index aedb8816d..908bc80a5 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -247,7 +247,7 @@ def test_pressurecoordfix_callback(): cube = read.read_cube("tests/test_data/test_renameplev.nc") assert ( str(cube.coords) - == ">" + == ">" ) From 47fbe249471399dc0118d98d51ecc3ae7d8e6090 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 16:23:06 +0000 Subject: [PATCH 08/17] extra test and directly invoke callback --- tests/operators/test_read.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 908bc80a5..0efcaddb8 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -244,17 +244,33 @@ def test_lfric_time_coord_fix_callback_no_time(): def test_pressurecoordfix_callback(): """Check that pressure_level is renamed to pressure if it exists.""" - cube = read.read_cube("tests/test_data/test_renameplev.nc") + 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 rename returns unchanged cube if no spatial coords.""" - cube = read.read_cube("tests/test_data/test_nospatialcoords.nc") + """Check that spatial coord gets renamed if it is not grid_latitude.""" + cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") + cube.coord("grid_latitude").rename("latitude") + cube.coord("grid_longitude").rename("longitude") + 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) + == ">" ) From 197522635d10821d450cf0ac1708ec6189e7ece4 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 16:23:34 +0000 Subject: [PATCH 09/17] remove surplus test data --- tests/test_data/test_nospatialcoords.nc | Bin 13233 -> 0 bytes tests/test_data/test_renameplev.nc | Bin 13920 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/test_data/test_nospatialcoords.nc delete mode 100644 tests/test_data/test_renameplev.nc diff --git a/tests/test_data/test_nospatialcoords.nc b/tests/test_data/test_nospatialcoords.nc deleted file mode 100644 index ef3693bd161f29ff378c77f98139be9cb99cafae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13233 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 J*)4&|{{Td%^tS*2 From 8c289d02d1155bb7d2a91eff18bd5052da7ad369 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 16:31:26 +0000 Subject: [PATCH 10/17] fix tests --- tests/operators/test_read.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 0efcaddb8..a215c54cb 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -255,13 +255,12 @@ def test_pressurecoordfix_callback(): 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") - cube.coord("grid_latitude").rename("latitude") - cube.coord("grid_longitude").rename("longitude") read._fix_spatialcoord_name_callback(cube) assert ( str(cube.coords) - == ">" + == ">" ) From 45b28f324f3f8a63469397a1781abcc9a5a09101 Mon Sep 17 00:00:00 2001 From: James Warner Date: Thu, 12 Dec 2024 16:38:20 +0000 Subject: [PATCH 11/17] fix bug where scalar coordinates could be returned --- src/CSET/operators/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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] From 8be0651e59ae7111739d35a763c907ed7624cf09 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 16 Dec 2024 09:52:28 +0000 Subject: [PATCH 12/17] revert coord check to only operate on dims --- src/CSET/operators/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CSET/operators/_utils.py b/src/CSET/operators/_utils.py index 2de961ee2..8522c6ebe 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.dim_coords] + coord_names = [coord.name() for coord in cube.coords()] # Check which x-coordinate we have, if any x_coords = [coord for coord in coord_names if coord in X_COORD_NAMES] From 6f2ba92258d13c32247305a56e2a00c311a02d91 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 16 Dec 2024 11:29:52 +0000 Subject: [PATCH 13/17] new spspatial test function --- src/CSET/operators/_utils.py | 36 +++++++++++++++++++++++++++++++++++ src/CSET/operators/read.py | 8 +++++--- tests/operators/test_utils.py | 14 ++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/CSET/operators/_utils.py b/src/CSET/operators/_utils.py index 8522c6ebe..d755af298 100644 --- a/src/CSET/operators/_utils.py +++ b/src/CSET/operators/_utils.py @@ -66,6 +66,42 @@ def get_cube_yxcoordname(cube: iris.cube.Cube) -> tuple[str, str]: return (y_coords[0], x_coords[0]) +def is_spatialdim(cube: iris.cube.Cube) -> bool: + """Determine whether a cube is has two spatial dimension coordinates. + + If cube has both spatial dims, it will contain two unique coordinates + that explain space (latitude and longitude). The coordinates have to + be iterable/contain usable dimension data, as cubes may contain these + coordinates as scaler dimensions after being collapsed. + + Arguments + --------- + cube: iris.cube.Cube + An iris cube which will be checked to see if it contains coordinate + names that match a pre-defined list of acceptable coordinate names. + + Returns + ------- + bool + If true, then the cube has a spatial projection and thus can be plotted + as a map. + """ + # Acceptable horizontal coordinate names. + X_COORD_NAMES = ["longitude", "grid_longitude", "projection_x_coordinate", "x"] + 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.dim_coords] + x_coords = [coord for coord in coord_names if coord in X_COORD_NAMES] + y_coords = [coord for coord in coord_names if coord in Y_COORD_NAMES] + + # If there is one coordinate for both x and y direction return True. + if len(x_coords) == 1 and len(y_coords) == 1: + return True + else: + return False + + def is_transect(cube: iris.cube.Cube) -> bool: """Determine whether a cube is a transect. diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index dcd5f6c73..1065ae087 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -344,12 +344,14 @@ def _fix_spatialcoord_name_callback(cube: iris.cube.Cube): """ import CSET.operators._utils as utils - try: - y_name, x_name = utils.get_cube_yxcoordname(cube) - except ValueError: + # Check if cube is spatial. + if not utils.is_spatialdim(cube): # Don't modify non-spatial cubes. return cube + # Get spatial coords. + y_name, x_name = utils.get_cube_yxcoordname(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. diff --git a/tests/operators/test_utils.py b/tests/operators/test_utils.py index 3269282d9..7f81f9ca6 100644 --- a/tests/operators/test_utils.py +++ b/tests/operators/test_utils.py @@ -14,6 +14,7 @@ """Tests for common operator functionality across CSET.""" +import iris import pytest import CSET.operators._utils as operator_utils @@ -58,3 +59,16 @@ def test_is_transect_correctcoord(transect_source_cube): # Retain only time and latitude coordinate, so it passes the first spatial coord test. transect_source_cube_slice = transect_source_cube[:, :, :, 0] assert operator_utils.is_transect(transect_source_cube_slice) + + +def test_is_spatialdim_false(): + """Check that is spatial test returns false 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 + assert not operator_utils.is_spatialdim(cube) + + +def test_is_spatialdim_true(): + """Check that is spatial test returns true if cube contains spatial coordinates.""" + cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") + assert operator_utils.is_spatialdim(cube) From 470b1418519392b9d4feb4ad99bea56e862e8d15 Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 16 Dec 2024 11:59:37 +0000 Subject: [PATCH 14/17] move to repr --- tests/operators/test_read.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index a215c54cb..b03029f9f 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -248,8 +248,8 @@ def test_pressurecoordfix_callback(): cube.coord("pressure").rename("pressure_level") read._fix_pressurecoord_name_callback(cube) assert ( - str(cube.coords) - == ">" + repr(cube.coords()) + == "[, , , , , ]" ) @@ -259,8 +259,8 @@ def test_spatialcoordrename_callback(): cube = iris.load_cube("tests/test_data/transect_test_umpl.nc") read._fix_spatialcoord_name_callback(cube) assert ( - str(cube.coords) - == ">" + repr(cube.coords()) + == "[, , , , , ]" ) @@ -270,6 +270,6 @@ def test_spatialcoordnotexist_callback(): cube = cube[:, :, 0, 0] # Remove spatial dimcoords read._fix_spatialcoord_name_callback(cube) assert ( - str(cube.coords) - == ">" + repr(cube.coords()) + == "[, , , , , ]" ) From e4825a430c127ef6543f1e35c84e0cd9c3e94f7f Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 16 Dec 2024 12:05:30 +0000 Subject: [PATCH 15/17] add more info on callback --- src/CSET/operators/read.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 1065ae087..47136f386 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -360,11 +360,14 @@ def _fix_spatialcoord_name_callback(cube: iris.cube.Cube): 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.""" + """Rename pressure_level coordinate to pressure if it exists. + + This problem was raised because the AIFS model data from ECMWF + defines the pressure coordinate with the name 'pressure_level' rather + than compliant CF coordinate names + """ # 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. @@ -372,8 +375,6 @@ def _fix_pressurecoord_name_callback(cube: iris.cube.Cube): 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. From 81bdbb5867ffaae8e9bfd7900681028b0fde5bcc Mon Sep 17 00:00:00 2001 From: James Warner Date: Mon, 16 Dec 2024 12:09:11 +0000 Subject: [PATCH 16/17] other fixes --- src/CSET/operators/read.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 47136f386..d4f6c4be4 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -352,12 +352,17 @@ def _fix_spatialcoord_name_callback(cube: iris.cube.Cube): # Get spatial coords. y_name, x_name = utils.get_cube_yxcoordname(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": + if y_name in ["latitude"] and cube.coord(y_name).units in [ + "degrees", + "degrees_north", + "degrees_south", + ]: cube.coord(y_name).rename("grid_latitude") - if x_name in ["longitude"] and cube.coord(x_name).units == "degrees": + if x_name in ["longitude"] and cube.coord(x_name).units in [ + "degrees", + "degrees_west", + "degrees_east", + ]: cube.coord(x_name).rename("grid_longitude") From 3d2f76c5a38387ef0b0d0609560791fdc33135f0 Mon Sep 17 00:00:00 2001 From: James W <62252918+jwarner8@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:12:55 +0000 Subject: [PATCH 17/17] Update src/CSET/operators/_utils.py Co-authored-by: David Flack <77390156+daflack@users.noreply.github.com> --- src/CSET/operators/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CSET/operators/_utils.py b/src/CSET/operators/_utils.py index d755af298..239b6b77f 100644 --- a/src/CSET/operators/_utils.py +++ b/src/CSET/operators/_utils.py @@ -72,7 +72,7 @@ def is_spatialdim(cube: iris.cube.Cube) -> bool: If cube has both spatial dims, it will contain two unique coordinates that explain space (latitude and longitude). The coordinates have to be iterable/contain usable dimension data, as cubes may contain these - coordinates as scaler dimensions after being collapsed. + coordinates as scalar dimensions after being collapsed. Arguments ---------