diff --git a/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json b/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json new file mode 100644 index 000000000..d675b1857 --- /dev/null +++ b/.tmp-srcCSEToperators_colorbar_definition.json-sorted.json @@ -0,0 +1,934 @@ +{ + "air_potential_temperature": { + "cmap": "RdYlBu_r", + "max": 350, + "min": 280, + "ymax": "auto", + "ymin": "auto" + }, + "air_pressure_at_mean_sea_level": { + "cmap": "coolwarm", + "max": 104000, + "min": 94000, + "ymax": "auto", + "ymin": "auto" + }, + "air_pressure_at_mean_sea_level_difference": { + "cmap": "bwr", + "max": 1000, + "min": -1000 + }, + "atmosphere_boundary_layer_thickness": { + "cmap": "terrain", + "max": 5000, + "min": 0, + "ymax": 2000, + "ymin": 0 + }, + "atmosphere_boundary_layer_thickness_difference": { + "cmap": "PRGn", + "max": 1000.0, + "min": -1000.0 + }, + "atmosphere_mass_content_of_cloud_ice": { + "cmap": "Blues", + "max": 5, + "min": 0.0 + }, + "atmosphere_mass_content_of_cloud_ice_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "atmosphere_mass_content_of_cloud_liquid_water": { + "cmap": "Blues", + "max": 5, + "min": 0.0, + "ymax": 1.0, + "ymin": 0.0 + }, + "atmosphere_mass_content_of_cloud_liquid_water_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "atmosphere_mass_content_of_water_vapor": { + "cmap": "Blues", + "max": 50, + "min": 0 + }, + "atmosphere_mass_content_of_water_vapor_difference": { + "cmap": "bwr", + "max": 50, + "min": -50 + }, + "atmosphere_mass_of_air_per_unit_area": { + "cmap": "Greys", + "max": 10560, + "min": 9200, + "ymax": 10200, + "ymin": 10000 + }, + "atmosphere_mass_of_air_per_unit_area_difference": { + "cmap": "bwr", + "max": 500, + "min": -500 + }, + "ceilometer_filtered_combined_cloud_amount_maximum_random_overlap": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "ceilometer_filtered_combined_cloud_amount_maximum_random_overlap_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "cloud_base_altitude": { + "cmap": "terrain", + "max": 25, + "min": 0 + }, + "cloud_base_altitude_difference": { + "cmap": "PRGn", + "max": 20.0, + "min": -20.0 + }, + "cloud_ice_mixing_ratio": { + "cmap": "GnBu", + "max": 1, + "min": 0.0001 + }, + "cloud_liquid_water_mixing_ratio": { + "camp": "GnBu", + "max": 1, + "min": 0.0001 + }, + "combined_cloud_amount": { + "cmap": "Greys_r", + "max": 1, + "min": 0 + }, + "combined_cloud_amount_maximum_random_overlap": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "combined_cloud_amount_maximum_random_overlap_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "dew_point_temperature_at_screen_level": { + "cmap": "RdYlBu_r", + "max": 315, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "dew_point_temperature_at_screen_level_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "eastward_wind_at_10m": { + "cmap": "RdBu_r", + "max": 25.0, + "min": -25.0, + "ymax": 8.0, + "ymin": -8.0 + }, + "eastward_wind_at_10m_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "eastward_wind_northward_wind_magnitude": { + "cmap": "YlOrRd", + "max": 25.0, + "min": 0.0 + }, + "exner_pressure_at_cell_interfaces": { + "cmap": "coolwarm", + "max": 1.1, + "min": 0.5 + }, + "fog_fraction_at_screen_level": { + "cmap": "viridis", + "max": 1, + "min": 0, + "ymax": 0.5, + "ymin": 0.0 + }, + "fog_fraction_at_screen_level_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "geopotential_height_at_pressure_levels": { + "cmap": "bwr", + "max": 15000, + "min": -250, + "pressure_levels": { + "1000": { + "max": 250, + "min": -250, + "ymax": "auto", + "ymin": "auto" + }, + "250": { + "max": 10800, + "min": 10200, + "ymax": "auto", + "ymin": "auto" + }, + "500": { + "max": 6000, + "min": 5000, + "ymax": "auto", + "ymin": "auto" + }, + "850": { + "max": 1700, + "min": 1200, + "ymax": "auto", + "ymin": "auto" + } + }, + "ymax": "auto", + "ymin": "auto" + }, + "geopotential_height_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 60, + "min": -60 + }, + "grid_surface_snow_amount": { + "cmap": "cool", + "max": 1, + "min": 0, + "ymax": 0.1, + "ymin": 0.0 + }, + "grid_surface_snow_amount_difference": { + "cmap": "BrBG", + "max": 1, + "min": -1 + }, + "grid_surface_temperature": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "grid_surface_temperature_difference": { + "cmap": "bwr", + "max": -10, + "min": 10 + }, + "grid_surface_upward_latent_heat_flux": { + "cmap": "RdBu_r", + "max": 500, + "min": -500, + "ymax": 250, + "ymin": -50 + }, + "grid_surface_upward_latent_heat_flux_difference": { + "cmap": "PuOr_r", + "max": 80, + "min": -80 + }, + "grid_surface_upward_sensible_heat_flux": { + "cmap": "bwr", + "max": 250.0, + "min": -250.0, + "ymax": 250.0, + "ymin": -50.0 + }, + "grid_surface_upward_sensible_heat_flux_difference": { + "cmap": "PuOr", + "max": 80.0, + "min": -80.0 + }, + "high_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "high_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "low_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "low_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "mass_content_of_water_in_soil_layer": { + "cmap": "ocean_r", + "max": 500, + "min": -10 + }, + "maximum_combined_cloud_amount_below_111m_asl": { + "cmap": "viridis", + "max": 1.0, + "min": 0.0 + }, + "maximum_combined_cloud_amount_below_111m_asl_difference": { + "cmap": "PRGn", + "max": 1.0, + "min": -1.0 + }, + "medium_type_cloud_area_fraction": { + "cmap": "viridis", + "max": 1, + "min": 0 + }, + "medium_type_cloud_area_fraction_difference": { + "cmap": "PRGn", + "max": 1, + "min": -1 + }, + "meridional_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 40, + "min": -40, + "pressure_levels": { + "100": { + "max": 100, + "min": -100, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "1000": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "200": { + "max": 80, + "min": -80, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "250": { + "max": 50, + "min": -50, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "300": { + "max": 55, + "min": -55, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "500": { + "max": 40, + "min": -40, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "700": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "850": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + }, + "950": { + "max": 35, + "min": -35, + "xmax": 20, + "xmin": -20, + "ymax": 10, + "ymin": -10 + } + }, + "xmax": 20, + "xmin": -20 + }, + "meridional_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 20, + "min": -20 + }, + "northward_wind_at_10m": { + "cmap": "RdBu_r", + "max": 25.0, + "min": -25.0, + "ymax": 8.0, + "ymin": -8.0 + }, + "northward_wind_at_10m_difference": { + "cmap": "bwr", + "max": 5.0, + "min": -5.0 + }, + "northward_wind_at_cell_centres": { + "cmap": "RdBu_r", + "max": 35.0, + "min": -35.0 + }, + "number_of_lightning_flashes_in_column": { + "cmap": "YlOrRd", + "max": 5, + "min": 1, + "ymax": 0.5, + "ymin": 0 + }, + "number_of_lightning_flashes_in_column_difference": { + "cmap": "RdGy_r", + "max": 5, + "min": -5 + }, + "potential_vorticity_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 1.8e-05, + "min": -1.8e-05, + "ymax": 5e-06, + "ymin": -5e-06 + }, + "potential_vorticity_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 1e-05, + "min": -1e-05 + }, + "radar_reflectivity_at_1km_above_the_surface": { + "cmap": "cubehelix_r", + "max": 70.0, + "min": -35.0 + }, + "radar_reflectivity_at_1km_above_the_surface_difference": { + "cmap": "BrBG", + "max": 100.0, + "min": -100.0 + }, + "relative_humidity_at_screen_level": { + "cmap": "Blues", + "max": 110, + "min": 0 + }, + "relative_humidity_at_screen_level_difference": { + "cmap": "BrBG", + "max": 30, + "min": -30 + }, + "relative_humidity_wrt_ice_at_pressure_levels": { + "cmap": "Blues", + "max": 110, + "min": 0 + }, + "relative_humidity_wrt_ice_at_pressure_levels_difference": { + "cmap": "BrBG", + "max": 50, + "min": -50 + }, + "soil_temperature": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "soil_temperature_difference": { + "cmap": "bwr", + "max": 0.5, + "min": -0.5 + }, + "structural_similarity": { + "cmap": "afmhot", + "max": 1.0, + "min": -1.0 + }, + "surface_downward_longwave_flux": { + "cmap": "Greys_r", + "max": 500, + "min": 0 + }, + "surface_downward_longwave_flux_difference": { + "cmap": "PuOr", + "max": 500, + "min": -500 + }, + "surface_downward_shortwave_flux": { + "cmap": "Greys", + "max": 1400, + "min": 0, + "ymax": 800, + "ymin": 0 + }, + "surface_downward_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 1400, + "min": -1400 + }, + "surface_microphysical_rainfall_amount": { + "cmap": "cividis", + "max": 256, + "min": 0, + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_rainfall_amount_difference": { + "cmap": "BrBG", + "max": 16.0, + "min": -16.0 + }, + "surface_microphysical_rainfall_rate": { + "cmap": "cividis", + "levels": [ + 0, + 0.125, + 0.25, + 0.5, + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_rainfall_rate_difference": { + "cmap": "BrBG", + "max": 32.0, + "min": -32.0 + }, + "surface_microphysical_snowfall_amount": { + "cmap": "cividis", + "max": 256, + "min": 0, + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_snowfall_amount_difference": { + "cmap": "BrBG", + "max": 4.0, + "min": -4.0 + }, + "surface_microphysical_snowfall_rate": { + "cmap": "cool", + "levels": [ + 0, + 0.125, + 0.25, + 0.5, + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256 + ], + "ymax": 1.0, + "ymin": 0.0 + }, + "surface_microphysical_snowfall_rate_difference": { + "cmap": "BrBG", + "max": 8.0, + "min": -8.0 + }, + "surface_net_longwave_flux_radiative_timestep": { + "cmap": "bwr", + "max": 250, + "min": -250, + "ymax": 0, + "ymin": -150 + }, + "surface_net_longwave_flux_radiative_timestep_difference": { + "cmap": "PuOr", + "max": 80, + "min": -80 + }, + "surface_net_shortwave_flux": { + "cmap": "bwr", + "max": 1400, + "min": -1400, + "ymax": 850, + "ymin": 0 + }, + "surface_net_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 1400, + "min": -1400 + }, + "temperature_at_pressure_levels": { + "cmap": "RdYlBu_r", + "max": 320, + "min": 240, + "pressure_levels": { + "100": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "1000": { + "max": 320, + "min": 240, + "ymax": "auto", + "ymin": "auto" + }, + "200": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "250": { + "max": 240, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "300": { + "max": 250, + "min": 200, + "ymax": "auto", + "ymin": "auto" + }, + "500": { + "max": 280, + "min": 220, + "ymax": "auto", + "ymin": "auto" + }, + "700": { + "max": 300, + "min": 240, + "ymax": "auto", + "ymin": "auto" + }, + "850": { + "max": 310, + "min": 250, + "ymax": "auto", + "ymin": "auto" + }, + "950": { + "max": 310, + "min": 250, + "ymax": "auto", + "ymin": "auto" + } + }, + "ymax": "auto", + "ymin": "auto" + }, + "temperature_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "temperature_at_screen_level": { + "cmap": "RdYlBu_r", + "max": 323, + "min": 263, + "ymax": "auto", + "ymin": "auto" + }, + "temperature_at_screen_level_difference": { + "cmap": "bwr", + "max": 10, + "min": -10 + }, + "toa_direct_shortwave_flux": { + "cmap": "Greys", + "max": 1400.0, + "min": 0.0 + }, + "toa_direct_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 2.0, + "min": -2.0 + }, + "toa_upward_longwave_flux_radiative_timestep": { + "cmap": "Greys", + "max": 350.0, + "min": 0.0, + "ymax": 350.0, + "ymin": 150.0 + }, + "toa_upward_longwave_flux_radiative_timestep_difference": { + "cmap": "PuOr", + "max": 100.0, + "min": -100.0 + }, + "toa_upward_shortwave_flux": { + "cmap": "Greys", + "max": 500.0, + "min": 0.0 + }, + "toa_upward_shortwave_flux_difference": { + "cmap": "PuOr", + "max": 100, + "min": -100 + }, + "turbulent_mixing_height": { + "cmap": "terrain", + "max": 5000, + "min": 0, + "ymax": 2000, + "ymin": 0 + }, + "turbulent_mixing_height_difference": { + "cmap": "PRGn", + "max": 5000, + "min": -5000 + }, + "vapour_specific_humidity_at_pressure_levels_for_climate_averaging": { + "cmap": "Blues", + "max": 0.01, + "min": 0, + "pressure_levels": { + "100": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "1000": { + "max": 0.01, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "200": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "250": { + "max": 5e-05, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "300": { + "max": 0.0001, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "500": { + "max": 0.003, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "700": { + "max": 0.003, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "850": { + "max": 0.005, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + }, + "950": { + "max": 0.01, + "min": 0, + "xmax": 0.015, + "xmin": 0.0 + } + }, + "xmax": 0.015, + "xmin": 0.0, + "ymax": "auto", + "ymin": "auto" + }, + "vapour_specific_humidity_at_pressure_levels_for_climate_averaging_difference": { + "cmap": "BrBG", + "max": 0.001, + "min": -0.001 + }, + "vertical_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 1, + "min": -1, + "xmax": 0.08, + "xmin": -0.08, + "ymax": 0.1, + "ymin": -0.1 + }, + "vertical_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 1, + "min": -1 + }, + "visibility_in_air": { + "cmap": "viridis", + "max": 30000.0, + "min": 0.0 + }, + "visibility_in_air_difference": { + "cmap": "PRGn", + "max": 5000.0, + "min": -5000.0 + }, + "wet_bulb_potential_temperature_at_pressure_levels": { + "cmap": "RdYlBu_r", + "max": 340, + "min": 260, + "ymax": "auto", + "ymin": "auto" + }, + "wet_bulb_potential_temperature_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "wind_speed_at_10m": { + "cmap": "RdBu_r", + "max": 25, + "min": 0 + }, + "wind_speed_at_10m_difference": { + "cmap": "bwr", + "max": 5, + "min": -5 + }, + "y_wind": { + "cmap": "RdBu_r", + "max": -30, + "min": 30 + }, + "zonal_wind_at_pressure_levels": { + "cmap": "RdBu_r", + "max": 50, + "min": -50, + "pressure_levels": { + "100": { + "max": 100, + "min": -100, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "1000": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "200": { + "max": 80, + "min": -80, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "250": { + "max": 70, + "min": -70, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "300": { + "max": 55, + "min": -55, + "xmax": 25, + "xmin": -25, + "ymax": 20, + "ymin": -20 + }, + "500": { + "max": 45, + "min": -45, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "700": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "850": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + }, + "950": { + "max": 35, + "min": -35, + "xmax": 25, + "xmin": -25, + "ymax": 10, + "ymin": -10 + } + }, + "xmax": 25, + "xmin": -25 + }, + "zonal_wind_at_pressure_levels_difference": { + "cmap": "bwr", + "max": 20, + "min": -20 + } +} diff --git a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf index caea8d373..eb0661b4d 100644 --- a/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf +++ b/src/CSET/cset_workflow/meta/diagnostics/rose-meta.conf @@ -432,6 +432,18 @@ type=real,real compulsory=true sort-key=1pressure8b +[template variables=PLEVEL_TRANSECT_AGGREGATION] +ns=Diagnostics/Pressure +description=Aggregate transects for each time. + Select all options required. + Option1: Aggregate by lead time. + Option2: Aggregate by hour of day. + Option3: Aggregate by validity time. + Option4: All cases aggregated to single profile. +type=python_boolean,python_boolean,python_boolean,python_boolean +compulsory=true +sort-key=1pressure8c + [template variables=SPECTRUM_PLEVEL_FIELD] ns=Diagnostics/Pressure description=Create spectrum of specified pressure level fields. diff --git a/src/CSET/cset_workflow/rose-suite.conf.example b/src/CSET/cset_workflow/rose-suite.conf.example index 662de16bd..b5577b4ad 100644 --- a/src/CSET/cset_workflow/rose-suite.conf.example +++ b/src/CSET/cset_workflow/rose-suite.conf.example @@ -99,6 +99,7 @@ MODERATE_RAIN_PRESENCE_SPATIAL_PLOT=False !!MULTI_OVERLAY_MASK_VALUE=0.0 !!ONE_TO_ONE=False PLACEHOLDER_OBS=False +!!PLEVEL_TRANSECT_AGGREGATION=True,True,True,True !!PLEVEL_TRANSECT_FINISHCOORDS= !!PLEVEL_TRANSECT_STARTCOORDS= PLOTTING_PROJECTION="" diff --git a/src/CSET/loaders/transects.py b/src/CSET/loaders/transects.py index a69636f71..55f425040 100644 --- a/src/CSET/loaders/transects.py +++ b/src/CSET/loaders/transects.py @@ -47,6 +47,31 @@ def load(conf: Config): aggregation=False, ) + # Create a list of case aggregation types. + AGGREGATION_TYPES = ["lead_time", "hour_of_day", "validity_time", "all"] + + # Transect aggregation + for model, atype, field in itertools.product( + models, AGGREGATION_TYPES, conf.PRESSURE_LEVEL_FIELDS + ): + if conf.PLEVEL_TRANSECT_AGGREGATION[AGGREGATION_TYPES.index(atype)]: + yield RawRecipe( + recipe=f"transect_case_aggregation_{atype}.yaml", + variables={ + "VARNAME": field, + "VERTICAL_COORDINATE": "pressure", + "MODEL_NAME": model["name"], + "START_COORDS": conf.PLEVEL_TRANSECT_STARTCOORDS, + "FINISH_COORDS": conf.PLEVEL_TRANSECT_FINISHCOORDS, + "SUBAREA_TYPE": conf.SUBAREA_TYPE if conf.SELECT_SUBAREA else None, + "SUBAREA_EXTENT": conf.SUBAREA_EXTENT + if conf.SELECT_SUBAREA + else None, + }, + model_ids=model["id"], + aggregation=True, + ) + # Model level fields if conf.EXTRACT_MLEVEL_TRANSECT: for model, field in itertools.product( diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index 72b49ff1d..edac16633 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -583,6 +583,7 @@ def _plot_and_save_spatial_plot( # Plot the field. if method == "contourf": # Filled contour plot of the field. + logging.info("testing!") plot = iplt.contourf(cube, cmap=cmap, levels=levels, norm=norm) elif method == "pcolormesh": try: @@ -653,15 +654,80 @@ def _plot_and_save_spatial_plot( fontsize=16, ) + # Inset code + import cartopy.feature as cfeature + from cartopy.mpl.geoaxes import GeoAxes + from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + axins = inset_axes( + axes, + width="20%", + height="20%", + loc="upper right", + axes_class=GeoAxes, + axes_kwargs=dict(map_projection=ccrs.PlateCarree()), + ) + + axins.coastlines(resolution="50m") + axins.add_feature(cfeature.BORDERS, linewidth=0.3) + + SLat = float(cube.attributes["transect_coords"].split("_")[0]) + SLon = float(cube.attributes["transect_coords"].split("_")[1]) + ELat = float(cube.attributes["transect_coords"].split("_")[2]) + ELon = float(cube.attributes["transect_coords"].split("_")[3]) + + # Plot points (note: lon, lat order for Cartopy) + axins.plot(SLon, SLat, marker="x", color="green", transform=ccrs.PlateCarree()) + axins.plot(ELon, ELat, marker="x", color="red", transform=ccrs.PlateCarree()) + + # Draw line between them + axins.plot( + [SLon, ELon], [SLat, ELat], color="black", transform=ccrs.PlateCarree() + ) + + lon_min, lon_max = sorted([SLon, ELon]) + lat_min, lat_max = sorted([SLat, ELat]) + + # Midpoints + lon_mid = (lon_min + lon_max) / 2 + lat_mid = (lat_min + lat_max) / 2 + + # Maximum half-range + half_range = max(lon_max - lon_min, lat_max - lat_min) / 2 + if half_range == 0: # points identical → provide small default + half_range = 1 + + # Set square extent + axins.set_extent( + [ + lon_mid - half_range, + lon_mid + half_range, + lat_mid - half_range, + lat_mid + half_range, + ], + crs=ccrs.PlateCarree(), + ) + + # Ensure square aspect + axins.set_aspect("equal") + else: # Add title. axes.set_title(title, fontsize=16) + # Adjust padding if spatial plot or transect + if is_transect(cube): + yinfopad = -0.1 + ycbarpad = 0.1 + else: + yinfopad = -0.05 + ycbarpad = 0.042 + # Add watermark with min/max/mean. Currently not user togglable. # In the bbox dictionary, fc and ec are hex colour codes for grey shade. axes.annotate( f"Min: {np.min(cube.data):.3g} Max: {np.max(cube.data):.3g} Mean: {np.mean(cube.data):.3g}", - xy=(1, -0.05), + xy=(1, yinfopad), xycoords="axes fraction", xytext=(-5, 5), textcoords="offset points", @@ -687,8 +753,9 @@ def _plot_and_save_spatial_plot( # Add main colour bar. cbar = fig.colorbar( - plot, orientation="horizontal", location="bottom", pad=0.042, shrink=0.7 + plot, orientation="horizontal", location="bottom", pad=ycbarpad, shrink=0.7 ) + cbar.set_label(label=f"{cube.name()} ({cube.units})", size=14) # add ticks and tick_labels for every levels if less than 20 levels exist if levels is not None and len(levels) < 20: diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 6c30af1e7..989f235b1 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -374,6 +374,7 @@ def _loading_callback(cube: iris.cube.Cube, field, filename: str) -> iris.cube.C _proleptic_gregorian_fix(cube) _lfric_time_callback(cube) _lfric_forecast_period_standard_name_callback(cube) + _normalise_ML_varname(cube) return cube @@ -430,6 +431,9 @@ def _lfric_normalise_callback(cube: iris.cube.Cube, field, filename): cube.attributes.pop("timeStamp", None) cube.attributes.pop("uuid", None) cube.attributes.pop("name", None) + cube.attributes.pop("source", None) + cube.attributes.pop("analysis_source", None) + cube.attributes.pop("history", None) # Sort STASH code list. stash_list = cube.attributes.get("um_stash_source") @@ -968,7 +972,20 @@ def _lfric_forecast_period_standard_name_callback(cube: iris.cube.Cube): """Add forecast_period standard name if missing.""" try: coord = cube.coord("forecast_period") + if coord.units != "hours": + cube.coord("forecast_period").convert_units("hours") if not coord.standard_name: coord.standard_name = "forecast_period" except iris.exceptions.CoordinateNotFoundError: pass + + +def _normalise_ML_varname(cube: iris.cube.Cube): + """Fix variable names in ML models to standard names.""" + if cube.coords("pressure"): + if cube.name() == "x_wind": + cube.long_name = "zonal_wind_at_pressure_levels" + if cube.name() == "y_wind": + cube.long_name = "meridional_wind_at_pressure_levels" + if cube.name() == "air_temperature": + cube.long_name = "temperature_at_pressure_levels" diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml new file mode 100644 index 000000000..c4aef3ffd --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_all.yaml @@ -0,0 +1,42 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation over all cases. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse + coordinate: time + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml new file mode 100644 index 000000000..7acc8a9b4 --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_hour_of_day.yaml @@ -0,0 +1,41 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by hour of day. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse_by_hour_of_day + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: hour + + - operator: write.write_cube_to_nc + overwrite: True diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml new file mode 100644 index 000000000..68861a5e9 --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_lead_time.yaml @@ -0,0 +1,42 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by lead time. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse + coordinate: "forecast_reference_time" + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: forecast_period + + - operator: write.write_cube_to_nc + overwrite: True diff --git a/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml b/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml new file mode 100644 index 000000000..45b21352d --- /dev/null +++ b/src/CSET/recipes/level_fields/transect_case_aggregation_validity_time.yaml @@ -0,0 +1,41 @@ +category: Transect +title: $MODEL_NAME Transect of $VARNAME Aggregation by validity time. +description: | + Extracts a $VERTICAL_COORDINATE transect for $VARNAME between two points and + plots it. + + Start coordinate: `$START_COORDS` + End coordinate: `$FINISH_COORDS` + +steps: + - operator: read.read_cubes + file_paths: $INPUT_PATHS + constraint: + operator: constraints.combine_constraints + cell_method_constraint: + operator: constraints.generate_cell_methods_constraint + cell_methods: [] + var_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + level_constraint: + operator: constraints.generate_level_constraint + coordinate: $VERTICAL_COORDINATE + levels: '*' + subarea_type: $SUBAREA_TYPE + subarea_extent: $SUBAREA_EXTENT + + - operator: aggregate.ensure_aggregatable_across_cases + + - operator: collapse.collapse_by_validity_time + method: MEAN + + - operator: transect.calc_transect + startcoords: $START_COORDS + endcoords: $FINISH_COORDS + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True