Repository: ESMValGroup/ESMValCore

Issue:
New custom variable for surface methane data
I would like to add methane flask measurements from ground-based stations (NOAA network) to our datapool. For this we need a custom CMOR table since this is not available yet for CMIP5 or CMIP6.
The custom variable would have the dimensions (latitude, longitude, time), in a first instance only for monthly mean values. The values are only available at the surface, so no pressure/altitude coordinate is necessary. 


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ESMValGroup/ESMValCore/pull/2168?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ESMValGroup) Report
> Merging [#2168](https://app.codecov.io/gh/ESMValGroup/ESMValCore/pull/2168?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ESMValGroup) (eefe791) into [main](https://app.codecov.io/gh/ESMValGroup/ESMValCore/commit/6b43ca25ee18b09c65b4794666601eb32fa6b7bd?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ESMValGroup) (6b43ca2) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main    #2168   +/-   ##
=======================================
  Coverage   93.10%   93.10%           
=======================================
  Files         237      237           
  Lines       12822    12822           
=======================================
  Hits        11938    11938           
  Misses        884      884           
```




hb326: > Nice job, @hb326! Looks good to me 👍 Just a very minor suggestion before the merge

Thanks, @remi-kazeroni! 
I have made your suggested changes.

Patch:
diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst
index 20da29ac0..5062d9fe1 100644
--- a/doc/quickstart/find_data.rst
+++ b/doc/quickstart/find_data.rst
@@ -121,6 +121,16 @@ ERA5
 - Supported variables: ``cl``, ``clt``, ``evspsbl``, ``evspsblpot``, ``mrro``, ``pr``, ``prsn``, ``ps``, ``psl``, ``ptype``, ``rls``, ``rlds``, ``rsds``, ``rsdt``, ``rss``, ``uas``, ``vas``, ``tas``, ``tasmax``, ``tasmin``, ``tdps``, ``ts``, ``tsn`` (``E1hr``/``Amon``), ``orog`` (``fx``)
 - Tier: 3
 
+.. note:: According to the description of Evapotranspiration and potential Evapotranspiration on the Copernicus page 
+  (https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-single-levels-monthly-means?tab=overview):
+  "The ECMWF Integrated Forecasting System (IFS) convention is that downward fluxes are positive. 
+  Therefore, negative values indicate evaporation and positive values indicate condensation."
+  
+  In the CMOR table, these fluxes are defined as positive, if they go from the surface into the atmosphere:
+  "Evaporation at surface (also known as evapotranspiration): flux of water into the atmosphere due to conversion 
+  of both liquid and solid phases to vapor (from underlying surface and vegetation)."
+  Therefore, the ERA5 (and ERA5-Land) CMORizer switches the signs of ``evspsbl`` and ``evspsblpot`` to be compatible with the CMOR standard used e.g. by the CMIP models.
+
 .. _read_native_mswep:
 
 MSWEP
diff --git a/esmvalcore/cmor/_fixes/native6/era5.py b/esmvalcore/cmor/_fixes/native6/era5.py
index 3c621b7d4..d76fd2170 100644
--- a/esmvalcore/cmor/_fixes/native6/era5.py
+++ b/esmvalcore/cmor/_fixes/native6/era5.py
@@ -16,7 +16,6 @@ logger = logging.getLogger(__name__)
 
 def get_frequency(cube):
     """Determine time frequency of input cube."""
-
     try:
         time = cube.coord(axis='T')
     except iris.exceptions.CoordinateNotFoundError:
@@ -111,6 +110,8 @@ class Evspsbl(Fix):
             fix_hourly_time_coordinate(cube)
             fix_accumulated_units(cube)
             multiply_with_density(cube)
+            # Correct sign to align with CMOR standards
+            cube.data = cube.core_data() * -1.0
 
         return cubes
 
@@ -126,6 +127,8 @@ class Evspsblpot(Fix):
             fix_hourly_time_coordinate(cube)
             fix_accumulated_units(cube)
             multiply_with_density(cube)
+            # Correct sign to align with CMOR standards
+            cube.data = cube.core_data() * -1.0
 
         return cubes
 
@@ -315,6 +318,7 @@ class Tasmax(Fix):
     """Fixes for tasmax."""
 
     def fix_metadata(self, cubes):
+        """Fix metadata."""
         for cube in cubes:
             fix_hourly_time_coordinate(cube)
         return cubes
@@ -324,6 +328,7 @@ class Tasmin(Fix):
     """Fixes for tasmin."""
 
     def fix_metadata(self, cubes):
+        """Fix metadata."""
         for cube in cubes:
             fix_hourly_time_coordinate(cube)
         return cubes
diff --git a/esmvalcore/cmor/_fixes/native6/era5_land.py b/esmvalcore/cmor/_fixes/native6/era5_land.py
index a2b1ebae9..5b6fbff27 100644
--- a/esmvalcore/cmor/_fixes/native6/era5_land.py
+++ b/esmvalcore/cmor/_fixes/native6/era5_land.py
@@ -2,10 +2,14 @@
 import logging
 
 from esmvalcore.cmor._fixes.native6.era5 import (Pr,
+                                                 Evspsbl,
+                                                 Evspsblpot,
                                                  AllVars)
 
 
 logger = logging.getLogger(__name__)
 logger.info("Load classes from era5.py")
 logger.info(Pr)
+logger.info(Evspsbl)
+logger.info(Evspsblpot)
 logger.info(AllVars)
diff --git a/esmvalcore/cmor/tables/custom/CMOR_ch4s.dat b/esmvalcore/cmor/tables/custom/CMOR_ch4s.dat
new file mode 100644
index 000000000..cdc164a3b
--- /dev/null
+++ b/esmvalcore/cmor/tables/custom/CMOR_ch4s.dat
@@ -0,0 +1,22 @@
+SOURCE: CMIP5
+!============
+variable_entry:    ch4s
+!============
+modeling_realm:    atmos
+!----------------------------------
+! Variable attributes:
+!----------------------------------
+standard_name:     mole_fraction_of_methane_in_air
+units:             1e-09
+cell_methods:      area: time: mean
+cell_measures:     area: areacella
+long_name:         Atmosphere CH4 surface
+comment:           As ch4, but only at the surface
+!----------------------------------
+! Additional variable information:
+!----------------------------------
+dimensions:        longitude latitude time
+out_name:          ch4s
+type:              real
+!----------------------------------
+!


Test patch:
diff --git a/tests/integration/cmor/_fixes/native6/test_era5.py b/tests/integration/cmor/_fixes/native6/test_era5.py
index e08d05b8c..cfe20e7fd 100644
--- a/tests/integration/cmor/_fixes/native6/test_era5.py
+++ b/tests/integration/cmor/_fixes/native6/test_era5.py
@@ -300,7 +300,7 @@ def clt_cmor_e1hr():
 def evspsbl_era5_hourly():
     time = _era5_time('hourly')
     cube = iris.cube.Cube(
-        _era5_data('hourly'),
+        _era5_data('hourly') * -1.,
         long_name='total evapotranspiration',
         var_name='e',
         units='unknown',
@@ -337,7 +337,7 @@ def evspsbl_cmor_e1hr():
 def evspsblpot_era5_hourly():
     time = _era5_time('hourly')
     cube = iris.cube.Cube(
-        _era5_data('hourly'),
+        _era5_data('hourly') * -1.,
         long_name='potential evapotranspiration',
         var_name='epot',
         units='unknown',
diff --git a/tests/integration/cmor/test_table.py b/tests/integration/cmor/test_table.py
index f3845633a..7b25b8d71 100644
--- a/tests/integration/cmor/test_table.py
+++ b/tests/integration/cmor/test_table.py
@@ -476,3 +476,12 @@ class TestCustomInfo(unittest.TestCase):
         self.assertEqual(var.long_name,
                          'Global-mean Near-Surface Air Temperature Anomaly')
         self.assertEqual(var.units, 'K')
+
+    def test_get_variable_ch4s(self):
+        """Get tas variable."""
+        CustomInfo()
+        var = self.variables_info.get_variable('Amon', 'ch4s')
+        self.assertEqual(var.short_name, 'ch4s')
+        self.assertEqual(var.long_name,
+                         'Atmosphere CH4 surface')
+        self.assertEqual(var.units, '1e-09')