Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 82 additions & 5 deletions src/earthkit/data/field/component/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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)


Expand All @@ -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.

Expand Down Expand Up @@ -191,15 +232,30 @@ 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

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

Expand Down Expand Up @@ -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.
Expand All @@ -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,
}
Expand Down
8 changes: 6 additions & 2 deletions src/earthkit/data/field/grib/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
6 changes: 4 additions & 2 deletions src/earthkit/data/field/xarray/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 18 additions & 4 deletions tests/field/test_parameter_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,26 @@ 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(
"input_d,ref",
[
(
[{"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",
},
),
],
)
Expand All @@ -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(
Expand Down
16 changes: 16 additions & 0 deletions tests/grib/test_grib_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion tests/grib/test_grib_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions tests/netcdf/test_netcdf_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down
Loading