|
| 1 | +"""ESMValTool CMORizer for CAMS data. |
| 2 | +
|
| 3 | +Tier |
| 4 | + Tier 3 |
| 5 | +
|
| 6 | +Source |
| 7 | + https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-greenhouse-gas-inversion?tab=form |
| 8 | +
|
| 9 | +Last access |
| 10 | + 20240909 |
| 11 | +
|
| 12 | +Download and processing instructions |
| 13 | + Select carbon dioxide, surface flux, surface air sample, monthly mean |
| 14 | + and download the year you require |
| 15 | +
|
| 16 | +""" |
| 17 | + |
| 18 | +import logging |
| 19 | +import os |
| 20 | +import warnings |
| 21 | +from datetime import datetime |
| 22 | + |
| 23 | +import dask.array as da |
| 24 | +import iris |
| 25 | +from cf_units import Unit |
| 26 | + |
| 27 | +from esmvaltool.cmorizers.data.utilities import ( |
| 28 | + fix_coords, |
| 29 | + fix_var_metadata, |
| 30 | + save_variable, |
| 31 | + set_global_atts, |
| 32 | + set_units, |
| 33 | +) |
| 34 | + |
| 35 | +logger = logging.getLogger(__name__) |
| 36 | + |
| 37 | + |
| 38 | +def _get_time_coord(year, month): |
| 39 | + """Get time coordinate.""" |
| 40 | + point = datetime(year=year, month=month, day=15) |
| 41 | + bound_low = datetime(year=year, month=month, day=1) |
| 42 | + if month == 12: |
| 43 | + month_bound_up = 1 |
| 44 | + year_bound_up = year + 1 |
| 45 | + else: |
| 46 | + month_bound_up = month + 1 |
| 47 | + year_bound_up = year |
| 48 | + bound_up = datetime(year=year_bound_up, month=month_bound_up, day=1) |
| 49 | + time_units = Unit("days since 1950-01-01 00:00:00", calendar="standard") |
| 50 | + time_coord = iris.coords.DimCoord( |
| 51 | + time_units.date2num(point), |
| 52 | + bounds=time_units.date2num([bound_low, bound_up]), |
| 53 | + var_name="time", |
| 54 | + standard_name="time", |
| 55 | + long_name="time", |
| 56 | + units=time_units, |
| 57 | + ) |
| 58 | + return time_coord |
| 59 | + |
| 60 | + |
| 61 | +def add_timeunits(cube, filename): |
| 62 | + """Add timestamp to cube.""" |
| 63 | + tmp = str.split(filename, "_") |
| 64 | + time_coord = _get_time_coord(int(tmp[-1][:4]), int(tmp[-1][4:6])) |
| 65 | + cube.add_aux_coord(time_coord) |
| 66 | + |
| 67 | + |
| 68 | +def _calculate_flux(cube, filename, area_type): |
| 69 | + """Calculate flux (dividing by land/sea area) and mask land/sea.""" |
| 70 | + # Get land/sea area fraction |
| 71 | + with warnings.catch_warnings(): |
| 72 | + warnings.filterwarnings( |
| 73 | + "ignore", |
| 74 | + message="Ignoring netCDF variable '.*?' invalid units '.*?'", |
| 75 | + category=UserWarning, |
| 76 | + module="iris", |
| 77 | + ) |
| 78 | + lsf_cube = iris.load_cube(filename, "lsf") |
| 79 | + lsf = lsf_cube.core_data() |
| 80 | + |
| 81 | + # Mask |
| 82 | + if area_type == "land": |
| 83 | + mask = lsf == 0.0 |
| 84 | + elif area_type == "ocean": |
| 85 | + mask = lsf > 0 |
| 86 | + cube.data = da.ma.masked_array(cube.core_data(), mask=mask) |
| 87 | + |
| 88 | + # Calculate flux (sign change since input data and CMOR use different |
| 89 | + # conventions) |
| 90 | + cube.data = -cube.core_data() |
| 91 | + |
| 92 | + cube.attributes["positive"] = "down" |
| 93 | + |
| 94 | + return cube |
| 95 | + |
| 96 | + |
| 97 | +def fix_units(cube): |
| 98 | + """Fix units from invalid units through import.""" |
| 99 | + set_units(cube, "kg m-2 month-1") |
| 100 | + cube.convert_units("kg m-2 s-1") |
| 101 | + del cube.attributes["invalid_units"] |
| 102 | + |
| 103 | + |
| 104 | +def extract_variable(short_name, var, filename): |
| 105 | + """Extract variable.""" |
| 106 | + with warnings.catch_warnings(): |
| 107 | + warnings.filterwarnings( |
| 108 | + "ignore", |
| 109 | + message="Ignoring netCDF variable '.*?' invalid units '.*?'", |
| 110 | + category=UserWarning, |
| 111 | + module="iris", |
| 112 | + ) |
| 113 | + cube = iris.load_cube(filename, var["varname"]) |
| 114 | + if short_name == "sftof": |
| 115 | + cube.data = 100.0 * (1.0 - cube.core_data()) |
| 116 | + cube.units = "%" |
| 117 | + elif short_name == "sftlf": |
| 118 | + cube.data = 100.0 * cube.core_data() |
| 119 | + cube.units = "%" |
| 120 | + elif short_name == "nbp": |
| 121 | + _calculate_flux(cube, filename, "land") |
| 122 | + add_timeunits(cube, filename) |
| 123 | + fix_units(cube) |
| 124 | + elif short_name == "fgco2": |
| 125 | + _calculate_flux(cube, filename, "ocean") |
| 126 | + add_timeunits(cube, filename) |
| 127 | + fix_units(cube) |
| 128 | + return cube |
| 129 | + |
| 130 | + |
| 131 | +def _fix_depth(cube, short_name, var, cfg): |
| 132 | + """Fix metadata of cube.""" |
| 133 | + cmor_info = cfg["cmor_table"].get_variable(var["mip"], short_name) |
| 134 | + if "depth0m" in cmor_info.dimensions: |
| 135 | + depth_coord = iris.coords.AuxCoord( |
| 136 | + 0.0, |
| 137 | + var_name="depth", |
| 138 | + standard_name="depth", |
| 139 | + long_name="depth", |
| 140 | + units=Unit("m"), |
| 141 | + attributes={"positive": "down"}, |
| 142 | + ) |
| 143 | + cube.add_aux_coord(depth_coord, ()) |
| 144 | + |
| 145 | + |
| 146 | +def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date): |
| 147 | + """Cmorize data.""" |
| 148 | + months = [f"{mo:02d}" for mo in range(1, 13)] |
| 149 | + fpattern = os.path.join(in_dir, cfg["filename"]) |
| 150 | + |
| 151 | + # run the cmorization |
| 152 | + for short_name, var in cfg["variables"].items(): |
| 153 | + var_info = cfg["cmor_table"].get_variable(var["mip"], short_name) |
| 154 | + var_cubes = iris.cube.CubeList() |
| 155 | + logger.info("CMORizing var %s from file type %s", short_name, fpattern) |
| 156 | + # fx files are time invariant |
| 157 | + if short_name in ["areacella", "areacello", "sftlf", "sftof"]: |
| 158 | + filename = fpattern.format(year=cfg["start_year"], month="01") |
| 159 | + cube = extract_variable(short_name, var, filename) |
| 160 | + else: |
| 161 | + for year in range(cfg["start_year"], cfg["end_year"] + 1): |
| 162 | + for month in months: |
| 163 | + filename = fpattern.format(year=year, month=month) |
| 164 | + var_cubes.append( |
| 165 | + extract_variable(short_name, var, filename) |
| 166 | + ) |
| 167 | + |
| 168 | + cube = var_cubes.merge_cube() |
| 169 | + |
| 170 | + cube.var_name = short_name |
| 171 | + fix_coords(cube) |
| 172 | + _fix_depth(cube, short_name, var, cfg) |
| 173 | + fix_var_metadata(cube, var_info) |
| 174 | + attrs = cfg["attributes"] |
| 175 | + attrs["mip"] = var["mip"] |
| 176 | + set_global_atts(cube, attrs) |
| 177 | + |
| 178 | + save_variable( |
| 179 | + cube, short_name, out_dir, attrs, unlimited_dimensions=["time"] |
| 180 | + ) |
0 commit comments