diff --git a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf index c68b08be1..174de8de7 100644 --- a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf +++ b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf @@ -1081,7 +1081,6 @@ ns=Diagnostics/Derived/Cyclones sort-key=sec-c8 title=Cyclones - ################################### # Extra-tropical cyclones [Diagnostics/ETCs] @@ -1089,7 +1088,6 @@ ns=Diagnostics/Derived/Cyclones/ETCs sort-key=sec-c8a title=Extra-tropical cyclones - ################################### # Tropical Cyclones [Diagnostics/TCs] @@ -1190,3 +1188,35 @@ help= Include condition and threshold required, it will loop over all conditions type=python_boolean compulsory=true sort-key=tpb4 + +####################################################################### +# Observations +[Diagnostics/Observations] +ns=Diagnostics/Observations +sort-key=sec-d11 +title=Observations + +################################### +# Cardington Observations +[Diagnostics/Cardington] +ns=Diagnostics/Observations/Cardington +sort-key=sec-d12 +title=Cardington Observations + +# Air temperature timeseries +[template variables=CARDINGTON_AIR_TEMPERATURE_SINGLE_POINT_TIME_SERIES] +ns=Diagnostics/Observations/Cardington +title=Cardington air temperature timeseries +description=Create Cardington diagnostic variable timeseries +type=python_boolean +compulsory=true +sort-key=0card1 + +# Relative humidity timeseries +[template variables=CARDINGTON_RELATIVE_HUMIDITY_SINGLE_POINT_TIME_SERIES] +ns=Diagnostics/Observations/Cardington +title=Cardington relative humidity timeseries +description=Create Cardington diagnostic variable timeseries +type=python_boolean +compulsory=true +sort-key=0card2 diff --git a/src/CSET/cset_workflow/rose-suite.conf.example b/src/CSET/cset_workflow/rose-suite.conf.example index c33efb054..b457f17a1 100644 --- a/src/CSET/cset_workflow/rose-suite.conf.example +++ b/src/CSET/cset_workflow/rose-suite.conf.example @@ -4,6 +4,8 @@ ANALYSIS_LENGTH="" AOA_DIAG=False !!AOA_PLEV=[] BASIC_QQ_PLOT=False +CARDINGTON_AIR_TEMPERATURE_SINGLE_POINT_TIME_SERIES=True +CARDINGTON_RELATIVE_HUMIDITY_SINGLE_POINT_TIME_SERIES=True COLORBAR_FILE="" !!CONDA_METPLUS_VENV_LOCATION="" CONDA_PATH="" diff --git a/src/CSET/loaders/timeseries.py b/src/CSET/loaders/timeseries.py index 1a44d6e10..688308407 100644 --- a/src/CSET/loaders/timeseries.py +++ b/src/CSET/loaders/timeseries.py @@ -161,6 +161,32 @@ def load(conf: Config): aggregation=False, ) + # Cardington air temperature time series. + if conf.CARDINGTON_AIR_TEMPERATURE_SINGLE_POINT_TIME_SERIES: + base_model = models[0] + for model in models[1:]: + yield RawRecipe( + recipe="cardington_air_temperature_single_point_time_series.yaml", + variables={ + "MODEL_NAME": model["name"], + }, + model_ids=[base_model["id"], model["id"]], + aggregation=False, + ) + + # Cardington relative humidity time series. + if conf.CARDINGTON_RELATIVE_HUMIDITY_SINGLE_POINT_TIME_SERIES: + base_model = models[0] + for model in models[1:]: + yield RawRecipe( + recipe="cardington_relative_humidity_single_point_time_series.yaml", + variables={ + "MODEL_NAME": model["name"], + }, + model_ids=[base_model["id"], model["id"]], + aggregation=False, + ) + # Create a list of case aggregation types. AGGREGATION_TYPES = ["lead_time", "hour_of_day", "validity_time", "all"] diff --git a/src/CSET/operators/regrid.py b/src/CSET/operators/regrid.py index c24cac73d..c763b6dfc 100644 --- a/src/CSET/operators/regrid.py +++ b/src/CSET/operators/regrid.py @@ -17,6 +17,7 @@ import warnings import iris +import iris.coord_systems import iris.cube import numpy as np @@ -199,7 +200,7 @@ def regrid_onto_xyspacing( def regrid_to_single_point( - cube: iris.cube.Cube, + cubes: iris.cube.Cube | iris.cube.CubeList, lat_pt: float, lon_pt: float, latlon_in_type: str = "rotated", @@ -215,9 +216,9 @@ def regrid_to_single_point( Parameters ---------- - cube: Cube - An iris cube of the data to regrid. As a minimum, it needs to be 2D with - latitude, longitude coordinates. + cubes: Cube | CubeList + An iris cube or CubeList of the data to regrid. As a minimum, it needs + to be 2D with latitude, longitude coordinates. lon_pt: float Selected value of longitude: this should be in the range -180 degrees to 180 degrees. @@ -236,9 +237,9 @@ def regrid_to_single_point( Returns ------- - cube_rgd: Cube - An iris cube of the data at the specified point (this may have time - and/or height dimensions). + regridded_cubes: Cube | CubeList + An iris cube or CubeList of the data at the specified point (this may + have time and/or height dimensions). Raises ------ @@ -259,75 +260,87 @@ def regrid_to_single_point( is potentially unreliable. """ - # Get x and y coordinate names. - y_coord, x_coord = get_cube_yxcoordname(cube) - - # List of supported grids - check if it is compatible - # NOTE: The "RotatedGeogCS" option below seems to be required for rotated grids -- - # this may need to be added in other places in these Operators. - supported_grids = (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) - if not isinstance(cube.coord(x_coord).coord_system, supported_grids): - raise NotImplementedError( - f"Does not currently support {cube.coord(x_coord).coord_system} regrid method" + # To store regridded cubes. + regridded_cubes = iris.cube.CubeList() + + # Iterate over all cubes and regrid. + for cube in iter_maybe(cubes): + # Get x and y coordinate names. + y_coord, x_coord = get_cube_yxcoordname(cube) + + # List of supported grids - check if it is compatible + # NOTE: The "RotatedGeogCS" option below seems to be required for rotated grids -- + # this may need to be added in other places in these Operators. + supported_grids = (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) + if not isinstance(cube.coord(x_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(x_coord).coord_system} regrid method" + ) + if not isinstance(cube.coord(y_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(y_coord).coord_system} regrid method" + ) + + # Transform input coordinates onto rotated grid if requested + if latlon_in_type == "realworld": + lon_tr, lat_tr = transform_lat_long_points(lon_pt, lat_pt, cube) + elif latlon_in_type == "rotated": + lon_tr, lat_tr = lon_pt, lat_pt + + # Get axis + lat, lon = cube.coord(y_coord), cube.coord(x_coord) + + # Get bounds + lat_min, lon_min = lat.points.min(), lon.points.min() + lat_max, lon_max = lat.points.max(), lon.points.max() + + # Get boundaries of frame to avoid selecting gridpoint close to domain edge + lat_min_bound, lon_min_bound = ( + lat.points[boundary_margin - 1], + lon.points[boundary_margin - 1], ) - if not isinstance(cube.coord(y_coord).coord_system, supported_grids): - raise NotImplementedError( - f"Does not currently support {cube.coord(y_coord).coord_system} regrid method" + lat_max_bound, lon_max_bound = ( + lat.points[-boundary_margin], + lon.points[-boundary_margin], ) - # Transform input coordinates onto rotated grid if requested - if latlon_in_type == "realworld": - lon_tr, lat_tr = transform_lat_long_points(lon_pt, lat_pt, cube) - elif latlon_in_type == "rotated": - lon_tr, lat_tr = lon_pt, lat_pt - - # Get axis - lat, lon = cube.coord(y_coord), cube.coord(x_coord) - - # Get bounds - lat_min, lon_min = lat.points.min(), lon.points.min() - lat_max, lon_max = lat.points.max(), lon.points.max() - - # Get boundaries of frame to avoid selecting gridpoint close to domain edge - lat_min_bound, lon_min_bound = ( - lat.points[boundary_margin - 1], - lon.points[boundary_margin - 1], - ) - lat_max_bound, lon_max_bound = ( - lat.points[-boundary_margin], - lon.points[-boundary_margin], - ) - - # Check to see if selected point is outside the domain - if (lat_tr < lat_min) or (lat_tr > lat_max): - raise ValueError("Selected point is outside the domain.") - else: - if (lon_tr < lon_min) or (lon_tr > lon_max): - if (lon_tr + 360.0 >= lon_min) and (lon_tr + 360.0 <= lon_max): - lon_tr += 360.0 - elif (lon_tr - 360.0 >= lon_min) and (lon_tr - 360.0 <= lon_max): - lon_tr -= 360.0 - else: - raise ValueError("Selected point is outside the domain.") - - # Check to see if selected point is near the domain boundaries - if ( - (lat_tr < lat_min_bound) - or (lat_tr > lat_max_bound) - or (lon_tr < lon_min_bound) - or (lon_tr > lon_max_bound) - ): - warnings.warn( - f"Selected point is within {boundary_margin} gridlengths of the domain edge, data may be unreliable.", - category=BoundaryWarning, - stacklevel=2, - ) + # Check to see if selected point is outside the domain + if (lat_tr < lat_min) or (lat_tr > lat_max): + raise ValueError("Selected point is outside the domain.") + else: + if (lon_tr < lon_min) or (lon_tr > lon_max): + if (lon_tr + 360.0 >= lon_min) and (lon_tr + 360.0 <= lon_max): + lon_tr += 360.0 + elif (lon_tr - 360.0 >= lon_min) and (lon_tr - 360.0 <= lon_max): + lon_tr -= 360.0 + else: + raise ValueError("Selected point is outside the domain.") + + # Check to see if selected point is near the domain boundaries + if ( + (lat_tr < lat_min_bound) + or (lat_tr > lat_max_bound) + or (lon_tr < lon_min_bound) + or (lon_tr > lon_max_bound) + ): + warnings.warn( + f"Selected point is within {boundary_margin} gridlengths of the domain edge, data may be unreliable.", + category=BoundaryWarning, + stacklevel=2, + ) - regrid_method = getattr(iris.analysis, method, None) - if not callable(regrid_method): - raise NotImplementedError(f"Does not currently support {method} regrid method") - cube_rgd = cube.interpolate(((lat, lat_tr), (lon, lon_tr)), regrid_method()) - return cube_rgd + regrid_method = getattr(iris.analysis, method, None) + if not callable(regrid_method): + raise NotImplementedError( + f"Does not currently support {method} regrid method" + ) + cube_rgd = cube.interpolate(((lat, lat_tr), (lon, lon_tr)), regrid_method()) + regridded_cubes.append(cube_rgd) + # Preserve returning a cube if only a cube has been supplied to regrid. + if len(regridded_cubes) == 1: + return regridded_cubes[0] + else: + return regridded_cubes def transform_lat_long_points(lon, lat, cube): diff --git a/src/CSET/operators/regrid_old.py b/src/CSET/operators/regrid_old.py new file mode 100644 index 000000000..c24cac73d --- /dev/null +++ b/src/CSET/operators/regrid_old.py @@ -0,0 +1,366 @@ +# © Crown copyright, Met Office (2022-2024) and CSET contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Operators to regrid cubes.""" + +import warnings + +import iris +import iris.cube +import numpy as np + +from CSET._common import iter_maybe +from CSET.operators._utils import get_cube_yxcoordname + + +class BoundaryWarning(UserWarning): + """Selected gridpoint is close to the domain edge. + + In many cases gridpoints near the domain boundary contain non-physical + values, so caution is advised when interpreting them. + """ + + +def regrid_onto_cube( + toregrid: iris.cube.Cube | iris.cube.CubeList, + target: iris.cube.Cube, + method: str, + **kwargs, +) -> iris.cube.Cube | iris.cube.CubeList: + """Regrid a cube or CubeList, projecting onto a target cube. + + All cubes must have at least 2 spatial (map) dimensions. + + Arguments + ---------- + toregrid: iris.cube | iris.cube.CubeList + An iris Cube of data to regrid, or multiple cubes to regrid in a + CubeList. A minimum requirement is that the cube(s) need to be 2D with a + latitude, longitude coordinates. + target: Cube + An iris cube of the data to regrid onto. It needs to be 2D with a + latitude, longitude coordinate. + method: str + Method used to regrid onto, etc. Linear will use iris.analysis.Linear() + + Returns + ------- + iris.cube | iris.cube.CubeList + An iris cube of the data that has been regridded, or a CubeList of the + cubes that have been regridded in the same order they were passed in + toregrid. + + Raises + ------ + ValueError + If a unique x/y coordinate cannot be found + NotImplementedError + If the cubes grid, or the method for regridding, is not yet supported. + + Notes + ----- + Currently rectlinear grids (uniform) are supported. + """ + # To store regridded cubes. + regridded_cubes = iris.cube.CubeList() + + # Iterate over all cubes and regrid. + for cube in iter_maybe(toregrid): + # Get y,x coord names + y_coord, x_coord = get_cube_yxcoordname(cube) + + # List of supported grids - check if it is compatible + supported_grids = (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) + if not isinstance(cube.coord(x_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(x_coord).coord_system} coordinate system" + ) + if not isinstance(cube.coord(y_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(y_coord).coord_system} coordinate system" + ) + + regrid_method = getattr(iris.analysis, method, None) + if callable(regrid_method): + regridded_cubes.append(cube.regrid(target, regrid_method())) + else: + raise NotImplementedError( + f"Does not currently support {method} regrid method" + ) + + # Preserve returning a cube if only a cube has been supplied to regrid. + if len(regridded_cubes) == 1: + return regridded_cubes[0] + else: + return regridded_cubes + + +def regrid_onto_xyspacing( + toregrid: iris.cube.Cube | iris.cube.CubeList, + xspacing: float, + yspacing: float, + method: str, + **kwargs, +) -> iris.cube.Cube | iris.cube.CubeList: + """Regrid cube or cubelist onto a set x,y spacing. + + Regrid cube(s) using specified x,y spacing, which is performed linearly. + + Parameters + ---------- + toregrid: iris.cube | iris.cube.CubeList + An iris cube of the data to regrid, or multiple cubes to regrid in a + cubelist. A minimum requirement is that the cube(s) need to be 2D with a + latitude, longitude coordinates. + xspacing: float + Spacing of points in longitude direction (could be degrees, meters etc.) + yspacing: float + Spacing of points in latitude direction (could be degrees, meters etc.) + method: str + Method used to regrid onto, etc. Linear will use iris.analysis.Linear() + + Returns + ------- + iris.cube | iris.cube.CubeList + An iris cube of the data that has been regridded, or a cubelist of the + cubes that have been regridded in the same order they were passed in + toregrid. + + Raises + ------ + ValueError + If a unique x/y coordinate cannot be found + NotImplementedError + If the cubes grid, or the method for regridding, is not yet supported. + + Notes + ----- + Currently rectlinear grids (uniform) are supported. + + """ + # To store regridded cubes. + regridded_cubes = iris.cube.CubeList() + + # Iterate over all cubes and regrid. + for cube in iter_maybe(toregrid): + # Get x,y coord names + y_coord, x_coord = get_cube_yxcoordname(cube) + + # List of supported grids - check if it is compatible + supported_grids = (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) + if not isinstance(cube.coord(x_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(x_coord).coord_system} regrid method" + ) + if not isinstance(cube.coord(y_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(y_coord).coord_system} regrid method" + ) + + # Get axis + lat, lon = cube.coord(y_coord), cube.coord(x_coord) + + # Get bounds + lat_min, lon_min = lat.points.min(), lon.points.min() + lat_max, lon_max = lat.points.max(), lon.points.max() + + # Generate new mesh + latout = np.arange(lat_min, lat_max, yspacing) + lonout = np.arange(lon_min, lon_max, xspacing) + + regrid_method = getattr(iris.analysis, method, None) + if callable(regrid_method): + regridded_cubes.append( + cube.interpolate( + [(y_coord, latout), (x_coord, lonout)], regrid_method() + ) + ) + else: + raise NotImplementedError( + f"Does not currently support {method} regrid method" + ) + + # Preserve returning a cube if only a cube has been supplied to regrid. + if len(regridded_cubes) == 1: + return regridded_cubes[0] + else: + return regridded_cubes + + +def regrid_to_single_point( + cube: iris.cube.Cube, + lat_pt: float, + lon_pt: float, + latlon_in_type: str = "rotated", + method: str = "Nearest", + boundary_margin: int = 8, + **kwargs, +) -> iris.cube.Cube: + """Select data at a single point by longitude and latitude. + + Selection of model grid point is performed by a regrid function, either + selecting the nearest gridpoint to the selected longitude and latitude + values or using linear interpolation across the surrounding points. + + Parameters + ---------- + cube: Cube + An iris cube of the data to regrid. As a minimum, it needs to be 2D with + latitude, longitude coordinates. + lon_pt: float + Selected value of longitude: this should be in the range -180 degrees to + 180 degrees. + lat_pt: float + Selected value of latitude: this should be in the range -90 degrees to + 90 degrees. + method: str + Method used to determine the values at the selected longitude and + latitude. The recommended approach is to use iris.analysis.Nearest(), + which selects the nearest gridpoint. An alternative is + iris.analysis.Linear(), which obtains the values at the selected + longitude and latitude by linear interpolation. + boundary_margin: int, optional + Number of grid points from the domain boundary considered "unreliable". + Defaults to 8. + + Returns + ------- + cube_rgd: Cube + An iris cube of the data at the specified point (this may have time + and/or height dimensions). + + Raises + ------ + ValueError + If a unique x/y coordinate cannot be found; also if, for selecting a + single gridpoint, the chosen longitude and latitude point is outside the + domain. + NotImplementedError + If the cubes grid, or the method for regridding, is not yet supported. + + Notes + ----- + The acceptable coordinate names for X and Y coordinates are currently + described in X_COORD_NAMES and Y_COORD_NAMES. These cover commonly used + coordinate types, though a user can append new ones. Currently rectilinear + grids (uniform) are supported. Warnings are raised if the selected gridpoint + is within boundary_margin grid lengths of the domain boundary as data here + is potentially unreliable. + + """ + # Get x and y coordinate names. + y_coord, x_coord = get_cube_yxcoordname(cube) + + # List of supported grids - check if it is compatible + # NOTE: The "RotatedGeogCS" option below seems to be required for rotated grids -- + # this may need to be added in other places in these Operators. + supported_grids = (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) + if not isinstance(cube.coord(x_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(x_coord).coord_system} regrid method" + ) + if not isinstance(cube.coord(y_coord).coord_system, supported_grids): + raise NotImplementedError( + f"Does not currently support {cube.coord(y_coord).coord_system} regrid method" + ) + + # Transform input coordinates onto rotated grid if requested + if latlon_in_type == "realworld": + lon_tr, lat_tr = transform_lat_long_points(lon_pt, lat_pt, cube) + elif latlon_in_type == "rotated": + lon_tr, lat_tr = lon_pt, lat_pt + + # Get axis + lat, lon = cube.coord(y_coord), cube.coord(x_coord) + + # Get bounds + lat_min, lon_min = lat.points.min(), lon.points.min() + lat_max, lon_max = lat.points.max(), lon.points.max() + + # Get boundaries of frame to avoid selecting gridpoint close to domain edge + lat_min_bound, lon_min_bound = ( + lat.points[boundary_margin - 1], + lon.points[boundary_margin - 1], + ) + lat_max_bound, lon_max_bound = ( + lat.points[-boundary_margin], + lon.points[-boundary_margin], + ) + + # Check to see if selected point is outside the domain + if (lat_tr < lat_min) or (lat_tr > lat_max): + raise ValueError("Selected point is outside the domain.") + else: + if (lon_tr < lon_min) or (lon_tr > lon_max): + if (lon_tr + 360.0 >= lon_min) and (lon_tr + 360.0 <= lon_max): + lon_tr += 360.0 + elif (lon_tr - 360.0 >= lon_min) and (lon_tr - 360.0 <= lon_max): + lon_tr -= 360.0 + else: + raise ValueError("Selected point is outside the domain.") + + # Check to see if selected point is near the domain boundaries + if ( + (lat_tr < lat_min_bound) + or (lat_tr > lat_max_bound) + or (lon_tr < lon_min_bound) + or (lon_tr > lon_max_bound) + ): + warnings.warn( + f"Selected point is within {boundary_margin} gridlengths of the domain edge, data may be unreliable.", + category=BoundaryWarning, + stacklevel=2, + ) + + regrid_method = getattr(iris.analysis, method, None) + if not callable(regrid_method): + raise NotImplementedError(f"Does not currently support {method} regrid method") + cube_rgd = cube.interpolate(((lat, lat_tr), (lon, lon_tr)), regrid_method()) + return cube_rgd + + +def transform_lat_long_points(lon, lat, cube): + """Transform a selected point in longitude and latitude. + + Transform the coordinates of a point from the real world + grid to the corresponding point on the rotated grid of a cube. + + Parameters + ---------- + cube: Cube + An iris cube of data defining the rotated grid to be used in + the longitude-latitude transformation. + lon: float + Selected value of longitude: this should be in the range -180 degrees to + 180 degrees. + lat: float + Selected value of latitude: this should be in the range -90 degrees to + 90 degrees. + + Returns + ------- + lon_rot, lat_rot: float + Coordinates of the selected point on the rotated grid specified within + the selected cube. + + """ + import cartopy.crs as ccrs + + rot_pole = cube.coord_system().as_cartopy_crs() + true_grid = ccrs.Geodetic() + rot_coords = rot_pole.transform_point(lon, lat, true_grid) + lon_rot = rot_coords[0] + lat_rot = rot_coords[1] + + return lon_rot, lat_rot diff --git a/src/CSET/recipes/surface_fields/cardington/cardington_air_temperature_single_point_time_series.yaml b/src/CSET/recipes/surface_fields/cardington/cardington_air_temperature_single_point_time_series.yaml new file mode 100644 index 000000000..73bbb6bb3 --- /dev/null +++ b/src/CSET/recipes/surface_fields/cardington/cardington_air_temperature_single_point_time_series.yaml @@ -0,0 +1,35 @@ +category: Time series of air temperature at Cardington single point +title: Time series of 'air_temperature' at lat_pt:52.10438, lon_pt:-0.42286 specific to Cardington gridpoint. +description: Plots a time series of the air temperature at a selected Cardington gridpoint. + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + model_names: ['Cardington','$MODEL_NAME'] + constraint: + operator: constraints.combine_constraints + varname_constraint: + operator: constraints.generate_var_constraint + varname: 'air_temperature' + cell_methods_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + varname: 'air_temperature' + pressure_level_constraint: + operator: constraints.generate_level_constraint + levels: [] + coordinate: "pressure" + + - operator: regrid.regrid_to_single_point + lat_pt: 52.10438 + lon_pt: -0.42286 + latlon_in_type: "realworld" + method: "Nearest" + boundary_margin: 0 + + # Make a single NetCDF with all the data inside it. + - operator: write.write_cube_to_nc + overwrite: True + + # Plot the data. + - operator: plot.plot_line_series diff --git a/src/CSET/recipes/surface_fields/cardington/cardington_relative_humidity_single_point_time_series.yaml b/src/CSET/recipes/surface_fields/cardington/cardington_relative_humidity_single_point_time_series.yaml new file mode 100644 index 000000000..a9effc100 --- /dev/null +++ b/src/CSET/recipes/surface_fields/cardington/cardington_relative_humidity_single_point_time_series.yaml @@ -0,0 +1,35 @@ +category: Time series of relative humidity at Cardington single point +title: Time series of 'relative humidity' at lat_pt:52.10438, lon_pt:-0.42286 specific to Cardington gridpoint. +description: Plots a time series of the relative humidity at a selected Cardington gridpoint. + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + model_names: ['Cardington','$MODEL_NAME'] + constraint: + operator: constraints.combine_constraints + varname_constraint: + operator: constraints.generate_var_constraint + varname: 'relative_humidity' + cell_methods_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + varname: 'relative_humidity' + pressure_level_constraint: + operator: constraints.generate_level_constraint + levels: [] + coordinate: "pressure" + + - operator: regrid.regrid_to_single_point + lat_pt: 52.10438 + lon_pt: -0.42286 + latlon_in_type: "realworld" + method: "Nearest" + boundary_margin: 0 + + # Make a single NetCDF with all the data inside it. + - operator: write.write_cube_to_nc + overwrite: True + + # Plot the data. + - operator: plot.plot_line_series