diff --git a/mip_convert/mip_convert/new_variable.py b/mip_convert/mip_convert/new_variable.py index 32d116603..e6fa2682a 100644 --- a/mip_convert/mip_convert/new_variable.py +++ b/mip_convert/mip_convert/new_variable.py @@ -337,7 +337,7 @@ def ordered_coords(self): return self._ordered_coords - def process(self): + def process(self, saver=None, cell_measures_config=None): """Process the data. The units of the data of the |MIP requested variable| are the @@ -367,6 +367,19 @@ def process(self): self._update_time_units() if hasattr(self.model_to_mip_mapping, 'valid_min'): self._apply_valid_min_correction() + if saver is not None and cell_measures_config is not None: + self.apply_cell_measures(saver, cell_measures_config) + + def apply_cell_measures(self, saver, cell_measures_config): + """Apply cell measures to the CMOR variable if not already applied. Parameters + ---------- + saver: + The CMOR outputter for this variable. + cell_measures_config: tuple + Arguments forwarded to cmor_wrapper.apply_cell_measures, + in the form (mip_era, mip_table_dir, realm, variable, frequency, region). + """ + saver.apply_cell_measures(self, *cell_measures_config) def _remove_alevhalf_bounds(self): """ diff --git a/mip_convert/mip_convert/requested_variables.py b/mip_convert/mip_convert/requested_variables.py index 31c07e6c4..96889d997 100644 --- a/mip_convert/mip_convert/requested_variables.py +++ b/mip_convert/mip_convert/requested_variables.py @@ -128,8 +128,17 @@ def produce_mip_requested_variable( # Process the data by performing the appropriate 'model to MIP mapping', then save the 'MIP output variable' # to an 'output netCDF file'. period = user_config.slicing.get(stream_id, 'year') + + cell_measures_config = ( + user_config.mip_era, + user_config.inpath, + mip_table.id, + variable_name, + frequency, + user_config.global_attributes.get('region', '')) + for time_slice in variable.slices_over(period): - time_slice.process() + time_slice.process(saver, cell_measures_config) logger.debug('MIP output variable contains: {}'.format(time_slice.info)) save(time_slice, saver) diff --git a/mip_convert/mip_convert/save/cmor/cmor_outputter.py b/mip_convert/mip_convert/save/cmor/cmor_outputter.py index b9007455c..312ecd009 100644 --- a/mip_convert/mip_convert/save/cmor/cmor_outputter.py +++ b/mip_convert/mip_convert/save/cmor/cmor_outputter.py @@ -38,6 +38,7 @@ from mip_convert import mip_parser import mip_convert.common from mip_convert.common import RelativePathChecker +from mip_convert.save import create_cmor_variable from mip_convert.save.mip_config import MipTableFactory @@ -301,6 +302,17 @@ def _optional_kwargs(self, variable): kwargs['comment'] = self._name_space.namespace_stamp(variable.comment) return kwargs + def apply_cell_measures(self, mip_output_variable, *cell_measures_config): + """Apply cell measures to this CMOR variable. + + Ensures the CMOR variable ID exists (creating it if needed), then + sets the cell_measures attribute via the CMOR wrapper. + """ + if self.varid is None: + cmor_variable = create_cmor_variable(mip_output_variable) + self._getVarId(cmor_variable) + self.cmor.apply_cell_measures(*cell_measures_config, self.varid) + def _close_file(self): self.cmor.close(self.varid, preserve=True) diff --git a/mip_convert/mip_convert/save/cmor/cmor_wrapper.py b/mip_convert/mip_convert/save/cmor/cmor_wrapper.py index 56c5a6463..5640f8ded 100644 --- a/mip_convert/mip_convert/save/cmor/cmor_wrapper.py +++ b/mip_convert/mip_convert/save/cmor/cmor_wrapper.py @@ -1,6 +1,8 @@ # (C) British Crown Copyright 2009-2025, Met Office. # Please see LICENSE.md for license details. from collections import OrderedDict +import os +import json import cmor @@ -115,3 +117,38 @@ def zfactor(self, *args, **kwargs): def set_frequency(self, frequency, **kwargs): self._debug_on_args('frequency', [frequency], kwargs) cmor.cmor.set_cur_dataset_attribute('frequency', frequency) + + def apply_cell_measures(self, mip_era, mip_table_dir, realm, variable, frequency, region, variable_id): + """ + Set the cell_measures attribute on a CMOR variable using a lookup in + ``{mip_era}_cell_measures.json`` from the MIP tables directory. Returns + silently if the file does not exist. + + The lookup key has the form ``{realm}.{root_label}.{branding}.{frequency}.{region}`` + (variable name split on ``_``), so entries in the JSON must follow the + convention used for CMIP7. + """ + self._debug_on_args('apply_cell_measures', [mip_era, mip_table_dir, realm, variable, frequency, region, variable_id], {}) + + cell_measures_file = os.path.join(mip_table_dir, f'{mip_era}_cell_measures.json') + + if os.path.exists(cell_measures_file): + with open(cell_measures_file) as fh: + cell_measures = json.load(fh) + if 'cell_measures' not in cell_measures: + self.logger.debug(f'"cell_measures" key not found in {cell_measures_file}') + return + cell_measures = cell_measures['cell_measures'] + + root_label, branding = variable.split('_') + key = f'{realm}.{root_label}.{branding}.{frequency}.{region}' + if key in cell_measures: + retval = cmor.cmor.set_variable_attribute( + variable_id, + 'cell_measures', + 'c', + cell_measures[key]) + if retval != 0: + self.logger.debug('cell_measures assignment failed. Check cmor log file for details') + else: + self.logger.debug(f'Cell_measures file "{cell_measures_file}" not found. Continuing')