diff --git a/src/earthkit/data/field/component/parameter.py b/src/earthkit/data/field/component/parameter.py index 7e24b904..bf7530ae 100644 --- a/src/earthkit/data/field/component/parameter.py +++ b/src/earthkit/data/field/component/parameter.py @@ -34,6 +34,9 @@ class ParameterBase(SimpleFieldComponent): of supported keys are as follows: - "variable": string representing the parameter variable + - "standard_name": string representing the standard name of the parameter variable, based + on the CF standard name + - "long_name": string representing the long name of the parameter variable - "units": as a string or a :class:`Units` object representing the parameter units - "chem_variable": string representing the parameter chemical variable - "param": alias of "variable" @@ -105,6 +108,25 @@ def chem_variable(self) -> Optional[str]: def param(self) -> Optional[str]: pass + @mark_get_key + @abstractmethod + def standard_name(self) -> Optional[str]: + """Return the standard name of the parameter variable. + + The standard name is a string representing the standard name of the parameter variable. It is + based on the CF standard name. + """ + pass + + @mark_get_key + @abstractmethod + def long_name(self) -> Optional[str]: + """Return the long name of the parameter variable. + + The long name is a string representing the long name of the parameter variable + """ + pass + def create_parameter(d: dict) -> "ParameterBase": """Create a ParameterBase object from a dictionary. @@ -123,7 +145,12 @@ def create_parameter(d: dict) -> "ParameterBase": raise TypeError(f"Cannot create Parameter from {type(d)}, expected dict") cls = Parameter - d1 = cls._normalise_create_kwargs(d, allowed_keys=("variable", "units", "chem_variable")) + d1 = cls._normalise_create_kwargs( + d, allowed_keys=("variable", "units", "chem_variable", "standard_name", "long_name") + ) + if "variable" not in d1: + raise ValueError("Cannot create Parameter without variable") + return cls(**d1) @@ -137,6 +164,20 @@ def variable(self) -> None: """ return None + def standard_name(self) -> None: + r"""Return the standard name of the parameter variable. + + An EmptyParameter does not contain any parameter information, and this method returns None. + """ + return None + + def long_name(self) -> None: + r"""Return the long name of the parameter variable. + + An EmptyParameter does not contain any parameter information, and this method returns None. + """ + return None + def units(self) -> None: r"""Return the parameter units. @@ -191,8 +232,17 @@ class Parameter(ParameterBase): _chem_variable = None - def __init__(self, variable: str = None, units: Union[str, "Units"] = None, chem_variable: str = None) -> None: + def __init__( + self, + variable: str = None, + standard_name: str = None, + long_name: str = None, + units: Union[str, "Units"] = None, + chem_variable: str = None, + ) -> None: self._variable = variable + self._standard_name = standard_name + self._long_name = long_name self._units = Units.from_any(units) if chem_variable is not None: self._chem_variable = chem_variable @@ -200,6 +250,12 @@ def __init__(self, variable: str = None, units: Union[str, "Units"] = None, chem def variable(self) -> Optional[str]: return self._variable + def standard_name(self) -> Optional[str]: + return self._standard_name + + def long_name(self) -> Optional[str]: + return self._long_name + def units(self) -> Optional["Units"]: return self._units @@ -228,17 +284,31 @@ def from_dict(cls, d: dict) -> "Parameter": return create_parameter(d) def to_dict(self): - return {"variable": self._variable, "units": str(self._units)} + return { + "variable": self._variable, + "standard_name": self._standard_name, + "long_name": self._long_name, + "units": str(self._units), + "chem_variable": self._chem_variable, + } def __getstate__(self): state = {} state["variable"] = self._variable + state["standard_name"] = self._standard_name + state["long_name"] = self._long_name state["units"] = str(self._units) state["chem_variable"] = self._chem_variable return state def __setstate__(self, state): - self.__init__(variable=state["variable"], units=state["units"], chem_id=state["chem_variable"]) + self.__init__( + variable=state["variable"], + standard_name=state["standard_name"], + long_name=state["long_name"], + units=state["units"], + chem_variable=state["chem_variable"], + ) def set(self, *args, **kwargs): """Create a new instance with updated data. @@ -254,11 +324,18 @@ def set(self, *args, **kwargs): - "variable": The parameter variable. - "units": The parameter units, as a string or a Units object. + - "standard_name": The standard name of the parameter variable. + - "long_name": The long name of the parameter variable. + - "chem_variable": The chemical variable of the parameter. """ - d = self._normalise_set_kwargs(*args, allowed_keys=("variable", "units", "chem_variable"), **kwargs) + d = self._normalise_set_kwargs( + *args, allowed_keys=("variable", "units", "chem_variable", "standard_name", "long_name"), **kwargs + ) current = { "variable": self._variable, + "standard_name": self._standard_name, + "long_name": self._long_name, "units": self._units, "chem_variable": self._chem_variable, } diff --git a/src/earthkit/data/field/grib/parameter.py b/src/earthkit/data/field/grib/parameter.py index 8d5e9827..c9061d86 100644 --- a/src/earthkit/data/field/grib/parameter.py +++ b/src/earthkit/data/field/grib/parameter.py @@ -32,14 +32,18 @@ def _get(key, default=None): v = handle.get("paramId", ktype=str, default=None) if v is None: v = _get("param", None) - name = v + variable = v + standard_name = _get("cfName", None) + long_name = _get("name", None) units = _get("units", None) chem_name = _get("chemShortName", None) return dict( - variable=name, + variable=variable, + standard_name=standard_name, + long_name=long_name, units=units, chem_variable=chem_name, ) diff --git a/src/earthkit/data/field/xarray/parameter.py b/src/earthkit/data/field/xarray/parameter.py index 6d56442d..3d68e204 100644 --- a/src/earthkit/data/field/xarray/parameter.py +++ b/src/earthkit/data/field/xarray/parameter.py @@ -32,6 +32,8 @@ def __init__(self, owner, selection=None) -> None: """ # self.owner = owner name = owner.name + standard_name = owner.variable.attrs.get("standard_name", "unknown") + long_name = owner.variable.attrs.get("long_name", "unknown") units = owner.variable.attrs.get("units", None) - spec = Parameter.from_dict(dict(variable=name, units=units)) - super().__init__(spec) + p = Parameter.from_dict(dict(variable=name, standard_name=standard_name, long_name=long_name, units=units)) + super().__init__(p) diff --git a/tests/field/test_parameter_component.py b/tests/field/test_parameter_component.py index 2fc691d9..bb741277 100644 --- a/tests/field/test_parameter_component.py +++ b/tests/field/test_parameter_component.py @@ -19,6 +19,8 @@ def test_parameter_component_alias_1(): assert r.variable() == "t" assert r.param() == "t" assert r.units() == "K" + assert r.standard_name() is None + assert r.long_name() is None @pytest.mark.parametrize( @@ -26,7 +28,17 @@ def test_parameter_component_alias_1(): [ ( [{"variable": "t", "units": "K"}, {"param": "t", "units": "K"}], - ("t", "K"), + {"variable": "t", "param": "t", "units": "K", "standard_name": None, "long_name": None}, + ), + ( + {"variable": "t", "units": "K", "standard_name": "air_temperature", "long_name": "Temperature"}, + { + "variable": "t", + "param": "t", + "units": "K", + "standard_name": "air_temperature", + "long_name": "Temperature", + }, ), ], ) @@ -39,9 +51,11 @@ def test_parameter_component_from_dict_ok(input_d, ref): for d in input_d: r = Parameter.from_dict(d) - assert r.variable() == ref[0] - assert r.param() == ref[0] - assert r.units() == ref[1] + assert r.variable() == ref["variable"] + assert r.param() == ref["param"] + assert r.units() == ref["units"] + assert r.standard_name() == ref["standard_name"] + assert r.long_name() == ref["long_name"] @pytest.mark.parametrize( diff --git a/tests/grib/test_grib_parameter.py b/tests/grib/test_grib_parameter.py index c9e59ddc..55b50eff 100644 --- a/tests/grib/test_grib_parameter.py +++ b/tests/grib/test_grib_parameter.py @@ -24,10 +24,24 @@ def test_grib_parameter_1(fl_type): f = ds[0] assert f.parameter.variable() == "2t" + assert f.parameter.standard_name() == "unknown" + assert f.parameter.long_name() == "2 metre temperature" assert f.parameter.param() == "2t" assert f.parameter.units() == "K" +@pytest.mark.parametrize("fl_type", FL_TYPES) +def test_grib_parameter_2(fl_type): + ds, _ = load_grib_data("tuv_pl.grib", fl_type) + f = ds[0] + + assert f.parameter.variable() == "t" + assert f.parameter.standard_name() == "air_temperature" + assert f.parameter.long_name() == "Temperature" + assert f.parameter.param() == "t" + assert f.parameter.units() == "K" + + @pytest.mark.parametrize("fl_type", FL_FILE) def test_grib_parameter_tilde_shortname(fl_type): # the shortName is ~ in the grib file @@ -38,6 +52,8 @@ def test_grib_parameter_tilde_shortname(fl_type): assert f.parameter.variable() == "106" assert f.parameter.param() == "106" assert f.parameter.units() == "~" + assert f.parameter.standard_name() == "unknown" + assert f.parameter.long_name() == "Experimental product" @pytest.mark.parametrize("fl_type", FL_TYPES) diff --git a/tests/grib/test_grib_summary.py b/tests/grib/test_grib_summary.py index b3c1d9c1..2132a0c6 100644 --- a/tests/grib/test_grib_summary.py +++ b/tests/grib/test_grib_summary.py @@ -329,7 +329,10 @@ def test_grib_ls_component(_kwargs, expected_values, fl_type): f, _ = load_grib_data("tuv_pl.grib", fl_type) df = f.ls(**_kwargs) - assert expected_values == df.to_dict() + d = df.to_dict() + for k, v in expected_values.items(): + assert k in d, f"key {k} missing from result" + assert v == d[k], f"key {k} expected {v} got {d[k]}" @pytest.mark.parametrize("fl_type", FL_TYPES) diff --git a/tests/netcdf/test_netcdf_metadata.py b/tests/netcdf/test_netcdf_metadata.py index 022cb53e..56c59c89 100644 --- a/tests/netcdf/test_netcdf_metadata.py +++ b/tests/netcdf/test_netcdf_metadata.py @@ -21,6 +21,9 @@ "key,expected_value", [ ("parameter.variable", "t"), + ("parameter.standard_name", "air_temperature"), + ("parameter.long_name", "Temperature"), + ("parameter.units", "K"), ("vertical.level", 1000), (["parameter.variable"], ["t"]), (["parameter.variable", "vertical.level"], ["t", 1000]),